mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-04-19 01:27:26 +00:00
Added usage plots and ADC reading for Vcc
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
bool AmsConfiguration::getSystemConfig(SystemConfig& config) {
|
||||
if(hasConfig()) {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.begin(SPI_FLASH_SEC_SIZE);
|
||||
EEPROM.get(CONFIG_SYSTEM_START, config);
|
||||
EEPROM.end();
|
||||
return true;
|
||||
@@ -12,7 +12,7 @@ bool AmsConfiguration::getSystemConfig(SystemConfig& config) {
|
||||
}
|
||||
|
||||
bool AmsConfiguration::setSystemConfig(SystemConfig& config) {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.begin(SPI_FLASH_SEC_SIZE);
|
||||
EEPROM.put(CONFIG_SYSTEM_START, config);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
@@ -21,7 +21,7 @@ bool AmsConfiguration::setSystemConfig(SystemConfig& config) {
|
||||
|
||||
bool AmsConfiguration::getWiFiConfig(WiFiConfig& config) {
|
||||
if(hasConfig()) {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.begin(SPI_FLASH_SEC_SIZE);
|
||||
EEPROM.get(CONFIG_WIFI_START, config);
|
||||
EEPROM.end();
|
||||
return true;
|
||||
@@ -45,7 +45,7 @@ bool AmsConfiguration::setWiFiConfig(WiFiConfig& config) {
|
||||
} else {
|
||||
wifiChanged = true;
|
||||
}
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.begin(SPI_FLASH_SEC_SIZE);
|
||||
EEPROM.put(CONFIG_WIFI_START, config);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
@@ -85,7 +85,7 @@ void AmsConfiguration::ackWifiChange() {
|
||||
|
||||
bool AmsConfiguration::getMqttConfig(MqttConfig& config) {
|
||||
if(hasConfig()) {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.begin(SPI_FLASH_SEC_SIZE);
|
||||
EEPROM.get(CONFIG_MQTT_START, config);
|
||||
EEPROM.end();
|
||||
return true;
|
||||
@@ -110,7 +110,7 @@ bool AmsConfiguration::setMqttConfig(MqttConfig& config) {
|
||||
} else {
|
||||
mqttChanged = true;
|
||||
}
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.begin(SPI_FLASH_SEC_SIZE);
|
||||
EEPROM.put(CONFIG_MQTT_START, config);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
@@ -143,7 +143,7 @@ void AmsConfiguration::ackMqttChange() {
|
||||
|
||||
bool AmsConfiguration::getWebConfig(WebConfig& config) {
|
||||
if(hasConfig()) {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.begin(SPI_FLASH_SEC_SIZE);
|
||||
EEPROM.get(CONFIG_WEB_START, config);
|
||||
EEPROM.end();
|
||||
return true;
|
||||
@@ -154,7 +154,7 @@ bool AmsConfiguration::getWebConfig(WebConfig& config) {
|
||||
}
|
||||
|
||||
bool AmsConfiguration::setWebConfig(WebConfig& config) {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.begin(SPI_FLASH_SEC_SIZE);
|
||||
EEPROM.put(CONFIG_WEB_START, config);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
@@ -169,7 +169,7 @@ void AmsConfiguration::clearAuth(WebConfig& config) {
|
||||
|
||||
bool AmsConfiguration::getMeterConfig(MeterConfig& config) {
|
||||
if(hasConfig()) {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.begin(SPI_FLASH_SEC_SIZE);
|
||||
EEPROM.get(CONFIG_METER_START, config);
|
||||
EEPROM.end();
|
||||
return true;
|
||||
@@ -193,7 +193,7 @@ bool AmsConfiguration::setMeterConfig(MeterConfig& config) {
|
||||
} else {
|
||||
meterChanged = true;
|
||||
}
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.begin(SPI_FLASH_SEC_SIZE);
|
||||
EEPROM.put(CONFIG_METER_START, config);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
@@ -221,7 +221,7 @@ void AmsConfiguration::ackMeterChanged() {
|
||||
|
||||
bool AmsConfiguration::getDebugConfig(DebugConfig& config) {
|
||||
if(hasConfig()) {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.begin(SPI_FLASH_SEC_SIZE);
|
||||
EEPROM.get(CONFIG_DEBUG_START, config);
|
||||
EEPROM.end();
|
||||
return true;
|
||||
@@ -232,7 +232,7 @@ bool AmsConfiguration::getDebugConfig(DebugConfig& config) {
|
||||
}
|
||||
|
||||
bool AmsConfiguration::setDebugConfig(DebugConfig& config) {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.begin(SPI_FLASH_SEC_SIZE);
|
||||
EEPROM.put(CONFIG_DEBUG_START, config);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
@@ -247,7 +247,7 @@ void AmsConfiguration::clearDebug(DebugConfig& config) {
|
||||
|
||||
bool AmsConfiguration::getDomoticzConfig(DomoticzConfig& config) {
|
||||
if(hasConfig()) {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.begin(SPI_FLASH_SEC_SIZE);
|
||||
EEPROM.get(CONFIG_DOMOTICZ_START, config);
|
||||
EEPROM.end();
|
||||
return true;
|
||||
@@ -269,7 +269,7 @@ bool AmsConfiguration::setDomoticzConfig(DomoticzConfig& config) {
|
||||
domoChanged = true;
|
||||
}
|
||||
mqttChanged = domoChanged;
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.begin(SPI_FLASH_SEC_SIZE);
|
||||
EEPROM.put(CONFIG_DOMOTICZ_START, config);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
@@ -310,7 +310,7 @@ bool AmsConfiguration::pinUsed(uint8_t pin, GpioConfig& config) {
|
||||
|
||||
bool AmsConfiguration::getGpioConfig(GpioConfig& config) {
|
||||
if(hasConfig()) {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.begin(SPI_FLASH_SEC_SIZE);
|
||||
EEPROM.get(CONFIG_GPIO_START, config);
|
||||
EEPROM.end();
|
||||
return true;
|
||||
@@ -366,7 +366,7 @@ bool AmsConfiguration::setGpioConfig(GpioConfig& config) {
|
||||
if(config.apPin >= 0)
|
||||
pinMode(config.apPin, INPUT_PULLUP);
|
||||
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.begin(SPI_FLASH_SEC_SIZE);
|
||||
EEPROM.put(CONFIG_GPIO_START, config);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
@@ -388,11 +388,13 @@ void AmsConfiguration::clearGpio(GpioConfig& config) {
|
||||
config.vccOffset = 0;
|
||||
config.vccMultiplier = 1000;
|
||||
config.vccBootLimit = 0;
|
||||
config.vccResistorGnd = 0;
|
||||
config.vccResistorVcc = 0;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::getNtpConfig(NtpConfig& config) {
|
||||
if(hasConfig()) {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.begin(SPI_FLASH_SEC_SIZE);
|
||||
EEPROM.get(CONFIG_NTP_START, config);
|
||||
EEPROM.end();
|
||||
return true;
|
||||
@@ -419,7 +421,7 @@ bool AmsConfiguration::setNtpConfig(NtpConfig& config) {
|
||||
} else {
|
||||
ntpChanged = true;
|
||||
}
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.begin(SPI_FLASH_SEC_SIZE);
|
||||
EEPROM.put(CONFIG_NTP_START, config);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
@@ -444,7 +446,7 @@ void AmsConfiguration::clearNtp(NtpConfig& config) {
|
||||
|
||||
bool AmsConfiguration::getEntsoeConfig(EntsoeConfig& config) {
|
||||
if(hasConfig()) {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.begin(SPI_FLASH_SEC_SIZE);
|
||||
EEPROM.get(CONFIG_ENTSOE_START, config);
|
||||
EEPROM.end();
|
||||
return true;
|
||||
@@ -463,7 +465,7 @@ bool AmsConfiguration::setEntsoeConfig(EntsoeConfig& config) {
|
||||
} else {
|
||||
entsoeChanged = true;
|
||||
}
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.begin(SPI_FLASH_SEC_SIZE);
|
||||
EEPROM.put(CONFIG_ENTSOE_START, config);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
@@ -486,7 +488,7 @@ void AmsConfiguration::ackEntsoeChange() {
|
||||
}
|
||||
|
||||
void AmsConfiguration::clear() {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.begin(SPI_FLASH_SEC_SIZE);
|
||||
MeterConfig meter;
|
||||
clearMeter(meter);
|
||||
EEPROM.put(CONFIG_METER_START, meter);
|
||||
@@ -522,7 +524,7 @@ void AmsConfiguration::clear() {
|
||||
|
||||
bool AmsConfiguration::hasConfig() {
|
||||
if(configVersion == 0) {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.begin(SPI_FLASH_SEC_SIZE);
|
||||
configVersion = EEPROM.read(EEPROM_CONFIG_ADDRESS);
|
||||
EEPROM.end();
|
||||
}
|
||||
@@ -553,6 +555,14 @@ bool AmsConfiguration::hasConfig() {
|
||||
configVersion = 0;
|
||||
return false;
|
||||
}
|
||||
case 88:
|
||||
configVersion = -1; // Prevent loop
|
||||
if(relocateConfig88()) {
|
||||
configVersion = 89;
|
||||
} else {
|
||||
configVersion = 0;
|
||||
return false;
|
||||
}
|
||||
case EEPROM_CHECK_SUM:
|
||||
return true;
|
||||
default:
|
||||
@@ -567,7 +577,7 @@ int AmsConfiguration::getConfigVersion() {
|
||||
}
|
||||
|
||||
void AmsConfiguration::loadTempSensors() {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.begin(SPI_FLASH_SEC_SIZE);
|
||||
TempSensorConfig* tempSensors[32];
|
||||
int address = EEPROM_TEMP_CONFIG_ADDRESS;
|
||||
int c = 0;
|
||||
@@ -606,7 +616,7 @@ void AmsConfiguration::saveTempSensors() {
|
||||
|
||||
bool AmsConfiguration::loadConfig83(int address) {
|
||||
ConfigObject83 c;
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.begin(SPI_FLASH_SEC_SIZE);
|
||||
EEPROM.get(address, c);
|
||||
|
||||
EntsoeConfig entsoe {"", "", "", 1000};
|
||||
@@ -644,7 +654,9 @@ bool AmsConfiguration::loadConfig83(int address) {
|
||||
c.vccPin,
|
||||
c.vccOffset,
|
||||
c.vccMultiplier,
|
||||
c.vccBootLimit
|
||||
c.vccBootLimit,
|
||||
0,
|
||||
0
|
||||
};
|
||||
EEPROM.put(CONFIG_GPIO_START, gpio);
|
||||
|
||||
@@ -715,7 +727,7 @@ bool AmsConfiguration::loadConfig83(int address) {
|
||||
bool AmsConfiguration::relocateConfig86() {
|
||||
MqttConfig86 mqtt86;
|
||||
MqttConfig mqtt;
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.begin(SPI_FLASH_SEC_SIZE);
|
||||
EEPROM.get(CONFIG_MQTT_START_86, mqtt86);
|
||||
strcpy(mqtt.host, mqtt86.host);
|
||||
mqtt.port = mqtt86.port;
|
||||
@@ -736,7 +748,7 @@ bool AmsConfiguration::relocateConfig86() {
|
||||
bool AmsConfiguration::relocateConfig87() {
|
||||
MeterConfig87 meter87;
|
||||
MeterConfig meter;
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.begin(SPI_FLASH_SEC_SIZE);
|
||||
EEPROM.get(CONFIG_METER_START_87, meter87);
|
||||
if(meter87.type < 5) {
|
||||
meter.baud = 2400;
|
||||
@@ -757,8 +769,38 @@ bool AmsConfiguration::relocateConfig87() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::relocateConfig88() {
|
||||
GpioConfig88 gpio88;
|
||||
EEPROM.begin(SPI_FLASH_SEC_SIZE);
|
||||
EEPROM.get(CONFIG_GPIO_START_88, gpio88);
|
||||
|
||||
GpioConfig gpio {
|
||||
gpio88.hanPin,
|
||||
gpio88.apPin,
|
||||
gpio88.ledPin,
|
||||
gpio88.ledInverted,
|
||||
gpio88.ledPinRed,
|
||||
gpio88.ledPinGreen,
|
||||
gpio88.ledPinBlue,
|
||||
gpio88.ledRgbInverted,
|
||||
gpio88.tempSensorPin,
|
||||
gpio88.tempAnalogSensorPin,
|
||||
gpio88.vccPin,
|
||||
gpio88.vccOffset,
|
||||
gpio88.vccMultiplier,
|
||||
gpio88.vccBootLimit,
|
||||
0,
|
||||
0
|
||||
};
|
||||
EEPROM.put(CONFIG_GPIO_START, gpio);
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, 89);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::save() {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.begin(SPI_FLASH_SEC_SIZE);
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, EEPROM_CHECK_SUM);
|
||||
saveTempSensors();
|
||||
bool success = EEPROM.commit();
|
||||
@@ -938,9 +980,17 @@ void AmsConfiguration::print(Print* debugger)
|
||||
debugger->printf("Temperature pin: %i\r\n", gpio.tempSensorPin);
|
||||
debugger->printf("Temp analog pin: %i\r\n", gpio.tempAnalogSensorPin);
|
||||
debugger->printf("Vcc pin: %i\r\n", gpio.vccPin);
|
||||
if(gpio.vccMultiplier > 0) {
|
||||
debugger->printf("Vcc multiplier: %f\r\n", gpio.vccMultiplier / 1000.0);
|
||||
}
|
||||
if(gpio.vccOffset > 0) {
|
||||
debugger->printf("Vcc offset: %f\r\n", gpio.vccOffset / 100.0);
|
||||
}
|
||||
if(gpio.vccBootLimit > 0) {
|
||||
debugger->printf("Vcc boot limit: %f\r\n", gpio.vccBootLimit / 10.0);
|
||||
}
|
||||
debugger->printf("GND resistor: %i\r\n", gpio.vccResistorGnd);
|
||||
debugger->printf("Vcc resistor: %i\r\n", gpio.vccResistorVcc);
|
||||
debugger->println("");
|
||||
delay(10);
|
||||
Serial.flush();
|
||||
|
||||
@@ -3,17 +3,16 @@
|
||||
#include <EEPROM.h>
|
||||
#include "Arduino.h"
|
||||
|
||||
#define EEPROM_SIZE 1024 * 3
|
||||
#define EEPROM_CHECK_SUM 88 // Used to check if config is stored. Change if structure changes
|
||||
#define EEPROM_CHECK_SUM 89 // Used to check if config is stored. Change if structure changes
|
||||
#define EEPROM_CONFIG_ADDRESS 0
|
||||
#define EEPROM_TEMP_CONFIG_ADDRESS 2048
|
||||
|
||||
#define CONFIG_SYSTEM_START 8
|
||||
#define CONFIG_WIFI_START 16
|
||||
#define CONFIG_METER_START 224
|
||||
#define CONFIG_GPIO_START 266
|
||||
#define CONFIG_WEB_START 648
|
||||
#define CONFIG_DEBUG_START 824
|
||||
#define CONFIG_GPIO_START 832
|
||||
#define CONFIG_DOMOTICZ_START 856
|
||||
#define CONFIG_NTP_START 872
|
||||
#define CONFIG_ENTSOE_START 944
|
||||
@@ -21,6 +20,7 @@
|
||||
|
||||
#define CONFIG_MQTT_START_86 224
|
||||
#define CONFIG_METER_START_87 784
|
||||
#define CONFIG_GPIO_START_88 832
|
||||
|
||||
|
||||
struct SystemConfig {
|
||||
@@ -111,6 +111,25 @@ struct GpioConfig {
|
||||
int16_t vccOffset;
|
||||
uint16_t vccMultiplier;
|
||||
uint8_t vccBootLimit;
|
||||
uint16_t vccResistorGnd;
|
||||
uint16_t vccResistorVcc;
|
||||
}; // 20
|
||||
|
||||
struct GpioConfig88 {
|
||||
uint8_t hanPin;
|
||||
uint8_t apPin;
|
||||
uint8_t ledPin;
|
||||
bool ledInverted;
|
||||
uint8_t ledPinRed;
|
||||
uint8_t ledPinGreen;
|
||||
uint8_t ledPinBlue;
|
||||
bool ledRgbInverted;
|
||||
uint8_t tempSensorPin;
|
||||
uint8_t tempAnalogSensorPin;
|
||||
uint8_t vccPin;
|
||||
int16_t vccOffset;
|
||||
uint16_t vccMultiplier;
|
||||
uint8_t vccBootLimit;
|
||||
}; // 16
|
||||
|
||||
struct DomoticzConfig {
|
||||
@@ -295,6 +314,7 @@ private:
|
||||
bool loadConfig83(int address);
|
||||
bool relocateConfig86();
|
||||
bool relocateConfig87();
|
||||
bool relocateConfig88(); // dev 1.6
|
||||
|
||||
int readString(int pAddress, char* pString[]);
|
||||
int readInt(int pAddress, int *pValue);
|
||||
|
||||
308
src/AmsDataStorage.cpp
Normal file
308
src/AmsDataStorage.cpp
Normal file
@@ -0,0 +1,308 @@
|
||||
#include "AmsDataStorage.h"
|
||||
#include <lwip/apps/sntp.h>
|
||||
#include "EEPROM.h"
|
||||
#include "LittleFS.h"
|
||||
#include "AmsStorage.h"
|
||||
|
||||
AmsDataStorage::AmsDataStorage(RemoteDebug* debugger) {
|
||||
day.version = 3;
|
||||
month.version = 4;
|
||||
this->debugger = debugger;
|
||||
}
|
||||
|
||||
void AmsDataStorage::setTimezone(Timezone* tz) {
|
||||
this->tz = tz;
|
||||
}
|
||||
|
||||
bool AmsDataStorage::update(AmsData* data) {
|
||||
if(data->getListType() != 3) return false;
|
||||
time_t now = time(nullptr);
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf("(AmsDataStorage) Time is: %d\n", now);
|
||||
}
|
||||
if(now < EPOCH_2021_01_01) {
|
||||
now = data->getMeterTimestamp();
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf("(AmsDataStorage) New time is: %d\n", now);
|
||||
}
|
||||
}
|
||||
if(now-day.lastMeterReadTime < 3590) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) {
|
||||
debugger->printf("(AmsDataStorage) It is only %d seconds since last update, ignoring\n", (now-day.lastMeterReadTime));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
tmElements_t tm;
|
||||
breakTime(now, tm);
|
||||
|
||||
if(tm.Minute > 5) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf("(AmsDataStorage) Already %d minutes into the hour, ignoring\n", tm.Minute);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int16_t val = (day.activeImport == 0 ? 0 : ((data->getActiveImportCounter()*1000) - day.activeImport) - ((data->getActiveExportCounter()*1000) - day.activeExport)) / 10;
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf("(AmsDataStorage) Usage for hour %d: %d", tm.Hour, val);
|
||||
}
|
||||
|
||||
if(tm.Hour == 1) {
|
||||
day.h00 = val;
|
||||
} else if(tm.Hour == 2) {
|
||||
day.h01 = val;
|
||||
} else if(tm.Hour == 3) {
|
||||
day.h02 = val;
|
||||
} else if(tm.Hour == 4) {
|
||||
day.h03 = val;
|
||||
} else if(tm.Hour == 5) {
|
||||
day.h04 = val;
|
||||
} else if(tm.Hour == 6) {
|
||||
day.h05 = val;
|
||||
} else if(tm.Hour == 7) {
|
||||
day.h06 = val;
|
||||
} else if(tm.Hour == 8) {
|
||||
day.h07 = val;
|
||||
} else if(tm.Hour == 9) {
|
||||
day.h08 = val;
|
||||
} else if(tm.Hour == 10) {
|
||||
day.h09 = val;
|
||||
} else if(tm.Hour == 11) {
|
||||
day.h10 = val;
|
||||
} else if(tm.Hour == 12) {
|
||||
day.h11 = val;
|
||||
} else if(tm.Hour == 13) {
|
||||
day.h12 = val;
|
||||
} else if(tm.Hour == 14) {
|
||||
day.h13 = val;
|
||||
} else if(tm.Hour == 15) {
|
||||
day.h14 = val;
|
||||
} else if(tm.Hour == 16) {
|
||||
day.h15 = val;
|
||||
} else if(tm.Hour == 17) {
|
||||
day.h16 = val;
|
||||
} else if(tm.Hour == 18) {
|
||||
day.h17 = val;
|
||||
} else if(tm.Hour == 19) {
|
||||
day.h18 = val;
|
||||
} else if(tm.Hour == 20) {
|
||||
day.h19 = val;
|
||||
} else if(tm.Hour == 21) {
|
||||
day.h20 = val;
|
||||
} else if(tm.Hour == 22) {
|
||||
day.h21 = val;
|
||||
} else if(tm.Hour == 23) {
|
||||
day.h22 = val;
|
||||
} else if(tm.Hour == 0) {
|
||||
day.h23 = val;
|
||||
}
|
||||
day.activeImport = data->getActiveImportCounter()*1000;
|
||||
day.activeExport = data->getActiveExportCounter()*1000;
|
||||
day.lastMeterReadTime = now;
|
||||
|
||||
// Update month plot
|
||||
if(tz != NULL) {
|
||||
time_t local = tz->toLocal(now);
|
||||
breakTime(local, tm);
|
||||
}
|
||||
|
||||
if(tm.Hour == 0) {
|
||||
val = (month.activeImport == 0 ? 0 : ((data->getActiveImportCounter()*1000) - month.activeImport) - ((data->getActiveExportCounter()*1000) - month.activeExport)) / 10;
|
||||
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf("(AmsDataStorage) Usage for day %d: %d", tm.Day, val);
|
||||
}
|
||||
|
||||
if(tm.Day == 1) {
|
||||
time_t yesterday = now-3600;
|
||||
breakTime(yesterday, tm);
|
||||
if(tm.Day == 29) {
|
||||
month.d28 = val;
|
||||
} else if(tm.Day == 30) {
|
||||
month.d29 = val;
|
||||
} else if(tm.Day == 31) {
|
||||
month.d30 = val;
|
||||
}
|
||||
} else if(tm.Day == 2) {
|
||||
month.d01 = val;
|
||||
} else if(tm.Day == 3) {
|
||||
month.d02 = val;
|
||||
} else if(tm.Day == 4) {
|
||||
month.d03 = val;
|
||||
} else if(tm.Day == 5) {
|
||||
month.d04 = val;
|
||||
} else if(tm.Day == 6) {
|
||||
month.d05 = val;
|
||||
} else if(tm.Day == 7) {
|
||||
month.d06 = val;
|
||||
} else if(tm.Day == 8) {
|
||||
month.d07 = val;
|
||||
} else if(tm.Day == 9) {
|
||||
month.d08 = val;
|
||||
} else if(tm.Day == 10) {
|
||||
month.d09 = val;
|
||||
} else if(tm.Day == 11) {
|
||||
month.d10 = val;
|
||||
} else if(tm.Day == 12) {
|
||||
month.d11 = val;
|
||||
} else if(tm.Day == 13) {
|
||||
month.d12 = val;
|
||||
} else if(tm.Day == 14) {
|
||||
month.d13 = val;
|
||||
} else if(tm.Day == 15) {
|
||||
month.d14 = val;
|
||||
} else if(tm.Day == 16) {
|
||||
month.d15 = val;
|
||||
} else if(tm.Day == 17) {
|
||||
month.d16 = val;
|
||||
} else if(tm.Day == 18) {
|
||||
month.d17 = val;
|
||||
} else if(tm.Day == 19) {
|
||||
month.d18 = val;
|
||||
} else if(tm.Day == 20) {
|
||||
month.d19 = val;
|
||||
} else if(tm.Day == 21) {
|
||||
month.d20 = val;
|
||||
} else if(tm.Day == 22) {
|
||||
month.d21 = val;
|
||||
} else if(tm.Day == 23) {
|
||||
month.d22 = val;
|
||||
} else if(tm.Day == 24) {
|
||||
month.d23 = val;
|
||||
} else if(tm.Day == 25) {
|
||||
month.d24 = val;
|
||||
} else if(tm.Day == 26) {
|
||||
month.d25 = val;
|
||||
} else if(tm.Day == 27) {
|
||||
month.d26 = val;
|
||||
} else if(tm.Day == 28) {
|
||||
month.d27 = val;
|
||||
} else if(tm.Day == 29) {
|
||||
month.d28 = val;
|
||||
} else if(tm.Day == 30) {
|
||||
month.d29 = val;
|
||||
} else if(tm.Day == 31) {
|
||||
month.d30 = val;
|
||||
}
|
||||
month.activeImport = data->getActiveImportCounter()*1000;
|
||||
month.activeExport = data->getActiveExportCounter()*1000;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
DayDataPoints AmsDataStorage::getDayDataPoints() {
|
||||
return day;
|
||||
}
|
||||
|
||||
MonthDataPoints AmsDataStorage::getMonthDataPoints() {
|
||||
return month;
|
||||
}
|
||||
|
||||
bool AmsDataStorage::load(AmsData* meterState) {
|
||||
if(!LittleFS.begin()) {
|
||||
printE("Unable to load LittleFS");
|
||||
return false;
|
||||
}
|
||||
bool ret = false;
|
||||
if(LittleFS.exists(FILE_DAYPLOT)) {
|
||||
File file = LittleFS.open(FILE_DAYPLOT, "r");
|
||||
char buf[file.size()];
|
||||
file.readBytes(buf, file.size());
|
||||
DayDataPoints* day = (DayDataPoints*) buf;
|
||||
file.close();
|
||||
|
||||
if(day->version == 3) {
|
||||
memcpy(&this->day, day, sizeof(this->day));
|
||||
ret = true;
|
||||
} else {
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(LittleFS.exists(FILE_MONTHPLOT)) {
|
||||
File file = LittleFS.open(FILE_MONTHPLOT, "r");
|
||||
char buf[file.size()];
|
||||
file.readBytes(buf, file.size());
|
||||
MonthDataPoints* month = (MonthDataPoints*) buf;
|
||||
file.close();
|
||||
|
||||
if(month->version == 3) { // dev-1.6
|
||||
month->d25 = month->d26;
|
||||
month->d26 = month->d27;
|
||||
month->d27 = month->d28;
|
||||
month->d28 = month->d29;
|
||||
month->d29 = month->d30;
|
||||
month->d30 = month->d31;
|
||||
month->version = 4;
|
||||
}
|
||||
|
||||
if(month->version == 4) {
|
||||
memcpy(&this->month, month, sizeof(this->month));
|
||||
ret = true;
|
||||
} else {
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
|
||||
LittleFS.end();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsDataStorage::save() {
|
||||
if(!LittleFS.begin()) {
|
||||
printE("Unable to load LittleFS");
|
||||
return false;
|
||||
}
|
||||
{
|
||||
File file = LittleFS.open(FILE_DAYPLOT, "w");
|
||||
char buf[sizeof(day)];
|
||||
memcpy(buf, &day, sizeof(day));
|
||||
for(int i = 0; i < sizeof(day); i++) {
|
||||
file.write(buf[i]);
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
{
|
||||
File file = LittleFS.open(FILE_MONTHPLOT, "w");
|
||||
char buf[sizeof(month)];
|
||||
memcpy(buf, &month, sizeof(month));
|
||||
for(int i = 0; i < sizeof(month); i++) {
|
||||
file.write(buf[i]);
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
|
||||
LittleFS.end();
|
||||
return true;
|
||||
}
|
||||
|
||||
void AmsDataStorage::printD(String fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf(String("(AmsDataStorage)" + fmt + "\n").c_str(), args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void AmsDataStorage::printI(String fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf(String("(AmsDataStorage)" + fmt + "\n").c_str(), args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void AmsDataStorage::printW(String fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
if(debugger->isActive(RemoteDebug::WARNING)) debugger->printf(String("(AmsDataStorage)" + fmt + "\n").c_str(), args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void AmsDataStorage::printE(String fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf(String("(AmsDataStorage)" + fmt + "\n").c_str(), args);
|
||||
va_end(args);
|
||||
}
|
||||
101
src/AmsDataStorage.h
Normal file
101
src/AmsDataStorage.h
Normal file
@@ -0,0 +1,101 @@
|
||||
#ifndef _AMSDATASTORAGE_H
|
||||
#define _AMSDATASTORAGE_H
|
||||
#include "Arduino.h"
|
||||
#include "AmsData.h"
|
||||
#include "RemoteDebug.h"
|
||||
#include "Timezone.h"
|
||||
|
||||
#define EPOCH_2021_01_01 1609459200
|
||||
|
||||
struct DayDataPoints {
|
||||
uint8_t version;
|
||||
int16_t h00;
|
||||
int16_t h01;
|
||||
int16_t h02;
|
||||
int16_t h03;
|
||||
int16_t h04;
|
||||
int16_t h05;
|
||||
int16_t h06;
|
||||
int16_t h07;
|
||||
int16_t h08;
|
||||
int16_t h09;
|
||||
int16_t h10;
|
||||
int16_t h11;
|
||||
int16_t h12;
|
||||
int16_t h13;
|
||||
int16_t h14;
|
||||
int16_t h15;
|
||||
int16_t h16;
|
||||
int16_t h17;
|
||||
int16_t h18;
|
||||
int16_t h19;
|
||||
int16_t h20;
|
||||
int16_t h21;
|
||||
int16_t h22;
|
||||
int16_t h23;
|
||||
time_t lastMeterReadTime;
|
||||
uint32_t activeImport;
|
||||
uint32_t activeExport;
|
||||
}; // 37 bytes
|
||||
|
||||
struct MonthDataPoints {
|
||||
uint8_t version;
|
||||
int16_t d01;
|
||||
int16_t d02;
|
||||
int16_t d03;
|
||||
int16_t d04;
|
||||
int16_t d05;
|
||||
int16_t d06;
|
||||
int16_t d07;
|
||||
int16_t d08;
|
||||
int16_t d09;
|
||||
int16_t d10;
|
||||
int16_t d11;
|
||||
int16_t d12;
|
||||
int16_t d13;
|
||||
int16_t d14;
|
||||
int16_t d15;
|
||||
int16_t d16;
|
||||
int16_t d17;
|
||||
int16_t d18;
|
||||
int16_t d19;
|
||||
int16_t d20;
|
||||
int16_t d21;
|
||||
int16_t d22;
|
||||
int16_t d23;
|
||||
int16_t d24;
|
||||
int16_t d25;
|
||||
int16_t d26;
|
||||
int16_t d27;
|
||||
int16_t d28;
|
||||
int16_t d29;
|
||||
int16_t d30;
|
||||
int16_t d31;
|
||||
time_t lastMeterReadTime;
|
||||
uint32_t activeImport;
|
||||
uint32_t activeExport;
|
||||
}; // 75 bytes
|
||||
|
||||
class AmsDataStorage {
|
||||
public:
|
||||
AmsDataStorage(RemoteDebug*);
|
||||
void setTimezone(Timezone*);
|
||||
bool update(AmsData*);
|
||||
DayDataPoints getDayDataPoints();
|
||||
MonthDataPoints getMonthDataPoints();
|
||||
bool load(AmsData*);
|
||||
bool save();
|
||||
|
||||
private:
|
||||
Timezone* tz;
|
||||
DayDataPoints day;
|
||||
MonthDataPoints month;
|
||||
RemoteDebug* debugger;
|
||||
|
||||
void printD(String fmt, ...);
|
||||
void printI(String fmt, ...);
|
||||
void printW(String fmt, ...);
|
||||
void printE(String fmt, ...);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -7,4 +7,7 @@
|
||||
#define FILE_MQTT_CERT "/mqtt-cert.pem"
|
||||
#define FILE_MQTT_KEY "/mqtt-key.pem"
|
||||
|
||||
#define FILE_DAYPLOT "/dayplot.bin"
|
||||
#define FILE_MONTHPLOT "/monthplot.bin"
|
||||
|
||||
#endif
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
|
||||
#define INVALID_BUTTON_PIN 0xFFFFFFFF
|
||||
|
||||
#define EPOCH_2021_01_01 1609459200
|
||||
|
||||
#define MAX_PEM_SIZE 4096
|
||||
|
||||
#include <SoftwareSerial.h>
|
||||
|
||||
@@ -18,14 +18,11 @@
|
||||
|
||||
#include "AmsToMqttBridge.h"
|
||||
#include "AmsStorage.h"
|
||||
#include "AmsDataStorage.h"
|
||||
#include <MQTT.h>
|
||||
#include <DNSServer.h>
|
||||
#include <lwip/apps/sntp.h>
|
||||
|
||||
#if defined(ESP8266)
|
||||
ADC_MODE(ADC_VCC);
|
||||
#endif
|
||||
|
||||
#include "HwTools.h"
|
||||
#include "entsoe/EntsoeApi.h"
|
||||
|
||||
@@ -74,6 +71,8 @@ String topic = "ams";
|
||||
AmsData meterState;
|
||||
bool ntpEnabled = false;
|
||||
|
||||
AmsDataStorage ds(&Debug);
|
||||
|
||||
void setup() {
|
||||
WiFiConfig wifi;
|
||||
Serial.begin(115200);
|
||||
@@ -143,7 +142,7 @@ void setup() {
|
||||
SerialConfig serialConfig;
|
||||
#elif defined(ESP32)
|
||||
uint32_t serialConfig;
|
||||
#endif;
|
||||
#endif
|
||||
switch(meterConfig.parity) {
|
||||
case 2:
|
||||
serialConfig = SERIAL_7N1;
|
||||
@@ -186,10 +185,10 @@ void setup() {
|
||||
}
|
||||
|
||||
float vccBootLimit = gpioConfig.vccBootLimit == 0 ? 0 : gpioConfig.vccBootLimit / 10.0;
|
||||
if(vccBootLimit > 0 && (gpioConfig.apPin == 0xFF || digitalRead(gpioConfig.apPin) == HIGH)) { // Skip if user is holding AP button while booting (HIGH = button is released)
|
||||
if(vccBootLimit > 2.5 && vccBootLimit < 3.3 && (gpioConfig.apPin == 0xFF || digitalRead(gpioConfig.apPin) == HIGH)) { // Skip if user is holding AP button while booting (HIGH = button is released)
|
||||
if (vcc < vccBootLimit) {
|
||||
if(Debug.isActive(RemoteDebug::INFO)) {
|
||||
debugI("Voltage is too low, sleeping");
|
||||
Debug.printf("(setup) Voltage is too low (%.2f < %.2f), sleeping\n", vcc, vccBootLimit);
|
||||
Serial.flush();
|
||||
}
|
||||
ESP.deepSleep(10000000); //Deep sleep to allow output cap to charge up
|
||||
@@ -214,7 +213,7 @@ void setup() {
|
||||
if(hasFs) {
|
||||
bool flashed = false;
|
||||
if(LittleFS.exists(FILE_FIRMWARE)) {
|
||||
if (digitalRead(gpioConfig.apPin) == HIGH) {
|
||||
if (gpioConfig.apPin == 0xFF || digitalRead(gpioConfig.apPin) == HIGH) {
|
||||
if(Debug.isActive(RemoteDebug::INFO)) debugI("Found firmware");
|
||||
#if defined(ESP8266)
|
||||
WiFi.setSleepMode(WIFI_LIGHT_SLEEP);
|
||||
@@ -286,7 +285,10 @@ void setup() {
|
||||
TimeChangeRule dst = {"DST", Last, Sun, Mar, 2, (ntp.offset + ntp.summerOffset) / 6};
|
||||
tz = new Timezone(dst, std);
|
||||
ws.setTimezone(tz);
|
||||
ds.setTimezone(tz);
|
||||
}
|
||||
|
||||
ds.load(&meterState);
|
||||
} else {
|
||||
if(Debug.isActive(RemoteDebug::INFO)) {
|
||||
debugI("No configuration, booting AP");
|
||||
@@ -294,7 +296,7 @@ void setup() {
|
||||
swapWifiMode();
|
||||
}
|
||||
|
||||
ws.setup(&config, &gpioConfig, &meterConfig, &meterState, &mqtt);
|
||||
ws.setup(&config, &gpioConfig, &meterConfig, &meterState, &ds, &mqtt);
|
||||
}
|
||||
|
||||
int buttonTimer = 0;
|
||||
@@ -472,7 +474,7 @@ void loop() {
|
||||
}
|
||||
|
||||
void setupHanPort(uint8_t pin, uint32_t baud, uint8_t parityOrdinal, bool invert) {
|
||||
debugI("Setting up HAN on pin %d with baud %d and parity %d", pin, baud, parityOrdinal);
|
||||
if(Debug.isActive(RemoteDebug::INFO)) Debug.printf("(setupHanPort) Setting up HAN on pin %d with baud %d and parity %d\n", pin, baud, parityOrdinal);
|
||||
|
||||
HardwareSerial *hwSerial = NULL;
|
||||
if(pin == 3) {
|
||||
@@ -630,6 +632,7 @@ void mqttMessageReceived(String &topic, String &payload)
|
||||
// Ideas could be to query for values or to initiate OTA firmware update
|
||||
}
|
||||
|
||||
int len = 0;
|
||||
uint8_t buf[BUF_SIZE];
|
||||
HDLCConfig* hc = NULL;
|
||||
int currentMeterType = -1;
|
||||
@@ -648,12 +651,22 @@ void readHanPort() {
|
||||
hanSerial->readBytes(buf, BUF_SIZE);
|
||||
return;
|
||||
}
|
||||
CosemDateTime timestamp;
|
||||
CosemDateTime timestamp = {0};
|
||||
AmsData data;
|
||||
if(currentMeterType == 1) {
|
||||
size_t len = hanSerial->readBytes(buf, BUF_SIZE); // TODO: read one byte at the time. This blocks up the GUI
|
||||
while(hanSerial->available()) {
|
||||
buf[len++] = hanSerial->read();
|
||||
}
|
||||
if(len > 0) {
|
||||
int pos = HDLC_validate((uint8_t *) buf, len, hc, ×tamp);
|
||||
if(pos == HDLC_FRAME_INCOMPLETE) {
|
||||
if(len >= BUF_SIZE) {
|
||||
hanSerial->readBytes(buf, BUF_SIZE);
|
||||
len = 0;
|
||||
debugI("Buffer overflow, resetting");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if(pos == HDLC_ENCRYPTION_CONFIG_MISSING) {
|
||||
hc = new HDLCConfig();
|
||||
memcpy(hc->encryption_key, meterConfig.encryptionKey, 16);
|
||||
@@ -673,6 +686,7 @@ void readHanPort() {
|
||||
debugPrint(hc->authentication_tag, 0, 8);
|
||||
}
|
||||
}
|
||||
len = 0;
|
||||
if(pos >= 0) {
|
||||
debugI("Valid HDLC, start at %d", pos);
|
||||
data = IEC6205675(((char *) (buf)) + pos, meterState.getMeterType(), timestamp);
|
||||
@@ -726,6 +740,14 @@ void readHanPort() {
|
||||
mqtt.loop();
|
||||
delay(10);
|
||||
}
|
||||
|
||||
if(ds.update(&data)) {
|
||||
debugI("Saving day plot");
|
||||
ds.save();
|
||||
} else if(data.getListType() == 3) {
|
||||
debugE("Unable to update day plot");
|
||||
}
|
||||
|
||||
meterState.apply(data);
|
||||
}
|
||||
}
|
||||
|
||||
147
src/HwTools.cpp
147
src/HwTools.cpp
@@ -1,4 +1,7 @@
|
||||
#include "HwTools.h"
|
||||
#if defined(ESP8266)
|
||||
ADC_MODE(ADC_VCC);
|
||||
#endif
|
||||
|
||||
void HwTools::setup(GpioConfig* config, AmsConfiguration* amsConf) {
|
||||
this->config = config;
|
||||
@@ -14,18 +17,35 @@ void HwTools::setup(GpioConfig* config, AmsConfiguration* amsConf) {
|
||||
config->tempSensorPin = 0xFF;
|
||||
}
|
||||
|
||||
if(config->vccPin > 0 && config->vccPin < 40) {
|
||||
getAdcChannel(config->vccPin, voltAdc);
|
||||
if(voltAdc.unit != 0xFF) {
|
||||
#if defined(ESP32)
|
||||
if(voltAdc.unit == ADC_UNIT_1) {
|
||||
voltAdcChar = (esp_adc_cal_characteristics_t*) calloc(1, sizeof(esp_adc_cal_characteristics_t));
|
||||
esp_adc_cal_value_t adcVal = esp_adc_cal_characterize((adc_unit_t) voltAdc.unit, ADC_ATTEN_DB_6, ADC_WIDTH_BIT_12, 1100, voltAdcChar);
|
||||
adc1_config_channel_atten((adc1_channel_t) voltAdc.channel, ADC_ATTEN_DB_6);
|
||||
} else if(voltAdc.unit == ADC_UNIT_2) {
|
||||
voltAdcChar = (esp_adc_cal_characteristics_t*) calloc(1, sizeof(esp_adc_cal_characteristics_t));
|
||||
esp_adc_cal_value_t adcVal = esp_adc_cal_characterize((adc_unit_t) voltAdc.unit, ADC_ATTEN_DB_6, ADC_WIDTH_BIT_12, 1100, voltAdcChar);
|
||||
adc2_config_channel_atten((adc2_channel_t) voltAdc.channel, ADC_ATTEN_DB_6);
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
pinMode(config->vccPin, INPUT);
|
||||
}
|
||||
} else {
|
||||
voltAdc.unit = 0xFF;
|
||||
voltAdc.channel = 0xFF;
|
||||
config->vccPin = 0xFF;
|
||||
}
|
||||
|
||||
if(config->tempAnalogSensorPin > 0 && config->tempAnalogSensorPin < 40) {
|
||||
pinMode(config->tempAnalogSensorPin, INPUT);
|
||||
} else {
|
||||
config->tempAnalogSensorPin = 0xFF;
|
||||
}
|
||||
|
||||
if(config->vccPin > 0 && config->vccPin < 40) {
|
||||
pinMode(config->vccPin, INPUT);
|
||||
} else {
|
||||
config->vccPin = 0xFF;
|
||||
}
|
||||
|
||||
if(config->ledPin > 0 && config->ledPin < 40) {
|
||||
pinMode(config->ledPin, OUTPUT);
|
||||
ledOff(LED_INTERNAL);
|
||||
@@ -55,19 +75,128 @@ void HwTools::setup(GpioConfig* config, AmsConfiguration* amsConf) {
|
||||
}
|
||||
}
|
||||
|
||||
void HwTools::getAdcChannel(uint8_t pin, AdcConfig& config) {
|
||||
config.unit = 0xFF;
|
||||
config.channel = 0xFF;
|
||||
#if defined(ESP32)
|
||||
switch(pin) {
|
||||
case ADC1_CHANNEL_0_GPIO_NUM:
|
||||
config.unit = ADC_UNIT_1;
|
||||
config.channel = ADC1_CHANNEL_0;
|
||||
break;
|
||||
case ADC1_CHANNEL_1_GPIO_NUM:
|
||||
config.unit = ADC_UNIT_1;
|
||||
config.channel = ADC1_CHANNEL_1;
|
||||
break;
|
||||
case ADC1_CHANNEL_2_GPIO_NUM:
|
||||
config.unit = ADC_UNIT_1;
|
||||
config.channel = ADC1_CHANNEL_2;
|
||||
break;
|
||||
case ADC1_CHANNEL_3_GPIO_NUM:
|
||||
config.unit = ADC_UNIT_1;
|
||||
config.channel = ADC1_CHANNEL_3;
|
||||
break;
|
||||
case ADC1_CHANNEL_4_GPIO_NUM:
|
||||
config.unit = ADC_UNIT_1;
|
||||
config.channel = ADC1_CHANNEL_4;
|
||||
break;
|
||||
case ADC1_CHANNEL_5_GPIO_NUM:
|
||||
config.unit = ADC_UNIT_1;
|
||||
config.channel = ADC1_CHANNEL_5;
|
||||
break;
|
||||
case ADC1_CHANNEL_6_GPIO_NUM:
|
||||
config.unit = ADC_UNIT_1;
|
||||
config.channel = ADC1_CHANNEL_6;
|
||||
break;
|
||||
case ADC1_CHANNEL_7_GPIO_NUM:
|
||||
config.unit = ADC_UNIT_1;
|
||||
config.channel = ADC1_CHANNEL_7;
|
||||
break;
|
||||
case ADC2_CHANNEL_0_GPIO_NUM:
|
||||
config.unit = ADC_UNIT_2;
|
||||
config.channel = ADC2_CHANNEL_0;
|
||||
break;
|
||||
case ADC2_CHANNEL_1_GPIO_NUM:
|
||||
config.unit = ADC_UNIT_2;
|
||||
config.channel = ADC2_CHANNEL_1;
|
||||
break;
|
||||
case ADC2_CHANNEL_2_GPIO_NUM:
|
||||
config.unit = ADC_UNIT_2;
|
||||
config.channel = ADC2_CHANNEL_2;
|
||||
break;
|
||||
case ADC2_CHANNEL_3_GPIO_NUM:
|
||||
config.unit = ADC_UNIT_2;
|
||||
config.channel = ADC2_CHANNEL_3;
|
||||
break;
|
||||
case ADC2_CHANNEL_4_GPIO_NUM:
|
||||
config.unit = ADC_UNIT_2;
|
||||
config.channel = ADC2_CHANNEL_4;
|
||||
break;
|
||||
case ADC2_CHANNEL_5_GPIO_NUM:
|
||||
config.unit = ADC_UNIT_2;
|
||||
config.channel = ADC2_CHANNEL_5;
|
||||
break;
|
||||
case ADC2_CHANNEL_6_GPIO_NUM:
|
||||
config.unit = ADC_UNIT_2;
|
||||
config.channel = ADC2_CHANNEL_6;
|
||||
break;
|
||||
case ADC2_CHANNEL_7_GPIO_NUM:
|
||||
config.unit = ADC_UNIT_2;
|
||||
config.channel = ADC2_CHANNEL_7;
|
||||
break;
|
||||
case ADC2_CHANNEL_8_GPIO_NUM:
|
||||
config.unit = ADC_UNIT_2;
|
||||
config.channel = ADC2_CHANNEL_8;
|
||||
break;
|
||||
case ADC2_CHANNEL_9_GPIO_NUM:
|
||||
config.unit = ADC_UNIT_2;
|
||||
config.channel = ADC2_CHANNEL_9;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
double HwTools::getVcc() {
|
||||
double volts = 0.0;
|
||||
if(config->vccPin != 0xFF) {
|
||||
#if defined(ESP8266)
|
||||
volts = (analogRead(config->vccPin) / 1024.0) * 3.3;
|
||||
uint32_t x = 0;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
x += analogRead(config->vccPin);
|
||||
}
|
||||
volts = x / 10240;
|
||||
#elif defined(ESP32)
|
||||
volts = (analogRead(config->vccPin) / 4095.0) * 3.3;
|
||||
if(voltAdc.unit != 0xFF) {
|
||||
uint32_t x = 0;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
if(voltAdc.unit == ADC_UNIT_1) {
|
||||
x += adc1_get_raw((adc1_channel_t) voltAdc.channel);
|
||||
} else if(voltAdc.unit == ADC_UNIT_2) {
|
||||
int v = 0;
|
||||
adc2_get_raw((adc2_channel_t) voltAdc.channel, ADC_WIDTH_BIT_12, &v);
|
||||
x += v;
|
||||
}
|
||||
}
|
||||
x = x / 10;
|
||||
uint32_t voltage = esp_adc_cal_raw_to_voltage(x, voltAdcChar);
|
||||
volts = voltage / 1000.0;
|
||||
} else {
|
||||
uint32_t x = 0;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
x += analogRead(config->vccPin);
|
||||
}
|
||||
volts = x / 40950;
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
#if defined(ESP8266)
|
||||
volts = ((double) ESP.getVcc()) / 1024.0;
|
||||
volts = ESP.getVcc() / 1024.0;
|
||||
#endif
|
||||
}
|
||||
if(config->vccResistorGnd > 0 && config->vccResistorVcc > 0) {
|
||||
volts *= ((double) (config->vccResistorGnd + config->vccResistorVcc) / config->vccResistorGnd);
|
||||
}
|
||||
|
||||
|
||||
float vccOffset = config->vccOffset / 100.0;
|
||||
float vccMultiplier = config->vccMultiplier / 1000.0;
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
#include <ESP8266WiFi.h>
|
||||
#elif defined(ESP32)
|
||||
#include <WiFi.h>
|
||||
#include <driver/adc.h>
|
||||
#include <esp_adc_cal.h>
|
||||
#include <soc/adc_channel.h>
|
||||
#endif
|
||||
|
||||
#include <DallasTemperature.h>
|
||||
@@ -26,6 +29,11 @@ struct TempSensorData {
|
||||
bool changed;
|
||||
};
|
||||
|
||||
struct AdcConfig {
|
||||
uint8_t unit;
|
||||
uint8_t channel;
|
||||
};
|
||||
|
||||
class HwTools {
|
||||
public:
|
||||
void setup(GpioConfig*, AmsConfiguration*);
|
||||
@@ -43,6 +51,10 @@ public:
|
||||
|
||||
HwTools() {};
|
||||
private:
|
||||
AdcConfig voltAdc, tempAdc;
|
||||
#if defined(ESP32)
|
||||
esp_adc_cal_characteristics_t* voltAdcChar, tempAdcChar;
|
||||
#endif
|
||||
GpioConfig* config;
|
||||
AmsConfiguration* amsConf;
|
||||
bool tempSensorInit;
|
||||
@@ -53,6 +65,7 @@ private:
|
||||
|
||||
bool writeLedPin(uint8_t color, uint8_t state);
|
||||
bool isSensorAddressEqual(uint8_t a[8], uint8_t b[8]);
|
||||
void getAdcChannel(uint8_t pin, AdcConfig&);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "IEC6205675.h"
|
||||
#include "lwip/def.h"
|
||||
#include "Timezone.h"
|
||||
|
||||
IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, CosemDateTime packageTimestamp) {
|
||||
uint32_t u32;
|
||||
@@ -261,9 +262,21 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, CosemDateTime packag
|
||||
}
|
||||
}
|
||||
|
||||
time_t ts = getTimestamp(AMS_OBIS_METER_TIMESTAMP, sizeof(AMS_OBIS_METER_TIMESTAMP), ((char *) (d)));
|
||||
if(ts > 0) {
|
||||
meterTimestamp = ts;
|
||||
CosemData* meterTs = findObis(AMS_OBIS_METER_TIMESTAMP, sizeof(AMS_OBIS_METER_TIMESTAMP), ((char *) (d)));
|
||||
if(meterTs != NULL) {
|
||||
TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120};
|
||||
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60};
|
||||
Timezone tz(CEST, CET);
|
||||
|
||||
AmsOctetTimestamp* amst = (AmsOctetTimestamp*) meterTs;
|
||||
time_t ts = getTimestamp(amst->dt);
|
||||
if(meterType == AmsTypeKamstrup || meterType == AmsTypeAidon) {
|
||||
this->packageTimestamp = tz.toUTC(ts);
|
||||
this->meterTimestamp = tz.toUTC(ts);
|
||||
Serial.printf("\nKamstrup/Aidon time: %d\n", meterTimestamp);
|
||||
} else {
|
||||
meterTimestamp = ts;
|
||||
}
|
||||
}
|
||||
|
||||
u32 = getUnsignedNumber(AMS_OBIS_POWER_FACTOR, sizeof(AMS_OBIS_POWER_FACTOR), ((char *) (d)));
|
||||
@@ -425,7 +438,6 @@ time_t IEC6205675::getTimestamp(uint8_t* obis, int matchlength, const char* ptr)
|
||||
case CosemTypeOctetString: {
|
||||
if(item->oct.length == 0x0C) {
|
||||
AmsOctetTimestamp* ts = (AmsOctetTimestamp*) item;
|
||||
//Serial.printf("\nYear: %d, Month: %d, Day: %d, Hour: %d, Minutes %d, Second: %d, Deviation: %d\n", ntohs(ts->dt.year), ts->dt.month, ts->dt.dayOfMonth, ts->dt.hour, ts->dt.minute, ts->dt.second, ntohs(ts->dt.deviation));
|
||||
return getTimestamp(ts->dt);
|
||||
}
|
||||
}
|
||||
@@ -436,13 +448,17 @@ time_t IEC6205675::getTimestamp(uint8_t* obis, int matchlength, const char* ptr)
|
||||
|
||||
time_t IEC6205675::getTimestamp(CosemDateTime timestamp) {
|
||||
tmElements_t tm;
|
||||
tm.Year = ntohs(timestamp.year) - 1970;
|
||||
uint16_t year = ntohs(timestamp.year);
|
||||
if(year < 1970) return 0;
|
||||
tm.Year = year - 1970;
|
||||
tm.Month = timestamp.month;
|
||||
tm.Day = timestamp.dayOfMonth;
|
||||
tm.Hour = timestamp.hour;
|
||||
tm.Minute = timestamp.minute;
|
||||
tm.Second = timestamp.second;
|
||||
|
||||
Serial.printf("\nY: %d, M: %d, D: %d, h: %d, m: %d, s: %d, deviation: 0x%2X, status: 0x%1X\n", tm.Year, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second, timestamp.deviation, timestamp.status);
|
||||
|
||||
time_t time = makeTime(tm);
|
||||
int16_t deviation = ntohs(timestamp.deviation);
|
||||
if(deviation >= -720 && deviation <= 720) {
|
||||
|
||||
@@ -15,13 +15,15 @@ void mbus_hexdump(const uint8_t* buf, int len) {
|
||||
printf("]\n");
|
||||
}
|
||||
|
||||
int HDLC_validate(const uint8_t* d, int len, HDLCConfig* config, CosemDateTime* timestamp) {
|
||||
int HDLC_validate(const uint8_t* d, int length, HDLCConfig* config, CosemDateTime* timestamp) {
|
||||
//mbus_hexdump(d, len);
|
||||
|
||||
HDLCHeader* h = (HDLCHeader*) d;
|
||||
|
||||
// Length field (11 lsb of format)
|
||||
len = (ntohs(h->format) & 0x7FF) + 2;
|
||||
int len = (ntohs(h->format) & 0x7FF) + 2;
|
||||
if(len > length)
|
||||
return -4;
|
||||
|
||||
HDLCFooter* f = (HDLCFooter*) (d + len - sizeof *f);
|
||||
|
||||
@@ -81,11 +83,12 @@ int HDLC_validate(const uint8_t* d, int len, HDLCConfig* config, CosemDateTime*
|
||||
}
|
||||
ptr += 2 + dateTime->base.length;
|
||||
} else if(dateTime->base.type == CosemTypeNull) {
|
||||
timestamp = 0;
|
||||
ptr++;
|
||||
} else if(dateTime->base.type == CosemTypeDateTime) {
|
||||
memcpy(timestamp, ptr, dateTime->base.length);
|
||||
} else if(dateTime->base.type == 0x0C) { // Kamstrup bug...
|
||||
memcpy(timestamp, ptr, dateTime->base.length);
|
||||
memcpy(timestamp, ptr, 0x0C);
|
||||
ptr += 13;
|
||||
} else {
|
||||
return -99;
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#define HDLC_FLAG 0x7E
|
||||
#define HDLC_BOUNDRY_FLAG_MISSING -1
|
||||
#define HDLC_FRAME_INCOMPLETE -4
|
||||
#define HDLC_ENCRYPTION_CONFIG_MISSING -90
|
||||
|
||||
struct HDLCConfig {
|
||||
|
||||
@@ -179,9 +179,9 @@ bool EntsoeApi::retrieve(const char* url, Stream* doc) {
|
||||
client.setBufferSizes(bufSize, bufSize);
|
||||
}
|
||||
*/
|
||||
|
||||
client.setInsecure();
|
||||
#endif
|
||||
|
||||
client.setInsecure();
|
||||
|
||||
HTTPClient https;
|
||||
https.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
||||
|
||||
@@ -30,9 +30,9 @@
|
||||
#include "root/restart_html.h"
|
||||
#include "root/restartwait_html.h"
|
||||
#include "root/boot_css.h"
|
||||
#include "root/gaugemeter_js.h"
|
||||
#include "root/github_svg.h"
|
||||
#include "root/upload_html.h"
|
||||
#include "root/firmware_html.h"
|
||||
#include "root/delete_html.h"
|
||||
#include "root/reset_html.h"
|
||||
#include "root/temperature_html.h"
|
||||
@@ -41,6 +41,8 @@
|
||||
#include "root/data_json.h"
|
||||
#include "root/tempsensor_json.h"
|
||||
#include "root/lowmem_html.h"
|
||||
#include "root/dayplot_json.h"
|
||||
#include "root/monthplot_json.h"
|
||||
|
||||
#include "base64.h"
|
||||
|
||||
@@ -49,11 +51,12 @@ AmsWebServer::AmsWebServer(RemoteDebug* Debug, HwTools* hw) {
|
||||
this->hw = hw;
|
||||
}
|
||||
|
||||
void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, MeterConfig* meterConfig, AmsData* meterState, MQTTClient* mqtt) {
|
||||
void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, MeterConfig* meterConfig, AmsData* meterState, AmsDataStorage* ds, MQTTClient* mqtt) {
|
||||
this->config = config;
|
||||
this->gpioConfig = gpioConfig;
|
||||
this->meterConfig = meterConfig;
|
||||
this->meterState = meterState;
|
||||
this->ds = ds;
|
||||
this->mqtt = mqtt;
|
||||
|
||||
char jsuri[32];
|
||||
@@ -73,9 +76,10 @@ void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, Meter
|
||||
server.on("/domoticz",HTTP_GET, std::bind(&AmsWebServer::configDomoticzHtml, this));
|
||||
server.on("/entsoe",HTTP_GET, std::bind(&AmsWebServer::configEntsoeHtml, this));
|
||||
server.on("/boot.css", HTTP_GET, std::bind(&AmsWebServer::bootCss, this));
|
||||
server.on("/gaugemeter.js", HTTP_GET, std::bind(&AmsWebServer::gaugemeterJs, this));
|
||||
server.on("/github.svg", HTTP_GET, std::bind(&AmsWebServer::githubSvg, this));
|
||||
server.on("/data.json", HTTP_GET, std::bind(&AmsWebServer::dataJson, this));
|
||||
server.on("/dayplot.json", HTTP_GET, std::bind(&AmsWebServer::dayplotJson, this));
|
||||
server.on("/monthplot.json", HTTP_GET, std::bind(&AmsWebServer::monthplotJson, this));
|
||||
|
||||
server.on("/save", HTTP_POST, std::bind(&AmsWebServer::handleSave, this));
|
||||
|
||||
@@ -660,13 +664,6 @@ void AmsWebServer::bootCss() {
|
||||
server.send_P(200, "text/css", BOOT_CSS);
|
||||
}
|
||||
|
||||
void AmsWebServer::gaugemeterJs() {
|
||||
printD("Serving /gaugemeter.js over http...");
|
||||
|
||||
server.sendHeader("Cache-Control", "public, max-age=3600");
|
||||
server.send_P(200, "application/javascript", GAUGEMETER_JS);
|
||||
}
|
||||
|
||||
void AmsWebServer::githubSvg() {
|
||||
printD("Serving /github.svg over http...");
|
||||
|
||||
@@ -782,6 +779,95 @@ void AmsWebServer::dataJson() {
|
||||
server.send(200, "application/json", json);
|
||||
}
|
||||
|
||||
void AmsWebServer::dayplotJson() {
|
||||
printD("Serving /dayplot.json over http...");
|
||||
|
||||
DayDataPoints d = ds->getDayDataPoints();
|
||||
|
||||
char json[384];
|
||||
snprintf_P(json, sizeof(json), DAYPLOT_JSON,
|
||||
d.h00 / 100.0,
|
||||
d.h01 / 100.0,
|
||||
d.h02 / 100.0,
|
||||
d.h03 / 100.0,
|
||||
d.h04 / 100.0,
|
||||
d.h05 / 100.0,
|
||||
d.h06 / 100.0,
|
||||
d.h07 / 100.0,
|
||||
d.h08 / 100.0,
|
||||
d.h09 / 100.0,
|
||||
d.h10 / 100.0,
|
||||
d.h11 / 100.0,
|
||||
d.h12 / 100.0,
|
||||
d.h13 / 100.0,
|
||||
d.h14 / 100.0,
|
||||
d.h15 / 100.0,
|
||||
d.h16 / 100.0,
|
||||
d.h17 / 100.0,
|
||||
d.h18 / 100.0,
|
||||
d.h19 / 100.0,
|
||||
d.h20 / 100.0,
|
||||
d.h21 / 100.0,
|
||||
d.h22 / 100.0,
|
||||
d.h23 / 100.0
|
||||
);
|
||||
|
||||
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
server.sendHeader("Pragma", "no-cache");
|
||||
server.sendHeader("Expires", "-1");
|
||||
|
||||
server.setContentLength(strlen(json));
|
||||
server.send(200, "application/json", json);
|
||||
}
|
||||
|
||||
void AmsWebServer::monthplotJson() {
|
||||
printD("Serving /monthplot.json over http...");
|
||||
|
||||
MonthDataPoints m = ds->getMonthDataPoints();
|
||||
|
||||
char json[512];
|
||||
snprintf_P(json, sizeof(json), MONTHPLOT_JSON,
|
||||
m.d01 / 100.0,
|
||||
m.d02 / 100.0,
|
||||
m.d03 / 100.0,
|
||||
m.d04 / 100.0,
|
||||
m.d05 / 100.0,
|
||||
m.d06 / 100.0,
|
||||
m.d07 / 100.0,
|
||||
m.d08 / 100.0,
|
||||
m.d09 / 100.0,
|
||||
m.d10 / 100.0,
|
||||
m.d11 / 100.0,
|
||||
m.d12 / 100.0,
|
||||
m.d13 / 100.0,
|
||||
m.d14 / 100.0,
|
||||
m.d15 / 100.0,
|
||||
m.d16 / 100.0,
|
||||
m.d17 / 100.0,
|
||||
m.d18 / 100.0,
|
||||
m.d19 / 100.0,
|
||||
m.d20 / 100.0,
|
||||
m.d21 / 100.0,
|
||||
m.d22 / 100.0,
|
||||
m.d23 / 100.0,
|
||||
m.d24 / 100.0,
|
||||
m.d25 / 100.0,
|
||||
m.d26 / 100.0,
|
||||
m.d27 / 100.0,
|
||||
m.d28 / 100.0,
|
||||
m.d29 / 100.0,
|
||||
m.d30 / 100.0,
|
||||
m.d31 / 100.0
|
||||
);
|
||||
|
||||
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
server.sendHeader("Pragma", "no-cache");
|
||||
server.sendHeader("Expires", "-1");
|
||||
|
||||
server.setContentLength(strlen(json));
|
||||
server.send(200, "application/json", json);
|
||||
}
|
||||
|
||||
void AmsWebServer::handleSetup() {
|
||||
printD("Handling setup method from http");
|
||||
|
||||
@@ -796,9 +882,6 @@ void AmsWebServer::handleSetup() {
|
||||
|
||||
config->clear();
|
||||
|
||||
config->clearGpio(*gpioConfig);
|
||||
config->clearMeter(*meterConfig);
|
||||
|
||||
switch(sys.boardType) {
|
||||
case 0: // roarfred
|
||||
gpioConfig->hanPin = 3;
|
||||
@@ -922,10 +1005,6 @@ void AmsWebServer::handleSetup() {
|
||||
printD("Unable to set web config");
|
||||
success = false;
|
||||
}
|
||||
if(!config->setMeterConfig(*meterConfig)) {
|
||||
printD("Unable to set meter config");
|
||||
success = false;
|
||||
}
|
||||
if(!config->setGpioConfig(*gpioConfig)) {
|
||||
printD("Unable to set GPIO config");
|
||||
success = false;
|
||||
@@ -1066,6 +1145,8 @@ void AmsWebServer::handleSave() {
|
||||
gpioConfig->vccOffset = server.hasArg("vccOffset") && !server.arg("vccOffset").isEmpty() ? server.arg("vccOffset").toFloat() * 100 : 0;
|
||||
gpioConfig->vccMultiplier = server.hasArg("vccMultiplier") && !server.arg("vccMultiplier").isEmpty() ? server.arg("vccMultiplier").toFloat() * 1000 : 1000;
|
||||
gpioConfig->vccBootLimit = server.hasArg("vccBootLimit") && !server.arg("vccBootLimit").isEmpty() ? server.arg("vccBootLimit").toFloat() * 10 : 0;
|
||||
gpioConfig->vccResistorGnd = server.hasArg("vccResistorGnd") && !server.arg("vccResistorGnd").isEmpty() ? server.arg("vccResistorGnd").toInt() : 0;
|
||||
gpioConfig->vccResistorVcc = server.hasArg("vccResistorVcc") && !server.arg("vccResistorVcc").isEmpty() ? server.arg("vccResistorVcc").toInt() : 0;
|
||||
config->setGpioConfig(*gpioConfig);
|
||||
}
|
||||
|
||||
@@ -1201,6 +1282,9 @@ void AmsWebServer::configGpioHtml() {
|
||||
html.replace("${config.vccMultiplier}", gpioConfig->vccMultiplier > 0 ? String(gpioConfig->vccMultiplier / 1000.0, 2) : "");
|
||||
html.replace("${config.vccBootLimit}", gpioConfig->vccBootLimit > 0 ? String(gpioConfig->vccBootLimit / 10.0, 1) : "");
|
||||
|
||||
html.replace("${config.vccResistorGnd}", gpioConfig->vccResistorGnd > 0 ? String(gpioConfig->vccResistorGnd) : "");
|
||||
html.replace("${config.vccResistorVcc}", gpioConfig->vccResistorVcc > 0 ? String(gpioConfig->vccResistorVcc) : "");
|
||||
|
||||
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
server.sendHeader("Pragma", "no-cache");
|
||||
|
||||
@@ -1372,7 +1456,21 @@ void AmsWebServer::firmwareHtml() {
|
||||
if(!checkSecurity(1))
|
||||
return;
|
||||
|
||||
uploadHtml("Firmware", "/firmware", "system");
|
||||
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
server.sendHeader("Pragma", "no-cache");
|
||||
|
||||
String html = String((const __FlashStringHelper*) FIRMWARE_HTML);
|
||||
|
||||
#if defined(ESP8266)
|
||||
html.replace("{chipset}", "ESP8266");
|
||||
#elif defined(ESP32)
|
||||
html.replace("{chipset}", "ESP32");
|
||||
#endif
|
||||
|
||||
server.setContentLength(html.length() + HEAD_HTML_LEN + FOOT_HTML_LEN);
|
||||
server.send_P(200, "text/html", HEAD_HTML);
|
||||
server.sendContent(html);
|
||||
server.sendContent_P(FOOT_HTML);
|
||||
}
|
||||
|
||||
void AmsWebServer::firmwareUpload() {
|
||||
@@ -1519,7 +1617,7 @@ void AmsWebServer::restartWaitHtml() {
|
||||
|
||||
yield();
|
||||
if(performRestart) {
|
||||
LittleFS.end();
|
||||
ds->save();
|
||||
printI("Rebooting");
|
||||
delay(1000);
|
||||
#if defined(ESP8266)
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "AmsConfiguration.h"
|
||||
#include "HwTools.h"
|
||||
#include "AmsData.h"
|
||||
#include "AmsDataStorage.h"
|
||||
#include "Uptime.h"
|
||||
#include "RemoteDebug.h"
|
||||
#include "entsoe/EntsoeApi.h"
|
||||
@@ -29,7 +30,7 @@
|
||||
class AmsWebServer {
|
||||
public:
|
||||
AmsWebServer(RemoteDebug* Debug, HwTools* hw);
|
||||
void setup(AmsConfiguration*, GpioConfig*, MeterConfig*, AmsData*, MQTTClient*);
|
||||
void setup(AmsConfiguration*, GpioConfig*, MeterConfig*, AmsData*, AmsDataStorage* ds, MQTTClient*);
|
||||
void loop();
|
||||
void setTimezone(Timezone* tz);
|
||||
void setMqttEnabled(bool);
|
||||
@@ -47,6 +48,7 @@ private:
|
||||
MeterConfig* meterConfig;
|
||||
WebConfig webConfig;
|
||||
AmsData* meterState;
|
||||
AmsDataStorage* ds;
|
||||
MQTTClient* mqtt;
|
||||
bool uploading = false;
|
||||
File file;
|
||||
@@ -76,9 +78,10 @@ private:
|
||||
void configGpioHtml();
|
||||
void configDebugHtml();
|
||||
void bootCss();
|
||||
void gaugemeterJs();
|
||||
void githubSvg();
|
||||
void dataJson();
|
||||
void dayplotJson();
|
||||
void monthplotJson();
|
||||
|
||||
void handleSetup();
|
||||
void handleSave();
|
||||
|
||||
@@ -1,46 +1,132 @@
|
||||
var nextVersion;
|
||||
var im, em, vm, am;
|
||||
$(function() {
|
||||
im = $("#im");
|
||||
if(im && im.gaugeMeter) {
|
||||
im.gaugeMeter({
|
||||
percent: 0,
|
||||
text: "-",
|
||||
append: "W"
|
||||
});
|
||||
}
|
||||
|
||||
em = $("#em");
|
||||
if(em && em.gaugeMeter) {
|
||||
em.gaugeMeter({
|
||||
percent: 0,
|
||||
text: "-",
|
||||
append: "W"
|
||||
});
|
||||
}
|
||||
|
||||
vm = $("#vm");
|
||||
if(vm && vm.gaugeMeter) {
|
||||
vm.gaugeMeter({
|
||||
percent: 0,
|
||||
text: "-",
|
||||
append: "V"
|
||||
});
|
||||
}
|
||||
|
||||
am = $("#am");
|
||||
if(am && am.gaugeMeter) {
|
||||
am.gaugeMeter({
|
||||
percent: 0,
|
||||
text: "-",
|
||||
append: "A"
|
||||
});
|
||||
}
|
||||
var im, em;
|
||||
|
||||
var meters = $('.SimpleMeter');
|
||||
// Day plot
|
||||
var ep;
|
||||
var ea;
|
||||
var eo = {
|
||||
title: 'Last 24 hours',
|
||||
curveType: 'function',
|
||||
legend: { position: 'none' },
|
||||
vAxis: {
|
||||
viewWindowMode: 'maximized'
|
||||
}
|
||||
};
|
||||
|
||||
// Month plot
|
||||
var mp;
|
||||
var ma;
|
||||
var mo = {
|
||||
title: 'Last month',
|
||||
curveType: 'function',
|
||||
legend: { position: 'none' },
|
||||
vAxis: {
|
||||
viewWindowMode: 'maximized'
|
||||
}
|
||||
};
|
||||
|
||||
// Voltage plot
|
||||
var vp;
|
||||
var va;
|
||||
var vo = {
|
||||
title: 'Phase voltage',
|
||||
titleTextStyle: {
|
||||
fontSize: 14
|
||||
},
|
||||
bar: { groupWidth: '90%' },
|
||||
vAxis: {
|
||||
minValue: 200,
|
||||
maxValue: 260,
|
||||
ticks: [
|
||||
{ v: 207, f: '-10%'},
|
||||
{ v: 230, f: '230V'},
|
||||
{ v: 253, f: '+10%'}
|
||||
]
|
||||
},
|
||||
legend: { position: 'none' },
|
||||
tooltip: { trigger: 'none'},
|
||||
enableInteractivity: false,
|
||||
};
|
||||
|
||||
// Amperage plot
|
||||
var ap;
|
||||
var aa;
|
||||
var ao = {
|
||||
title: 'Phase current',
|
||||
titleTextStyle: {
|
||||
fontSize: 14
|
||||
},
|
||||
bar: { groupWidth: '90%' },
|
||||
vAxis: {
|
||||
minValue: 0,
|
||||
maxValue: 100,
|
||||
ticks: [
|
||||
{ v: 25, f: '25%'},
|
||||
{ v: 50, f: '50%'},
|
||||
{ v: 75, f: '75%'},
|
||||
{ v: 100, f: '100%'}
|
||||
]
|
||||
},
|
||||
legend: { position: 'none' },
|
||||
tooltip: { trigger: 'none'},
|
||||
enableInteractivity: false,
|
||||
};
|
||||
|
||||
// Import plot
|
||||
var ip;
|
||||
var ia;
|
||||
var io = {
|
||||
legend: 'none',
|
||||
pieHole: 0.6,
|
||||
pieSliceText: 'none',
|
||||
pieStartAngle: 216,
|
||||
slices: {
|
||||
0: { color: 'green' },
|
||||
1: { color: '#eee' },
|
||||
2: { color: 'transparent' }
|
||||
},
|
||||
legend: { position: 'none' },
|
||||
tooltip: { trigger: 'none'},
|
||||
enableInteractivity: false,
|
||||
chartArea: {
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
}
|
||||
};
|
||||
|
||||
// Export plot
|
||||
var xp;
|
||||
var xa;
|
||||
var xo = {
|
||||
legend: 'none',
|
||||
pieHole: 0.6,
|
||||
pieSliceText: 'none',
|
||||
pieStartAngle: 216,
|
||||
slices: {
|
||||
0: { color: 'green' },
|
||||
1: { color: '#eee' },
|
||||
2: { color: 'transparent' }
|
||||
},
|
||||
legend: { position: 'none' },
|
||||
tooltip: { trigger: 'none'},
|
||||
enableInteractivity: false,
|
||||
chartArea: {
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
}
|
||||
};
|
||||
|
||||
$(function() {
|
||||
var meters = $('.plot1');
|
||||
|
||||
if(meters.length > 0) {
|
||||
fetch();
|
||||
// Chart
|
||||
google.charts.load('current', {'packages':['corechart']});
|
||||
google.charts.setOnLoadCallback(setupChart);
|
||||
}
|
||||
|
||||
// For mqtt
|
||||
@@ -97,6 +183,9 @@ $(function() {
|
||||
var fileName = $(this).val();
|
||||
$(this).next('.custom-file-label').html(fileName);
|
||||
})
|
||||
$('.upload-form').on('submit', function(i, form) {
|
||||
$('#loading-indicator').show();
|
||||
});
|
||||
|
||||
// For NTP
|
||||
$('#n').on('change', function() {
|
||||
@@ -135,8 +224,9 @@ $(function() {
|
||||
}
|
||||
|
||||
// Check for software upgrade
|
||||
var swv = $('#swVersion')
|
||||
if(meters.length > 0 && swv.length == 1 && swv.text() != "SNAPSHOT") {
|
||||
var swv = $('#swVersion');
|
||||
var fwl = $('#fwLink');
|
||||
if((meters.length > 0 || fwl.length > 0) && swv.length == 1) {
|
||||
var v = swv.text().substring(1).split('.');
|
||||
var v_major = parseInt(v[0]);
|
||||
var v_minor = parseInt(v[1]);
|
||||
@@ -145,48 +235,66 @@ $(function() {
|
||||
url: swv.data('url'),
|
||||
dataType: 'json'
|
||||
}).done(function(releases) {
|
||||
releases.reverse();
|
||||
var me;
|
||||
var next_patch;
|
||||
var next_minor;
|
||||
var next_major;
|
||||
$.each(releases, function(i, release) {
|
||||
var ver2 = release.tag_name;
|
||||
var v2 = ver2.substring(1).split('.');
|
||||
var v2_major = parseInt(v2[0]);
|
||||
var v2_minor = parseInt(v2[1]);
|
||||
var v2_patch = parseInt(v2[2]);
|
||||
if(!swv.text().match("^v\d{1,2}\.\d{1,2}\.\d{1,2}$")) {
|
||||
nextVersion = releases[0];
|
||||
} else {
|
||||
releases.reverse();
|
||||
var next_patch;
|
||||
var next_minor;
|
||||
var next_major;
|
||||
$.each(releases, function(i, release) {
|
||||
var ver2 = release.tag_name;
|
||||
var v2 = ver2.substring(1).split('.');
|
||||
var v2_major = parseInt(v2[0]);
|
||||
var v2_minor = parseInt(v2[1]);
|
||||
var v2_patch = parseInt(v2[2]);
|
||||
|
||||
if(v2_major == v_major) {
|
||||
if(v2_minor == v_minor) {
|
||||
if(v2_patch > v_patch) {
|
||||
next_patch = release;
|
||||
if(v2_major == v_major) {
|
||||
if(v2_minor == v_minor) {
|
||||
if(v2_patch > v_patch) {
|
||||
next_patch = release;
|
||||
}
|
||||
} else if(v2_minor == v_minor+1) {
|
||||
next_minor = release;
|
||||
}
|
||||
} else if(v2_minor == v_minor+1) {
|
||||
next_minor = release;
|
||||
}
|
||||
} else if(v2_major == v_major+1) {
|
||||
if(next_major) {
|
||||
var mv = next_major.tag_name.substring(1).split('.');
|
||||
var mv_major = parseInt(mv[0]);
|
||||
var mv_minor = parseInt(mv[1]);
|
||||
var mv_patch = parseInt(mv[2]);
|
||||
if(v2_minor == mv_minor) {
|
||||
} else if(v2_major == v_major+1) {
|
||||
if(next_major) {
|
||||
var mv = next_major.tag_name.substring(1).split('.');
|
||||
var mv_major = parseInt(mv[0]);
|
||||
var mv_minor = parseInt(mv[1]);
|
||||
var mv_patch = parseInt(mv[2]);
|
||||
if(v2_minor == mv_minor) {
|
||||
next_major = release;
|
||||
}
|
||||
} else {
|
||||
next_major = release;
|
||||
}
|
||||
} else {
|
||||
next_major = release;
|
||||
}
|
||||
});
|
||||
if(next_minor) {
|
||||
nextVersion = next_minor;
|
||||
} else if(next_major) {
|
||||
nextVersion = next_major;
|
||||
} else if(next_patch) {
|
||||
nextVersion = next_patch;
|
||||
}
|
||||
});
|
||||
if(next_minor) {
|
||||
nextVersion = next_minor;
|
||||
} else if(next_major) {
|
||||
nextVersion = next_major;
|
||||
} else if(next_patch) {
|
||||
nextVersion = next_patch;
|
||||
|
||||
}
|
||||
if(nextVersion) {
|
||||
if(fwl.length > 0) {
|
||||
var chipset = fwl.data('chipset').toLowerCase();
|
||||
$.each(releases, function(i, release) {
|
||||
if(release.tag_name == nextVersion.tag_name) {
|
||||
$.each(release.assets, function(i, asset) {
|
||||
if(asset.name.includes(chipset) && !asset.name.includes("partitions")) {
|
||||
fwl.prop('href', asset.browser_download_url);
|
||||
$('#fwDownload').show();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
$('#newVersionTag').text(nextVersion.tag_name);
|
||||
$('#newVersionUrl').prop('href', nextVersion.html_url);
|
||||
$('#newVersion').removeClass('d-none');
|
||||
@@ -201,6 +309,97 @@ $(function() {
|
||||
}
|
||||
});
|
||||
|
||||
var resizeTO;
|
||||
$( window ).resize(function() {
|
||||
if(resizeTO) clearTimeout(resizeTO);
|
||||
resizeTO = setTimeout(function() {
|
||||
$(this).trigger('resizeEnd');
|
||||
}, 250);
|
||||
});
|
||||
|
||||
$(window).on('resizeEnd', function() {
|
||||
redraw();
|
||||
});
|
||||
|
||||
var zeropad = function(num) {
|
||||
num = num.toString();
|
||||
while (num.length < 2) num = "0" + num;
|
||||
return num;
|
||||
}
|
||||
|
||||
var setupChart = function() {
|
||||
ep = new google.visualization.LineChart(document.getElementById('ep'));
|
||||
mp = new google.visualization.LineChart(document.getElementById('mp'));
|
||||
vp = new google.visualization.ColumnChart(document.getElementById('vp'));
|
||||
ap = new google.visualization.ColumnChart(document.getElementById('ap'));
|
||||
ip = new google.visualization.PieChart(document.getElementById('ip'));
|
||||
xp = new google.visualization.PieChart(document.getElementById('xp'));
|
||||
fetch();
|
||||
drawEnergy();
|
||||
};
|
||||
|
||||
var redraw = function() {
|
||||
ep.draw(ea, eo);
|
||||
mp.draw(ma, mo);
|
||||
vp.draw(va, vo);
|
||||
ap.draw(aa, ao);
|
||||
ip.draw(ia, io);
|
||||
xp.draw(xa, xo);
|
||||
};
|
||||
|
||||
var drawEnergy = function() {
|
||||
$.ajax({
|
||||
url: '/dayplot.json',
|
||||
timeout: 30000,
|
||||
dataType: 'json',
|
||||
}).done(function(json) {
|
||||
data = [['Hour','Value']];
|
||||
var r = 1;
|
||||
var hour = moment.utc().hours();
|
||||
var offset = moment().utcOffset()/60;
|
||||
var min = 0;
|
||||
for(var i = hour; i<24; i++) {
|
||||
var val = json["h"+zeropad(i)];
|
||||
data[r++] = [zeropad((i+offset)%24), val];
|
||||
Math.min(0, val);
|
||||
};
|
||||
for(var i = 0; i < hour; i++) {
|
||||
var val = json["h"+zeropad(i)];
|
||||
data[r++] = [zeropad((i+offset)%24), val];
|
||||
Math.min(0, val);
|
||||
};
|
||||
ea = google.visualization.arrayToDataTable(data);
|
||||
if(min == 0)
|
||||
eo.vAxis.minValue = 0;
|
||||
ep.draw(ea, eo);
|
||||
});
|
||||
$.ajax({
|
||||
url: '/monthplot.json',
|
||||
timeout: 30000,
|
||||
dataType: 'json',
|
||||
}).done(function(json) {
|
||||
data = [['Day','Value']];
|
||||
var r = 1;
|
||||
var day = moment().date();
|
||||
var start = moment().subtract(1, 'months').endOf('month').date();
|
||||
var min = 0;
|
||||
for(var i = day; i<=start; i++) {
|
||||
var val = json["d"+zeropad(i)];
|
||||
data[r++] = [zeropad((i)), val];
|
||||
Math.min(0, val);
|
||||
}
|
||||
for(var i = 1; i < day; i++) {
|
||||
var val = json["d"+zeropad(i)];
|
||||
data[r++] = [zeropad((i)), val];
|
||||
Math.min(0, val);
|
||||
}
|
||||
ma = google.visualization.arrayToDataTable(data);
|
||||
if(min == 0)
|
||||
mo.vAxis.minValue = 0;
|
||||
mp.draw(ma, mo);
|
||||
});
|
||||
};
|
||||
|
||||
var setStatus = function(id, sid) {
|
||||
var item = $('#'+id);
|
||||
item.removeClass('d-none');
|
||||
@@ -220,6 +419,26 @@ var setStatus = function(id, sid) {
|
||||
item.addClass('badge badge-' + status);
|
||||
};
|
||||
|
||||
var voltcol = function(pct) {
|
||||
if(pct > 85) return '#d90000';
|
||||
else if(pct > 75) return'#e32100';
|
||||
else if(pct > 70) return '#ffb800';
|
||||
else if(pct > 65) return '#dcd800';
|
||||
else if(pct > 35) return '#32d900';
|
||||
else if(pct > 25) return '#dcd800';
|
||||
else if(pct > 20) return '#ffb800';
|
||||
else if(pct > 15) return'#e32100';
|
||||
else return '#d90000';
|
||||
};
|
||||
|
||||
var ampcol = function(pct) {
|
||||
if(pct > 85) return '#d90000';
|
||||
else if(pct > 75) return'#e32100';
|
||||
else if(pct > 70) return '#ffb800';
|
||||
else if(pct > 65) return '#dcd800';
|
||||
else return '#32d900';
|
||||
};
|
||||
|
||||
var interval = 5000;
|
||||
var fetch = function() {
|
||||
$.ajax({
|
||||
@@ -231,8 +450,7 @@ var fetch = function() {
|
||||
$(".SimpleMeter").hide();
|
||||
im.show();
|
||||
em.show();
|
||||
vm.show();
|
||||
am.show();
|
||||
|
||||
}
|
||||
|
||||
for(var id in json) {
|
||||
@@ -265,7 +483,7 @@ var fetch = function() {
|
||||
setStatus("mqtt", json.mm);
|
||||
|
||||
|
||||
if(im && im.gaugeMeter) {
|
||||
if(ip) {
|
||||
var v = parseInt(json.i);
|
||||
var pct = (v*100)/parseInt(json.im);
|
||||
var append = "W";
|
||||
@@ -273,14 +491,20 @@ var fetch = function() {
|
||||
v = (v/1000).toFixed(1);
|
||||
append = "kW";
|
||||
}
|
||||
im.gaugeMeter({
|
||||
percent: pct,
|
||||
text: v,
|
||||
append: append
|
||||
});
|
||||
$('.ipo').html(v);
|
||||
$('.ipoa').html(append);
|
||||
var arr = [
|
||||
['Slice', 'Value'],
|
||||
['', (pct*2.88)],
|
||||
['', ((100-pct)*2.88)],
|
||||
['', 72],
|
||||
];
|
||||
io.slices[0].color = ampcol(pct);
|
||||
ia = google.visualization.arrayToDataTable(arr);
|
||||
ip.draw(ia, io);
|
||||
}
|
||||
|
||||
if(em && em.gaugeMeter && json.om) {
|
||||
if(xp) {
|
||||
var v = parseInt(json.e);
|
||||
var pct = (v*100)/(parseInt(json.om)*1000);
|
||||
var append = "W";
|
||||
@@ -288,52 +512,78 @@ var fetch = function() {
|
||||
v = (v/1000).toFixed(1);
|
||||
append = "kW";
|
||||
}
|
||||
em.gaugeMeter({
|
||||
percent: pct,
|
||||
text: v,
|
||||
append: append
|
||||
});
|
||||
$('.epo').html(v);
|
||||
$('.epoa').html(append);
|
||||
var arr = [
|
||||
['Slice', 'Value'],
|
||||
['', (pct*2.88)],
|
||||
['', ((100-pct)*2.88)],
|
||||
['', 72],
|
||||
];
|
||||
xo.slices[0].color = ampcol(pct);
|
||||
xa = google.visualization.arrayToDataTable(arr);
|
||||
xp.draw(xa, xo);
|
||||
}
|
||||
|
||||
if(vm && vm.gaugeMeter) {
|
||||
if(vp) {
|
||||
var c = 0;
|
||||
var t = 0;
|
||||
var r = 1;
|
||||
var arr = [['Phase', 'Voltage', { role: 'style' }, { role: 'annotation' }]];
|
||||
if(json.u1) {
|
||||
t += parseFloat(json.u1);
|
||||
var u1 = parseFloat(json.u1);
|
||||
t += u1;
|
||||
c++;
|
||||
var pct = (Math.max(parseFloat(json.u1)-195.5, 1)*100/69);
|
||||
arr[r++] = ['L1', u1, voltcol(pct), u1 + "V"];
|
||||
}
|
||||
if(json.u2) {
|
||||
t += parseFloat(json.u2);
|
||||
var u2 = parseFloat(json.u2);
|
||||
t += u2;
|
||||
c++;
|
||||
var pct = (Math.max(parseFloat(json.u2)-195.5, 1)*100/69);
|
||||
arr[r++] = ['L2', u2, voltcol(pct), u2 + "V"];
|
||||
}
|
||||
if(json.u3) {
|
||||
t += parseFloat(json.u3);
|
||||
var u3 = parseFloat(json.u3);
|
||||
t += u3;
|
||||
c++;
|
||||
var pct = (Math.max(parseFloat(json.u3)-195.5, 1)*100/69);
|
||||
arr[r++] = ['L3', u3, voltcol(pct), u3 + "V"];
|
||||
}
|
||||
v = t/c;
|
||||
var pct = (Math.max(v-207, 1)*100/46);
|
||||
vm.gaugeMeter({
|
||||
percent: pct,
|
||||
text: v.toFixed(1)
|
||||
});
|
||||
if(v > 0) {
|
||||
va = google.visualization.arrayToDataTable(arr);
|
||||
vp.draw(va, vo);
|
||||
}
|
||||
}
|
||||
|
||||
if(am && am.gaugeMeter && json.mf) {
|
||||
if(ap && json.mf) {
|
||||
var a = 0;
|
||||
var r = 1;
|
||||
var arr = [['Phase', 'Amperage', { role: 'style' }, { role: 'annotation' }]];
|
||||
if(json.i1) {
|
||||
a = Math.max(a, parseFloat(json.i1));
|
||||
var i1 = parseFloat(json.i1);
|
||||
a = Math.max(a, i1);
|
||||
var pct = (parseFloat(json.i1)/parseInt(json.mf))*100;
|
||||
arr[r++] = ['L1', pct, ampcol(pct), i1 + "A"];
|
||||
}
|
||||
if(json.i2) {
|
||||
a = Math.max(a, parseFloat(json.i2));
|
||||
var i2 = parseFloat(json.i2);
|
||||
a = Math.max(a, i2);
|
||||
var pct = (parseFloat(json.i2)/parseInt(json.mf))*100;
|
||||
arr[r++] = ['L2', pct, ampcol(pct), i2 + "A"];
|
||||
}
|
||||
if(json.i3) {
|
||||
a = Math.max(a, parseFloat(json.i3));
|
||||
var i3 = parseFloat(json.i3);
|
||||
a = Math.max(a, i3);
|
||||
var pct = (parseFloat(json.i3)/parseInt(json.mf))*100;
|
||||
arr[r++] = ['L3', pct, ampcol(pct), i3 + "A"];
|
||||
}
|
||||
if(a > 0) {
|
||||
aa = google.visualization.arrayToDataTable(arr);
|
||||
ap.draw(aa, ao);
|
||||
}
|
||||
var pct = (a*100)/parseInt(json.mf);
|
||||
am.gaugeMeter({
|
||||
percent: pct,
|
||||
text: a.toFixed(1)
|
||||
});
|
||||
}
|
||||
|
||||
if(json.me) {
|
||||
@@ -363,6 +613,7 @@ var fetch = function() {
|
||||
var upgrade = function() {
|
||||
if(nextVersion) {
|
||||
if(confirm("Are you sure you want to perform upgrade to " + nextVersion.tag_name + "?")) {
|
||||
$('#loading-indicator').show();
|
||||
window.location.href="/upgrade?version=" + nextVersion.tag_name;
|
||||
}
|
||||
}
|
||||
|
||||
26
web/dayplot.json
Normal file
26
web/dayplot.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"h00" : %.2f,
|
||||
"h01" : %.2f,
|
||||
"h02" : %.2f,
|
||||
"h03" : %.2f,
|
||||
"h04" : %.2f,
|
||||
"h05" : %.2f,
|
||||
"h06" : %.2f,
|
||||
"h07" : %.2f,
|
||||
"h08" : %.2f,
|
||||
"h09" : %.2f,
|
||||
"h10" : %.2f,
|
||||
"h11" : %.2f,
|
||||
"h12" : %.2f,
|
||||
"h13" : %.2f,
|
||||
"h14" : %.2f,
|
||||
"h15" : %.2f,
|
||||
"h16" : %.2f,
|
||||
"h17" : %.2f,
|
||||
"h18" : %.2f,
|
||||
"h19" : %.2f,
|
||||
"h20" : %.2f,
|
||||
"h21" : %.2f,
|
||||
"h22" : %.2f,
|
||||
"h23" : %.2f
|
||||
}
|
||||
34
web/firmware.html
Normal file
34
web/firmware.html
Normal file
@@ -0,0 +1,34 @@
|
||||
<div class="alert alert-danger">
|
||||
WARNING: Units powered over M-bus must be connected to an external power supply during firmware upload. Failure to do so may cause power-down during upload resulting in non-functioning unit.
|
||||
</div>
|
||||
<div class="alert alert-warning">
|
||||
Your board is using {chipset} chipset. Only upload firmware designed for this chipset. Failure to do so may result in non-functioning unit.
|
||||
<span id="fwDownload" style="display: none;"><br/>Download latest firmware file <a id="fwLink" href="#" data-chipset="{chipset}">here</a></span>
|
||||
</div>
|
||||
|
||||
<form method="post" enctype="multipart/form-data" class="upload-form">
|
||||
<div class="my-3 p-3 bg-white rounded shadow">
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Upload</span>
|
||||
</div>
|
||||
<div class="custom-file">
|
||||
<input name="file" type="file" class="custom-file-input" id="fileUploadField">
|
||||
<label class="custom-file-label" for="fileUploadField">Choose file</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row form-group">
|
||||
<div class="col-6">
|
||||
<a href="javascript:history.back();" class="btn btn-outline-secondary">Back</a>
|
||||
</div>
|
||||
<div class="col-6 text-right">
|
||||
<button class="btn btn-primary">Upload</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,12 +1,20 @@
|
||||
<div id="newVersion" class="alert alert-info d-none">New version <span id="newVersionTag"></span>! <a id="newVersionUrl" href="#" target="_blank">view</a><span class="d-none ssl-capable"> or <a href="javascript:upgrade();">upgrade</a></span></div>
|
||||
<div id="newVersion" class="alert alert-info d-none">New version <span id="newVersionTag"></span>!
|
||||
<a id="newVersionUrl" href="#" target="_blank">view</a>
|
||||
<span class="d-none ssl-capable"> or <a href="javascript:upgrade();">upgrade</a></span>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div id="loading-indicator" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: #dddddd99; z-index: 999999; padding-top: 20%; display: none;" class="text-center">
|
||||
<div class="spinner-border text-primary" role="status" style="width: 5rem; height: 5rem;">
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"></script>
|
||||
<script src="gaugemeter.js"></script>
|
||||
<script src="https://www.gstatic.com/charts/loader.js"></script>
|
||||
<script src="application-${version}.js"></script>
|
||||
|
||||
</body>
|
||||
|
||||
@@ -1,276 +0,0 @@
|
||||
/*
|
||||
* AshAlom Gauge Meter. Version 2.0.0
|
||||
* Copyright AshAlom.com All rights reserved.
|
||||
* https://github.com/AshAlom/GaugeMeter <- Deleted!
|
||||
* https://github.com/githubsrinath/GaugeMeter <- Backup original.
|
||||
*
|
||||
* Original created by Dr Ash Alom
|
||||
*
|
||||
* This is a bug fixed and modified version of the AshAlom Gauge Meter.
|
||||
* Copyright 2018 Michael Wolf (Mictronics)
|
||||
* https://github.com/mictronics/GaugeMeter
|
||||
*
|
||||
*/
|
||||
!function ($) {
|
||||
$.fn.gaugeMeter = function (t) {
|
||||
var defaults = $.extend({
|
||||
id: "",
|
||||
percent: 0,
|
||||
used: null,
|
||||
min: null,
|
||||
total: null,
|
||||
size: 100,
|
||||
prepend: "",
|
||||
append: "",
|
||||
theme: "Red-Gold-Green",
|
||||
color: "",
|
||||
back: "RGBa(0,0,0,.06)",
|
||||
width: 3,
|
||||
style: "Full",
|
||||
stripe: "0",
|
||||
animationstep: 1,
|
||||
animate_gauge_colors: false,
|
||||
animate_text_colors: false,
|
||||
label: "",
|
||||
label_color: "Black",
|
||||
text: "",
|
||||
text_size: 0.22,
|
||||
fill: "",
|
||||
showvalue: false
|
||||
}, t);
|
||||
return this.each(function () {
|
||||
|
||||
function getThemeColor(e) {
|
||||
var t = "#2C94E0";
|
||||
return e || (e = 1e-14),
|
||||
"Red-Gold-Green" === option.theme && (e > 0 && (t = "#d90000"), e > 10 && (t = "#e32100"), e > 20 && (t = "#f35100"), e > 30 && (t = "#ff8700"), e > 40 && (t = "#ffb800"), e > 50 && (t = "#ffd900"), e > 60 && (t = "#dcd800"), e > 70 && (t = "#a6d900"), e > 80 && (t = "#69d900"), e > 90 && (t = "#32d900")),
|
||||
"Green-Gold-Red" === option.theme && (e > 0 && (t = "#32d900"), e > 10 && (t = "#69d900"), e > 20 && (t = "#a6d900"), e > 30 && (t = "#dcd800"), e > 40 && (t = "#ffd900"), e > 50 && (t = "#ffb800"), e > 60 && (t = "#ff8700"), e > 70 && (t = "#f35100"), e > 80 && (t = "#e32100"), e > 90 && (t = "#d90000")),
|
||||
"Green-Red" === option.theme && (e > 0 && (t = "#32d900"), e > 10 && (t = "#41c900"), e > 20 && (t = "#56b300"), e > 30 && (t = "#6f9900"), e > 40 && (t = "#8a7b00"), e > 50 && (t = "#a75e00"), e > 60 && (t = "#c24000"), e > 70 && (t = "#db2600"), e > 80 && (t = "#f01000"), e > 90 && (t = "#ff0000")),
|
||||
"Red-Green" === option.theme && (e > 0 && (t = "#ff0000"), e > 10 && (t = "#f01000"), e > 20 && (t = "#db2600"), e > 30 && (t = "#c24000"), e > 40 && (t = "#a75e00"), e > 50 && (t = "#8a7b00"), e > 60 && (t = "#6f9900"), e > 70 && (t = "#56b300"), e > 80 && (t = "#41c900"), e > 90 && (t = "#32d900")),
|
||||
"DarkBlue-LightBlue" === option.theme && (e > 0 && (t = "#2c94e0"), e > 10 && (t = "#2b96e1"), e > 20 && (t = "#2b99e4"), e > 30 && (t = "#2a9ce7"), e > 40 && (t = "#28a0e9"), e > 50 && (t = "#26a4ed"), e > 60 && (t = "#25a8f0"), e > 70 && (t = "#24acf3"), e > 80 && (t = "#23aff5"), e > 90 && (t = "#21b2f7")),
|
||||
"LightBlue-DarkBlue" === option.theme && (e > 0 && (t = "#21b2f7"), e > 10 && (t = "#23aff5"), e > 20 && (t = "#24acf3"), e > 30 && (t = "#25a8f0"), e > 40 && (t = "#26a4ed"), e > 50 && (t = "#28a0e9"), e > 60 && (t = "#2a9ce7"), e > 70 && (t = "#2b99e4"), e > 80 && (t = "#2b96e1"), e > 90 && (t = "#2c94e0")),
|
||||
"DarkRed-LightRed" === option.theme && (e > 0 && (t = "#d90000"), e > 10 && (t = "#dc0000"), e > 20 && (t = "#e00000"), e > 30 && (t = "#e40000"), e > 40 && (t = "#ea0000"), e > 50 && (t = "#ee0000"), e > 60 && (t = "#f30000"), e > 70 && (t = "#f90000"), e > 80 && (t = "#fc0000"), e > 90 && (t = "#ff0000")),
|
||||
"LightRed-DarkRed" === option.theme && (e > 0 && (t = "#ff0000"), e > 10 && (t = "#fc0000"), e > 20 && (t = "#f90000"), e > 30 && (t = "#f30000"), e > 40 && (t = "#ee0000"), e > 50 && (t = "#ea0000"), e > 60 && (t = "#e40000"), e > 70 && (t = "#e00000"), e > 80 && (t = "#dc0000"), e > 90 && (t = "#d90000")),
|
||||
"DarkGreen-LightGreen" === option.theme && (e > 0 && (t = "#32d900"), e > 10 && (t = "#33db00"), e > 20 && (t = "#34df00"), e > 30 && (t = "#34e200"), e > 40 && (t = "#36e700"), e > 50 && (t = "#37ec00"), e > 60 && (t = "#38f100"), e > 70 && (t = "#38f600"), e > 80 && (t = "#39f900"), e > 90 && (t = "#3afc00")),
|
||||
"LightGreen-DarkGreen" === option.theme && (e > 0 && (t = "#3afc00"), e > 10 && (t = "#39f900"), e > 20 && (t = "#38f600"), e > 30 && (t = "#38f100"), e > 40 && (t = "#37ec00"), e > 50 && (t = "#36e700"), e > 60 && (t = "#34e200"), e > 70 && (t = "#34df00"), e > 80 && (t = "#33db00"), e > 90 && (t = "#32d900")),
|
||||
"DarkGold-LightGold" === option.theme && (e > 0 && (t = "#ffb800"), e > 10 && (t = "#ffba00"), e > 20 && (t = "#ffbd00"), e > 30 && (t = "#ffc200"), e > 40 && (t = "#ffc600"), e > 50 && (t = "#ffcb00"), e > 60 && (t = "#ffcf00"), e > 70 && (t = "#ffd400"), e > 80 && (t = "#ffd600"), e > 90 && (t = "#ffd900")),
|
||||
"LightGold-DarkGold" === option.theme && (e > 0 && (t = "#ffd900"), e > 10 && (t = "#ffd600"), e > 20 && (t = "#ffd400"), e > 30 && (t = "#ffcf00"), e > 40 && (t = "#ffcb00"), e > 50 && (t = "#ffc600"), e > 60 && (t = "#ffc200"), e > 70 && (t = "#ffbd00"), e > 80 && (t = "#ffba00"), e > 90 && (t = "#ffb800")),
|
||||
"Voltage" === option.theme && (e <= 0 && (t = "#d90000"), e > 0 && (t = "#e32100"), e > 10 && (t = "#ffb800"), e > 20 && (t = "#dcd800"), e > 30 && (t = "#32d900"), e > 40 && (t = "#32d900"), e > 50 && (t = "#32d900"), e > 60 && (t = "#32d900"), e > 70 && (t = "#dcd800"), e > 80 && (t = "#ffb800"), e > 90 && (t = "#e32100"), e >= 100 && (t = "#d90000")),
|
||||
"White" === option.theme && (t = "#fff"),
|
||||
"Black" === option.theme && (t = "#000"),
|
||||
t;
|
||||
};
|
||||
/* The label below gauge. */
|
||||
function createLabel(t, a) {
|
||||
if(t.children("b").length === 0){
|
||||
$("<b></b>").appendTo(t).html(option.label).css({
|
||||
"line-height": option.size + 5 * a + "px",
|
||||
color: option.label_color
|
||||
});
|
||||
}
|
||||
};
|
||||
/* Prepend and append text, the gauge text or percentage value. */
|
||||
function createSpanTag(t) {
|
||||
var fgcolor = "";
|
||||
if (option.animate_text_colors === true){
|
||||
fgcolor = option.fgcolor;
|
||||
}
|
||||
var child = t.children("span");
|
||||
if(child.length !== 0){
|
||||
child.html(r).css({color: fgcolor});
|
||||
return;
|
||||
}
|
||||
if(option.text_size <= 0.0 || Number.isNaN(option.text_size)){
|
||||
option.text_size = 0.22;
|
||||
}
|
||||
if(option.text_size > 0.5){
|
||||
option.text_size = 0.5;
|
||||
}
|
||||
$("<span></span>").appendTo(t).html(r).css({
|
||||
"line-height": option.size + "px",
|
||||
"font-size": option.text_size * option.size + "px",
|
||||
color: fgcolor
|
||||
});
|
||||
};
|
||||
/* Get data attributes as options from div tag. Fall back to defaults when not exists. */
|
||||
function getDataAttr(t) {
|
||||
$.each(dataAttr, function (index, element) {
|
||||
if(t.data(element) !== undefined && t.data(element) !== null){
|
||||
option[element] = t.data(element);
|
||||
} else {
|
||||
option[element] = $(defaults).attr(element);
|
||||
}
|
||||
|
||||
if(element === "fill"){
|
||||
s = option[element];
|
||||
}
|
||||
|
||||
if((element === "size" ||
|
||||
element === "width" ||
|
||||
element === "animationstep" ||
|
||||
element === "stripe"
|
||||
) && !Number.isInteger(option[element])){
|
||||
option[element] = parseInt(option[element]);
|
||||
}
|
||||
|
||||
if(element === "text_size"){
|
||||
option[element] = parseFloat(option[element]);
|
||||
}
|
||||
});
|
||||
};
|
||||
/* Draws the gauge. */
|
||||
function drawGauge(a) {
|
||||
if(M < 0) M = 0;
|
||||
if(M > 100) M = 100;
|
||||
var lw = option.width < 1 || isNaN(option.width) ? option.size / 20 : option.width;
|
||||
g.clearRect(0, 0, b.width, b.height);
|
||||
g.beginPath();
|
||||
g.arc(m, v, x, G, k, !1);
|
||||
if(s){
|
||||
g.fillStyle = option.fill;
|
||||
g.fill();
|
||||
}
|
||||
g.lineWidth = lw;
|
||||
g.strokeStyle = option.back;
|
||||
option.stripe > parseInt(0) ? g.setLineDash([option.stripe], 1) : g.lineCap = "round";
|
||||
g.stroke();
|
||||
g.beginPath();
|
||||
g.arc(m, v, x, -I, P * a - I, !1);
|
||||
g.lineWidth = lw;
|
||||
g.strokeStyle = option.fgcolor;
|
||||
g.stroke();
|
||||
c > M && (M += z, requestAnimationFrame(function(){
|
||||
drawGauge(Math.min(M, c) / 100);
|
||||
}, p));
|
||||
};
|
||||
|
||||
$(this).attr("data-id", $(this).attr("id"));
|
||||
var r,
|
||||
dataAttr = ["percent",
|
||||
"used",
|
||||
"min",
|
||||
"total",
|
||||
"size",
|
||||
"prepend",
|
||||
"append",
|
||||
"theme",
|
||||
"color",
|
||||
"back",
|
||||
"width",
|
||||
"style",
|
||||
"stripe",
|
||||
"animationstep",
|
||||
"animate_gauge_colors",
|
||||
"animate_text_colors",
|
||||
"label",
|
||||
"label_color",
|
||||
"text",
|
||||
"text_size",
|
||||
"fill",
|
||||
"showvalue"],
|
||||
option = {},
|
||||
c = 0,
|
||||
p = $(this),
|
||||
s = false;
|
||||
p.addClass("gaugeMeter");
|
||||
getDataAttr(p);
|
||||
|
||||
if(Number.isInteger(option.used) && Number.isInteger(option.total)){
|
||||
var u = option.used;
|
||||
var t = option.total;
|
||||
if(Number.isInteger(option.min)) {
|
||||
if(option.min < 0) {
|
||||
t -= option.min;
|
||||
u -= option.min;
|
||||
}
|
||||
}
|
||||
c = u / (t / 100);
|
||||
} else {
|
||||
if(Number.isInteger(option.percent)){
|
||||
c = option.percent;
|
||||
} else {
|
||||
c = parseInt(defaults.percent);
|
||||
}
|
||||
}
|
||||
if(c < 0) c = 0;
|
||||
if(c > 100) c = 100;
|
||||
|
||||
if( option.text !== "" && option.text !== null && option.text !== undefined){
|
||||
if(option.append !== "" && option.append !== null && option.append !== undefined){
|
||||
r = option.text + "<u>" + option.append + "</u>";
|
||||
} else {
|
||||
r = option.text;
|
||||
}
|
||||
if(option.prepend !== "" && option.prepend !== null && option.prepend !== undefined){
|
||||
r = "<s>" + option.prepend + "</s>" + r;
|
||||
}
|
||||
} else {
|
||||
if(defaults.showvalue === true || option.showvalue === true){
|
||||
r = option.used;
|
||||
} else {
|
||||
r = c.toString();
|
||||
}
|
||||
if(option.prepend !== "" && option.prepend !== null && option.prepend !== undefined){
|
||||
r = "<s>" + option.prepend + "</s>" + r;
|
||||
}
|
||||
|
||||
if(option.append !== "" && option.append !== null && option.append !== undefined){
|
||||
r = r + "<u>" + option.append + "</u>";
|
||||
}
|
||||
}
|
||||
|
||||
option.fgcolor = getThemeColor(c);
|
||||
if(option.color !== "" && option.color !== null && option.color !== undefined){
|
||||
option.fgcolor = option.color;
|
||||
}
|
||||
|
||||
if(option.animate_gauge_colors === true){
|
||||
option.fgcolor = getThemeColor(c);
|
||||
}
|
||||
createSpanTag(p);
|
||||
|
||||
if(option.style !== "" && option.style !== null && option.style !== undefined){
|
||||
createLabel(p, option.size / 13);
|
||||
}
|
||||
|
||||
$(this).width(option.size + "px");
|
||||
|
||||
var b = $("<canvas></canvas>").attr({width: option.size, height: option.size}).get(0),
|
||||
g = b.getContext("2d"),
|
||||
m = b.width / 2,
|
||||
v = b.height / 2,
|
||||
_ = 360 * option.percent,
|
||||
x = (_ * (Math.PI / 180), b.width / 2.5),
|
||||
k = 2.3 * Math.PI,
|
||||
G = 0,
|
||||
M = 0 === option.animationstep ? c : 0,
|
||||
z = Math.max(option.animationstep, 0),
|
||||
P = 2 * Math.PI,
|
||||
I = Math.PI / 2,
|
||||
R = option.style;
|
||||
var child = $(this).children("canvas");
|
||||
if(child.length !== 0){
|
||||
/* Replace existing canvas when new percentage was written. */
|
||||
child.replaceWith(b);
|
||||
} else {
|
||||
/* Initially create canvas. */
|
||||
$(b).appendTo($(this));
|
||||
}
|
||||
|
||||
if ("Semi" === R){
|
||||
k = 2 * Math.PI;
|
||||
G = 3.13;
|
||||
P = 1 * Math.PI;
|
||||
I = Math.PI / .996;
|
||||
}
|
||||
if ("Arch" === R){
|
||||
k = 2.195 * Math.PI;
|
||||
G = 1, G = 655.99999;
|
||||
P = 1.4 * Math.PI;
|
||||
I = Math.PI / .8335;
|
||||
}
|
||||
drawGauge(M / 100);
|
||||
});
|
||||
};
|
||||
}
|
||||
(jQuery);
|
||||
@@ -60,6 +60,24 @@
|
||||
</div>
|
||||
<input name="vccPin" type="number" min="0" max="${gpio.max}" class="form-control" value="${config.vccPin}"/>
|
||||
</div>
|
||||
<div class="m-2 input-group input-group-sm" style="width: 200px;">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">GND resistor</span>
|
||||
</div>
|
||||
<input type="number" min="1" max="1000" step="1" class="form-control" name="vccResistorGnd" value="${config.vccResistorGnd}" />
|
||||
<div class="input-group-append" title="Inverted">
|
||||
<label class="input-group-text">kΩ</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="m-2 input-group input-group-sm" style="width: 190px;">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Vcc resistor</span>
|
||||
</div>
|
||||
<input type="number" min="1" max="1000" step="1" class="form-control" name="vccResistorVcc" value="${config.vccResistorVcc}" />
|
||||
<div class="input-group-append" title="Inverted">
|
||||
<label class="input-group-text">kΩ</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="m-2 input-group input-group-sm" style="width: 140px;">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Multiplier</span>
|
||||
@@ -70,7 +88,7 @@
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Offset</span>
|
||||
</div>
|
||||
<input type="number" min="0.1" max="3.5" step="0.01" class="form-control" name="vccOffset" value="${config.vccOffset}" />
|
||||
<input type="number" min="0.0" max="3.5" step="0.01" class="form-control" name="vccOffset" value="${config.vccOffset}" />
|
||||
</div>
|
||||
<div class="m-2 input-group input-group-sm" style="width: 130px;">
|
||||
<div class="input-group-prepend">
|
||||
|
||||
@@ -6,51 +6,40 @@
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
|
||||
<style>
|
||||
.GaugeMeter {
|
||||
position: Relative;
|
||||
text-align: Center;
|
||||
overflow: Hidden;
|
||||
cursor: Default;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.GaugeMeter SPAN, .GaugeMeter B {
|
||||
width: 54%;
|
||||
position: Absolute;
|
||||
text-align: Center;
|
||||
display: Inline-Block;
|
||||
color: RGBa(0,0,0,.8);
|
||||
font-weight: 100;
|
||||
font-family: "Open Sans", Arial;
|
||||
overflow: Hidden;
|
||||
white-space: NoWrap;
|
||||
text-overflow: Ellipsis;
|
||||
margin: 0 23%;
|
||||
}
|
||||
|
||||
.GaugeMeter[data-style="Semi"] B {
|
||||
width: 80%;
|
||||
margin: 0 10%;
|
||||
}
|
||||
|
||||
.GaugeMeter S, .GaugeMeter U {
|
||||
text-decoration: None;
|
||||
font-size: .60em;
|
||||
font-weight: 200;
|
||||
opacity: .6;
|
||||
}
|
||||
|
||||
.GaugeMeter B {
|
||||
color: #000;
|
||||
font-weight: 200;
|
||||
opacity: .8;
|
||||
}
|
||||
.navbar-expand .navbar-nav .nav-link,.navbar-brand {
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
.plot1 {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
}
|
||||
.plot2 {
|
||||
width: 100%;
|
||||
height: 224px;
|
||||
}
|
||||
.overlay-plot {
|
||||
position: relative;
|
||||
}
|
||||
.plot-overlay {
|
||||
position: absolute;
|
||||
top: 70px;
|
||||
left: 25%;
|
||||
width: 50%;
|
||||
text-align: center;
|
||||
}
|
||||
.ipo,.xpo {
|
||||
font-size: 1.7rem;
|
||||
}
|
||||
.ipoa,.xpoa {
|
||||
font-size: 1.0rem;
|
||||
color: grey;
|
||||
}
|
||||
.pol {
|
||||
font-size: 1.0rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
|
||||
@@ -6,51 +6,40 @@
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
|
||||
<style>
|
||||
.GaugeMeter {
|
||||
position: Relative;
|
||||
text-align: Center;
|
||||
overflow: Hidden;
|
||||
cursor: Default;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.GaugeMeter SPAN, .GaugeMeter B {
|
||||
width: 54%;
|
||||
position: Absolute;
|
||||
text-align: Center;
|
||||
display: Inline-Block;
|
||||
color: RGBa(0,0,0,.8);
|
||||
font-weight: 100;
|
||||
font-family: "Open Sans", Arial;
|
||||
overflow: Hidden;
|
||||
white-space: NoWrap;
|
||||
text-overflow: Ellipsis;
|
||||
margin: 0 23%;
|
||||
}
|
||||
|
||||
.GaugeMeter[data-style="Semi"] B {
|
||||
width: 80%;
|
||||
margin: 0 10%;
|
||||
}
|
||||
|
||||
.GaugeMeter S, .GaugeMeter U {
|
||||
text-decoration: None;
|
||||
font-size: .60em;
|
||||
font-weight: 200;
|
||||
opacity: .6;
|
||||
}
|
||||
|
||||
.GaugeMeter B {
|
||||
color: #000;
|
||||
font-weight: 200;
|
||||
opacity: .8;
|
||||
}
|
||||
.navbar-expand .navbar-nav .nav-link,.navbar-brand {
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
.plot1 {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
}
|
||||
.plot2 {
|
||||
width: 100%;
|
||||
height: 224px;
|
||||
}
|
||||
.overlay-plot {
|
||||
position: relative;
|
||||
}
|
||||
.plot-overlay {
|
||||
position: absolute;
|
||||
top: 70px;
|
||||
left: 25%;
|
||||
width: 50%;
|
||||
text-align: center;
|
||||
}
|
||||
.ipo,.xpo {
|
||||
font-size: 1.7rem;
|
||||
}
|
||||
.ipoa,.xpoa {
|
||||
font-size: 1.0rem;
|
||||
color: grey;
|
||||
}
|
||||
.pol {
|
||||
font-size: 1.0rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="bg-white rounded shadow p-1">
|
||||
<div class="bg-white rounded shadow p-1 mb-3">
|
||||
<div class="row">
|
||||
<div class="col-md-2 col-6">
|
||||
<div class="text-center">Up <span class="ju">{cs}</span></div>
|
||||
@@ -21,51 +21,39 @@
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="row">
|
||||
<div class="col-sm-6 mt-3">
|
||||
<div class="col-sm-6 mb-3">
|
||||
<div class="bg-white rounded shadow p-3">
|
||||
<div class="text-center">
|
||||
<div class="SimpleMeter ji" style="display: inline;">
|
||||
{P} W
|
||||
</div>
|
||||
<div id="im" class="GaugeMeter rounded"
|
||||
style="display: none;"
|
||||
data-size="180px"
|
||||
data-text_size="0.15"
|
||||
data-width="25"
|
||||
data-style="Arch"
|
||||
data-theme="Green-Gold-Red"
|
||||
data-animationstep="0"
|
||||
data-label="{ti}"
|
||||
></div>
|
||||
<div class="text-center overlay-plot">
|
||||
<div id="ip" class="plot1"></div>
|
||||
<span class="plot-overlay">
|
||||
<span class="ipo">{P}</span>
|
||||
<span class="ipoa">W</span>
|
||||
<br/>
|
||||
<span class="pol">{ti}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="row ric" style="display: {da};">
|
||||
<div class="col-12 text-right"><span class="jic">{tPI}</span> kWh</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 mt-3" style="display: {de};">
|
||||
<div class="col-sm-6 mb-3" style="display: {de};">
|
||||
<div class="bg-white rounded shadow p-3">
|
||||
<div class="text-center">
|
||||
<div class="SimpleMeter je" style="display: inline;">
|
||||
{PO} W
|
||||
</div>
|
||||
<div id="em" class="GaugeMeter rounded"
|
||||
style="display: none;"
|
||||
data-size="180px"
|
||||
data-text_size="0.15"
|
||||
data-width="25"
|
||||
data-style="Arch"
|
||||
data-theme="DarkGreen-LightGreen"
|
||||
data-animationstep="0"
|
||||
data-label="Export"
|
||||
></div>
|
||||
<div class="text-center overlay-plot">
|
||||
<div id="xp" class="plot1"></div>
|
||||
<span class="plot-overlay">
|
||||
<span class="xpo">{PO}</span>
|
||||
<span class="xpoa">W</span>
|
||||
<br/>
|
||||
<span class="pol">Export</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="row rec" style="display: {da};">
|
||||
<div class="col-12 text-right"><span class="jec">{tPO}</span> kWh</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 mt-3" style="display: {dn};">
|
||||
<div class="col-sm-6 mb-3" style="display: {dn};">
|
||||
<div class="bg-white rounded shadow p-3" style="display: {da};">
|
||||
<h5 class="text-center">Reactive</h5>
|
||||
<div class="row rric">
|
||||
@@ -76,7 +64,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 mt-3" style="display: {de};">
|
||||
<div class="col-sm-12 mb-3" style="display: {de};">
|
||||
<div class="bg-white rounded shadow p-3" style="display: {da};">
|
||||
<div class="row rrec">
|
||||
<div class="col-4 col-sm-2">In</div>
|
||||
@@ -88,48 +76,26 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-sm-6 mt-3">
|
||||
<div class="col-lg-3 col-sm-6 mb-3">
|
||||
<div class="bg-white rounded shadow p-3">
|
||||
<div class="text-center">
|
||||
<div id="vm" class="GaugeMeter rounded"
|
||||
style="display: none;"
|
||||
data-size="180px"
|
||||
data-text_size="0.15"
|
||||
data-width="25"
|
||||
data-style="Arch"
|
||||
data-theme="Voltage"
|
||||
data-animationstep="0"
|
||||
data-label="Volt"
|
||||
></div>
|
||||
</div>
|
||||
<div class="row ru2" style="display: {3p};">
|
||||
<div class="col-4"><span class="ju1">{U1}</span>V</div>
|
||||
<div class="col-4 text-center"><span class="ju2">{U2}</span>V</div>
|
||||
<div class="col-4 text-right"><span class="ju3">{U3}</span>V</div>
|
||||
<div id="vp" class="plot2"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-sm-6 mb-3 mt-3">
|
||||
<div class="col-lg-3 col-sm-6 mb-3">
|
||||
<div class="bg-white rounded shadow p-3">
|
||||
<div class="text-center">
|
||||
<div id="am" class="GaugeMeter rounded"
|
||||
style="display: none;"
|
||||
data-size="180px"
|
||||
data-text_size="0.15"
|
||||
data-width="25"
|
||||
data-style="Arch"
|
||||
data-theme="Green-Gold-Red"
|
||||
data-animationstep="0"
|
||||
data-label="Ampere"
|
||||
></div>
|
||||
</div>
|
||||
<div class="row ru2" style="display: {3p};">
|
||||
<div class="col-4"><span class="ji1">{I1}</span>A</div>
|
||||
<div class="col-4 text-center"><span class="ji2">{I2}</span>A</div>
|
||||
<div class="col-4 text-right"><span class="ji3">{I3}</span>A</div>
|
||||
<div id="ap" class="plot2"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-12 mb-3">
|
||||
<div class="bg-white rounded shadow" id="ep" style="width: 100%; height: 224px;"></div>
|
||||
</div>
|
||||
<div class="col-xl-12 mb-3">
|
||||
<div class="bg-white rounded shadow" id="mp" style="width: 100%; height: 224px;"></div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-sm-6 mb-3 d-none me me-1 me-2 me-3 me-4 me-5 me-6 me-7 me-8 me-9 me-10 me-11 me-12 me-13">
|
||||
<div class="d-none badge badge-danger me me-1 me-2 me-5 me-6 me-7 me-8 me-9 me-12">MQTT communication error (<span id="ml">-</span>)</div>
|
||||
<div class="d-none badge badge-danger me me-3">MQTT failed to connect</div>
|
||||
@@ -139,3 +105,4 @@
|
||||
<div class="d-none badge badge-danger me me-13">MQTT lost connection</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -80,6 +80,8 @@
|
||||
</div>
|
||||
<select class="form-control" name="f">
|
||||
<option value="0" {f0}></option>
|
||||
<option value="16" {f16}>16A</option>
|
||||
<option value="20" {f20}>20A</option>
|
||||
<option value="25" {f25}>25A</option>
|
||||
<option value="32" {f32}>32A</option>
|
||||
<option value="35" {f35}>35A</option>
|
||||
|
||||
33
web/monthplot.json
Normal file
33
web/monthplot.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"d01" : %.2f,
|
||||
"d02" : %.2f,
|
||||
"d03" : %.2f,
|
||||
"d04" : %.2f,
|
||||
"d05" : %.2f,
|
||||
"d06" : %.2f,
|
||||
"d07" : %.2f,
|
||||
"d08" : %.2f,
|
||||
"d09" : %.2f,
|
||||
"d10" : %.2f,
|
||||
"d11" : %.2f,
|
||||
"d12" : %.2f,
|
||||
"d13" : %.2f,
|
||||
"d14" : %.2f,
|
||||
"d15" : %.2f,
|
||||
"d16" : %.2f,
|
||||
"d17" : %.2f,
|
||||
"d18" : %.2f,
|
||||
"d19" : %.2f,
|
||||
"d20" : %.2f,
|
||||
"d21" : %.2f,
|
||||
"d22" : %.2f,
|
||||
"d23" : %.2f,
|
||||
"d24" : %.2f,
|
||||
"d25" : %.2f,
|
||||
"d26" : %.2f,
|
||||
"d27" : %.2f,
|
||||
"d28" : %.2f,
|
||||
"d29" : %.2f,
|
||||
"d30" : %.2f,
|
||||
"d31" : %.2f
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
<form method="post" enctype="multipart/form-data" class="upload-form">
|
||||
<div class="my-3 p-3 bg-white rounded shadow">
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
|
||||
Reference in New Issue
Block a user