Compare commits

...

28 Commits

Author SHA1 Message Date
Gunnar Skjold
217247b28e Temperature sensor graph sort 2021-12-19 10:23:18 +01:00
Gunnar Skjold
4828f5a727 Temperature graph 2021-12-19 09:57:51 +01:00
Gunnar Skjold
062a3634a9 Fixed entso-e debugging 2021-12-19 09:17:57 +01:00
Gunnar Skjold
376cd0cf90 Fixed NULL access 2021-12-19 09:15:06 +01:00
Gunnar Skjold
2a20893a58 Fixed real time calculation issues 2021-12-19 09:14:31 +01:00
Gunnar Skjold
f3dba112de Merge branch 'master' of github.com:gskjold/AmsToMqttBridge 2021-12-18 18:30:22 +01:00
Gunnar Skjold
b037d6bb64 Try at least twice before showing ESP as red 2021-12-18 18:25:31 +01:00
Gunnar Skjold
6f7eacddff Trying to get ESP32S2 to work 2021-12-18 18:24:58 +01:00
Gunnar Skjold
4185411315 Merge pull request #169 from kng/master
Change time.h to timelib.h for 32bit to compile
2021-12-18 17:21:19 +01:00
Daniel Ekman
2a1b5a5f6d Change time.h to timelib.h for 32bit to compile 2021-12-18 17:10:53 +01:00
Gunnar Skjold
246a4f96fe Merge pull request #168 from kng/master
Assignment error in meter types
2021-12-18 15:43:09 +01:00
Daniel Ekman
c7d235b367 Assignment error in meter types 2021-12-18 15:37:30 +01:00
Gunnar Skjold
c5c8fbc2a0 Trying timezone from repo again 2021-12-18 15:25:06 +01:00
Gunnar Skjold
8adf591c4e Fixed timelib 2021-12-18 15:20:15 +01:00
Gunnar Skjold
aa893f7ede Fixed bug in price fetch 2021-12-18 15:17:34 +01:00
Gunnar Skjold
f8b1725e94 Revert lib changes 2021-12-18 14:41:58 +01:00
Gunnar Skjold
ae7e3d11d5 Merge branch 'master' of github.com:gskjold/AmsToMqttBridge 2021-12-18 14:33:59 +01:00
Gunnar Skjold
73b20a0766 Merge pull request #167 from kng/master
Libdeps updated to avoid conflict with time.h
2021-12-18 14:29:58 +01:00
Daniel Ekman
d8cf961258 Libdeps updated to avoid conflict with time.h 2021-12-18 13:55:57 +01:00
Gunnar Skjold
bccd19812d Added power factor to MQTT JSON 2021-12-16 11:37:00 +01:00
Gunnar Skjold
6954ff5432 Merge pull request #163 from mikkle/pf-mqtt-cp-fix
Fix c/p error in mqtt powerfactor publishing
2021-12-16 11:26:01 +01:00
Mikkel Troest
6200f31b83 Fix c/p error in mqtt powerfactor publishing 2021-12-16 10:36:38 +01:00
Gunnar Skjold
5408b3c2a9 Make sure temperatures are read just after receiving HAN data 2021-12-13 20:57:17 +01:00
Gunnar Skjold
bd0b3ebb26 Removed debugging 2021-12-12 10:38:40 +01:00
Gunnar Skjold
895a9bc6b1 Fixed api key overwrite 2021-12-12 10:36:50 +01:00
Gunnar Skjold
ff935fc920 Specify that prices only work for ESP32 2021-12-12 08:36:23 +01:00
Gunnar Skjold
3833421e5f Fixed "new version" announcement 2021-12-10 19:09:24 +01:00
Gunnar Skjold
ebdc357a47 Check for scaling and use if available 2021-12-10 18:46:57 +01:00
27 changed files with 475 additions and 259 deletions

1
.gitignore vendored
View File

@@ -13,3 +13,4 @@ platformio-user.ini
/test /test
/web/test.html /web/test.html
/sdkconfig /sdkconfig
/.tmp

View File

@@ -1,7 +1,7 @@
# AMS MQTT Bridge # AMS MQTT Bridge
This code is designed to decode data from electric smart meters installed in many countries in Europe these days. The data is presented in a graphical web interface and can also send the data to a MQTT broker which makes it suitable for home automation project. Originally it was only designed to work with Norwegian meters, but has since been adapter to read any IEC-62056-7-5 or IEC-62056-21 compliant meters. This code is designed to decode data from electric smart meters installed in many countries in Europe these days. The data is presented in a graphical web interface and can also send the data to a MQTT broker which makes it suitable for home automation project. Originally it was only designed to work with Norwegian meters, but has since been adapter to read any IEC-62056-7-5 or IEC-62056-21 compliant meters.
Later development have added Energy usage graph for both day and month, as well as future energy price. The code can run on any ESP8266 or ESP32 hardware which you can read more about in the [WiKi](https://github.com/gskjold/AmsToMqttBridge/wiki). If you don't have the knowledge to set up a ESP device yourself, have a look at the shop at [amsleser.no](https://amsleser.no/). Later development have added Energy usage graph for both day and month, as well as future energy price (Prices only available for ESP32). The code can run on any ESP8266 or ESP32 hardware which you can read more about in the [WiKi](https://github.com/gskjold/AmsToMqttBridge/wiki). If you don't have the knowledge to set up a ESP device yourself, have a look at the shop at [amsleser.no](https://amsleser.no/).
<img src="webui.png"> <img src="webui.png">

30
frames/Aidon-Sweden.raw Normal file
View File

@@ -0,0 +1,30 @@
7E A2 43 41 08 83 13 85 EB E6 E7 00 0F 40 00 00 00 00
01 1B
02 02 09 06 00 00 01 00 00 FF 09 0C 07 E5 0C 0A 05 10 39 00 FF 80 00 FF
02 03 09 06 01 00 01 07 00 FF 06 00 00 07 E5 02 02 0F 00 16 1B
02 03 09 06 01 00 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1B
02 03 09 06 01 00 03 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1D
02 03 09 06 01 00 04 07 00 FF 06 00 00 02 48 02 02 0F 00 16 1D
02 03 09 06 01 00 1F 07 00 FF 10 00 09 02 02 0F FF 16 21
02 03 09 06 01 00 33 07 00 FF 10 00 25 02 02 0F FF 16 21
02 03 09 06 01 00 47 07 00 FF 10 00 2E 02 02 0F FF 16 21
02 03 09 06 01 00 20 07 00 FF 12 08 E3 02 02 0F FF 16 23
02 03 09 06 01 00 34 07 00 FF 12 08 D8 02 02 0F FF 16 23
02 03 09 06 01 00 48 07 00 FF 12 08 DF 02 02 0F FF 16 23
02 03 09 06 01 00 15 07 00 FF 06 00 00 00 D5 02 02 0F 00 16 1B
02 03 09 06 01 00 16 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1B
02 03 09 06 01 00 17 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1D
02 03 09 06 01 00 18 07 00 FF 06 00 00 00 36 02 02 0F 00 16 1D
02 03 09 06 01 00 29 07 00 FF 06 00 00 03 0C 02 02 0F 00 16 1B
02 03 09 06 01 00 2A 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1B
02 03 09 06 01 00 2B 07 00 FF 06 00 00 01 21 02 02 0F 00 16 1D
02 03 09 06 01 00 2C 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1D
02 03 09 06 01 00 3D 07 00 FF 06 00 00 03 F9 02 02 0F 00 16 1B
02 03 09 06 01 00 3E 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1B
02 03 09 06 01 00 3F 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1D
02 03 09 06 01 00 40 07 00 FF 06 00 00 00 E9 02 02 0F 00 16 1D
02 03 09 06 01 00 01 08 00 FF 06 03 C2 5A 64 02 02 0F 00 16 1E
02 03 09 06 01 00 02 08 00 FF 06 00 00 00 00 02 02 0F 00 16 1E
02 03 09 06 01 00 03 08 00 FF 06 00 04 5D 06 02 02 0F 00 16 20
02 03 09 06 01 00 04 08 00 FF 06 00 B4 9D 89 02 02 0F 00 16 20
1C 90 7E

View File

@@ -2,7 +2,7 @@
extra_configs = platformio-user.ini extra_configs = platformio-user.ini
[common] [common]
lib_deps = file://lib/Timezone, 256dpi/MQTT@2.5.0, OneWireNg@0.10.0, DallasTemperature@3.9.1, EspSoftwareSerial@6.14.1, https://github.com/gskjold/RemoteDebug.git, Time@1.6.0 lib_deps = Timezone@1.2.4, 256dpi/MQTT@2.5.0, OneWireNg@0.10.0, DallasTemperature@3.9.1, EspSoftwareSerial@6.14.1, https://github.com/gskjold/RemoteDebug.git, Time@1.6.1
lib_ignore = OneWire lib_ignore = OneWire
[env:esp8266] [env:esp8266]

View File

@@ -571,6 +571,14 @@ bool AmsConfiguration::hasConfig() {
configVersion = 0; configVersion = 0;
return false; return false;
} }
case 90:
configVersion = -1; // Prevent loop
if(relocateConfig90()) {
configVersion = 91;
} else {
configVersion = 0;
return false;
}
case EEPROM_CHECK_SUM: case EEPROM_CHECK_SUM:
return true; return true;
default: default:
@@ -831,6 +839,17 @@ bool AmsConfiguration::relocateConfig89() {
return ret; return ret;
} }
bool AmsConfiguration::relocateConfig90() {
EntsoeConfig entsoe;
EEPROM.begin(EEPROM_SIZE);
EEPROM.get(CONFIG_ENTSOE_START_90, entsoe);
EEPROM.put(CONFIG_ENTSOE_START, entsoe);
EEPROM.put(EEPROM_CONFIG_ADDRESS, 91);
bool ret = EEPROM.commit();
EEPROM.end();
return ret;
}
bool AmsConfiguration::save() { bool AmsConfiguration::save() {
EEPROM.begin(EEPROM_SIZE); EEPROM.begin(EEPROM_SIZE);
EEPROM.put(EEPROM_CONFIG_ADDRESS, EEPROM_CHECK_SUM); EEPROM.put(EEPROM_CONFIG_ADDRESS, EEPROM_CHECK_SUM);
@@ -847,6 +866,8 @@ uint8_t AmsConfiguration::getTempSensorCount() {
} }
TempSensorConfig* AmsConfiguration::getTempSensorConfig(uint8_t address[8]) { TempSensorConfig* AmsConfiguration::getTempSensorConfig(uint8_t address[8]) {
if(tempSensors == NULL)
return NULL;
for(int x = 0; x < tempSensorCount; x++) { for(int x = 0; x < tempSensorCount; x++) {
TempSensorConfig *conf = tempSensors[x]; TempSensorConfig *conf = tempSensors[x];
if(isSensorAddressEqual(conf->address, address)) { if(isSensorAddressEqual(conf->address, address)) {
@@ -858,14 +879,16 @@ TempSensorConfig* AmsConfiguration::getTempSensorConfig(uint8_t address[8]) {
void AmsConfiguration::updateTempSensorConfig(uint8_t address[8], const char name[32], bool common) { void AmsConfiguration::updateTempSensorConfig(uint8_t address[8], const char name[32], bool common) {
bool found = false; bool found = false;
for(int x = 0; x < tempSensorCount; x++) { if(tempSensors != NULL) {
TempSensorConfig *data = tempSensors[x]; for(int x = 0; x < tempSensorCount; x++) {
if(isSensorAddressEqual(data->address, address)) { TempSensorConfig *data = tempSensors[x];
found = true; if(isSensorAddressEqual(data->address, address)) {
strcpy(data->name, name); found = true;
data->common = common; strcpy(data->name, name);
} data->common = common;
} }
}
}
if(!found) { if(!found) {
TempSensorConfig** tempSensors = new TempSensorConfig*[tempSensorCount+1]; TempSensorConfig** tempSensors = new TempSensorConfig*[tempSensorCount+1];
if(this->tempSensors != NULL) { if(this->tempSensors != NULL) {
@@ -1013,13 +1036,13 @@ void AmsConfiguration::print(Print* debugger)
debugger->printf("Temp analog pin: %i\r\n", gpio.tempAnalogSensorPin); debugger->printf("Temp analog pin: %i\r\n", gpio.tempAnalogSensorPin);
debugger->printf("Vcc pin: %i\r\n", gpio.vccPin); debugger->printf("Vcc pin: %i\r\n", gpio.vccPin);
if(gpio.vccMultiplier > 0) { if(gpio.vccMultiplier > 0) {
debugger->printf("Vcc multiplier: %f\r\n", gpio.vccMultiplier / 1000.0); debugger->printf("Vcc multiplier: %f\r\n", gpio.vccMultiplier / 1000.0);
} }
if(gpio.vccOffset > 0) { if(gpio.vccOffset > 0) {
debugger->printf("Vcc offset: %f\r\n", gpio.vccOffset / 100.0); debugger->printf("Vcc offset: %f\r\n", gpio.vccOffset / 100.0);
} }
if(gpio.vccBootLimit > 0) { if(gpio.vccBootLimit > 0) {
debugger->printf("Vcc boot limit: %f\r\n", gpio.vccBootLimit / 10.0); debugger->printf("Vcc boot limit: %f\r\n", gpio.vccBootLimit / 10.0);
} }
debugger->printf("GND resistor: %i\r\n", gpio.vccResistorGnd); debugger->printf("GND resistor: %i\r\n", gpio.vccResistorGnd);
debugger->printf("Vcc resistor: %i\r\n", gpio.vccResistorVcc); debugger->printf("Vcc resistor: %i\r\n", gpio.vccResistorVcc);

View File

@@ -4,7 +4,7 @@
#include "Arduino.h" #include "Arduino.h"
#define EEPROM_SIZE 1024*3 #define EEPROM_SIZE 1024*3
#define EEPROM_CHECK_SUM 90 // Used to check if config is stored. Change if structure changes #define EEPROM_CHECK_SUM 91 // Used to check if config is stored. Change if structure changes
#define EEPROM_CONFIG_ADDRESS 0 #define EEPROM_CONFIG_ADDRESS 0
#define EEPROM_TEMP_CONFIG_ADDRESS 2048 #define EEPROM_TEMP_CONFIG_ADDRESS 2048
@@ -12,7 +12,7 @@
#define CONFIG_WIFI_START 16 #define CONFIG_WIFI_START 16
#define CONFIG_METER_START 224 #define CONFIG_METER_START 224
#define CONFIG_GPIO_START 266 #define CONFIG_GPIO_START 266
#define CONFIG_ENTSOE_START 286 #define CONFIG_ENTSOE_START 290
#define CONFIG_WEB_START 648 #define CONFIG_WEB_START 648
#define CONFIG_DEBUG_START 824 #define CONFIG_DEBUG_START 824
#define CONFIG_DOMOTICZ_START 856 #define CONFIG_DOMOTICZ_START 856
@@ -23,6 +23,7 @@
#define CONFIG_METER_START_87 784 #define CONFIG_METER_START_87 784
#define CONFIG_GPIO_START_88 832 #define CONFIG_GPIO_START_88 832
#define CONFIG_ENTSOE_START_89 944 #define CONFIG_ENTSOE_START_89 944
#define CONFIG_ENTSOE_START_90 286
struct SystemConfig { struct SystemConfig {
@@ -318,13 +319,14 @@ private:
bool wifiChanged, mqttChanged, meterChanged = true, domoChanged, ntpChanged = true, entsoeChanged = false; bool wifiChanged, mqttChanged, meterChanged = true, domoChanged, ntpChanged = true, entsoeChanged = false;
uint8_t tempSensorCount = 0; uint8_t tempSensorCount = 0;
TempSensorConfig** tempSensors; TempSensorConfig** tempSensors = NULL;
bool loadConfig83(int address); bool loadConfig83(int address);
bool relocateConfig86(); bool relocateConfig86();
bool relocateConfig87(); bool relocateConfig87();
bool relocateConfig88(); // dev 1.6 bool relocateConfig88(); // dev 1.6
bool relocateConfig89(); // dev 1.6 bool relocateConfig89(); // dev 1.6
bool relocateConfig90(); // 2.0.0
int readString(int pAddress, char* pString[]); int readString(int pAddress, char* pString[]);
int readInt(int pAddress, int *pValue); int readInt(int pAddress, int *pValue);

View File

@@ -7,22 +7,35 @@ void AmsData::apply(AmsData& other) {
unsigned long ms = this->lastUpdateMillis > other.getLastUpdateMillis() ? 0 : other.getLastUpdateMillis() - this->lastUpdateMillis; unsigned long ms = this->lastUpdateMillis > other.getLastUpdateMillis() ? 0 : other.getLastUpdateMillis() - this->lastUpdateMillis;
if(ms > 0) { if(ms > 0) {
if(other.getActiveImportPower() > 0) if(other.getActiveImportPower() > 0) {
activeImportCounter += (((float) ms) * other.getActiveImportPower()) / 3600000000; float add = other.getActiveImportPower() * (((float) ms) / 3600000.0);
activeImportCounter += add / 1000.0;
//Serial.printf("%dW, %dms, %.6fkWh added\n", other.getActiveImportPower(), ms, add);
}
if(other.getListType() > 1) { if(other.getListType() > 1) {
if(other.getActiveExportPower() > 0) ms = this->lastUpdateMillis > other.getLastUpdateMillis() ? 0 : other.getLastUpdateMillis() - this->lastList2;
activeExportCounter += (((float) ms*2) * other.getActiveExportPower()) / 3600000000; if(other.getActiveExportPower() > 0) {
if(other.getReactiveImportPower() > 0) float add = other.getActiveExportPower() * (((float) ms) / 3600000.0);
reactiveImportCounter += (((float) ms*2) * other.getReactiveImportPower()) / 3600000000; activeExportCounter += add / 1000.0;
if(other.getReactiveExportPower() > 0) }
reactiveExportCounter += (((float) ms*2) * other.getReactiveExportPower()) / 3600000000; if(other.getReactiveImportPower() > 0) {
float add = other.getReactiveImportPower() * (((float) ms) / 3600000.0);
reactiveImportCounter += add / 1000.0;
}
if(other.getReactiveExportPower() > 0) {
float add = other.getReactiveExportPower() * (((float) ms) / 3600000.0);
reactiveExportCounter += add / 1000.0;
}
} }
counterEstimated = true; counterEstimated = true;
} }
} }
this->lastUpdateMillis = other.getLastUpdateMillis(); this->lastUpdateMillis = other.getLastUpdateMillis();
if(other.getListType() > 1) {
this->lastList2 = this->lastUpdateMillis;
}
this->packageTimestamp = other.getPackageTimestamp(); this->packageTimestamp = other.getPackageTimestamp();
if(other.getListType() > this->listType) if(other.getListType() > this->listType)
this->listType = other.getListType(); this->listType = other.getListType();

View File

@@ -63,6 +63,7 @@ public:
protected: protected:
unsigned long lastUpdateMillis = 0; unsigned long lastUpdateMillis = 0;
unsigned long lastList2 = 0;
uint8_t listType = 0, meterType = AmsTypeUnknown; uint8_t listType = 0, meterType = AmsTypeUnknown;
time_t packageTimestamp = 0; time_t packageTimestamp = 0;
String listId, meterId, meterModel; String listId, meterId, meterModel;

View File

@@ -343,17 +343,6 @@ void loop() {
} }
} }
if(now - lastTemperatureRead > 15000) {
unsigned long start = millis();
hw.updateTemperatures();
lastTemperatureRead = now;
if(mqtt != NULL && mqttHandler != NULL && WiFi.getMode() != WIFI_AP && WiFi.status() == WL_CONNECTED && mqtt->connected() && !topic.isEmpty()) {
mqttHandler->publishTemperatures(&config, &hw);
}
debugD("Used %d ms to update temperature", millis()-start);
}
// Only do normal stuff if we're not booted as AP // Only do normal stuff if we're not booted as AP
if (WiFi.getMode() != WIFI_AP) { if (WiFi.getMode() != WIFI_AP) {
if (WiFi.status() != WL_CONNECTED) { if (WiFi.status() != WL_CONNECTED) {
@@ -482,7 +471,18 @@ void loop() {
hc = NULL; hc = NULL;
} }
readHanPort(); if(readHanPort() || now - meterState.getLastUpdateMillis() > 30000) {
if(now - lastTemperatureRead > 15000) {
unsigned long start = millis();
hw.updateTemperatures();
lastTemperatureRead = now;
if(mqtt != NULL && mqttHandler != NULL && WiFi.getMode() != WIFI_AP && WiFi.status() == WL_CONNECTED && mqtt->connected() && !topic.isEmpty()) {
mqttHandler->publishTemperatures(&config, &hw);
}
debugD("Used %d ms to update temperature", millis()-start);
}
}
delay(1); // Needed for auto modem sleep delay(1); // Needed for auto modem sleep
} }
@@ -498,9 +498,13 @@ void setupHanPort(uint8_t pin, uint32_t baud, uint8_t parityOrdinal, bool invert
if(pin == 9) { if(pin == 9) {
hwSerial = &Serial1; hwSerial = &Serial1;
} }
if(pin == 16) { #if defined(CONFIG_IDF_TARGET_ESP32)
hwSerial = &Serial2; if(pin == 16) {
} hwSerial = &Serial2;
}
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
#endif
#endif #endif
if(pin == 0) { if(pin == 0) {
@@ -656,20 +660,20 @@ void swapWifiMode() {
int len = 0; int len = 0;
uint8_t buf[BUF_SIZE]; uint8_t buf[BUF_SIZE];
int currentMeterType = -1; int currentMeterType = -1;
void readHanPort() { bool readHanPort() {
if(!hanSerial->available()) return; if(!hanSerial->available()) return false;
if(currentMeterType == -1) { if(currentMeterType == -1) {
hanSerial->readBytes(buf, BUF_SIZE); hanSerial->readBytes(buf, BUF_SIZE);
currentMeterType = 0; currentMeterType = 0;
return; return false;
} }
if(currentMeterType == 0) { if(currentMeterType == 0) {
uint8_t flag = hanSerial->read(); uint8_t flag = hanSerial->read();
if(flag == 0x7E || flag == 0x68) currentMeterType = 1; if(flag == 0x7E || flag == 0x68) currentMeterType = 1;
else currentMeterType = 2; else currentMeterType = 2;
hanSerial->readBytes(buf, BUF_SIZE); hanSerial->readBytes(buf, BUF_SIZE);
return; return false;
} }
CosemDateTime timestamp = {0}; CosemDateTime timestamp = {0};
AmsData data; AmsData data;
@@ -683,11 +687,11 @@ void readHanPort() {
hanSerial->readBytes(buf, BUF_SIZE); hanSerial->readBytes(buf, BUF_SIZE);
len = 0; len = 0;
debugI("Buffer overflow, resetting"); debugI("Buffer overflow, resetting");
return; return false;
} }
int pos = HDLC_validate((uint8_t *) buf, len, hc, &timestamp); int pos = HDLC_validate((uint8_t *) buf, len, hc, &timestamp);
if(pos == HDLC_FRAME_INCOMPLETE) { if(pos == HDLC_FRAME_INCOMPLETE) {
return; return false;
} }
for(int i = len; i<BUF_SIZE; i++) { for(int i = len; i<BUF_SIZE; i++) {
buf[i] = 0x00; buf[i] = 0x00;
@@ -719,16 +723,17 @@ void readHanPort() {
} else { } else {
debugW("Invalid HDLC, returned with %d", pos); debugW("Invalid HDLC, returned with %d", pos);
currentMeterType = 0; currentMeterType = 0;
return; return false;
} }
} else { } else {
return; return false;
} }
} else if(currentMeterType == 2) { } else if(currentMeterType == 2) {
String payload = hanSerial->readString(); String payload = hanSerial->readString();
data = IEC6205621(payload); data = IEC6205621(payload);
if(data.getListType() == 0) { if(data.getListType() == 0) {
currentMeterType = 1; currentMeterType = 1;
return false;
} else { } else {
if(Debug.isActive(RemoteDebug::DEBUG)) { if(Debug.isActive(RemoteDebug::DEBUG)) {
debugD("Frame dump: %d", payload.length()); debugD("Frame dump: %d", payload.length());
@@ -781,6 +786,7 @@ void readHanPort() {
} }
} }
delay(1); delay(1);
return true;
} }
void debugPrint(byte *buffer, int start, int length) { void debugPrint(byte *buffer, int start, int length) {

View File

@@ -17,7 +17,7 @@ void HwTools::setup(GpioConfig* config, AmsConfiguration* amsConf) {
if(config->vccPin > 0 && config->vccPin < 40) { if(config->vccPin > 0 && config->vccPin < 40) {
getAdcChannel(config->vccPin, voltAdc); getAdcChannel(config->vccPin, voltAdc);
if(voltAdc.unit != 0xFF) { if(voltAdc.unit != 0xFF) {
#if defined(ESP32) #if defined(CONFIG_IDF_TARGET_ESP32)
if(voltAdc.unit == ADC_UNIT_1) { if(voltAdc.unit == ADC_UNIT_1) {
voltAdcChar = (esp_adc_cal_characteristics_t*) calloc(1, sizeof(esp_adc_cal_characteristics_t)); 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); 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);
@@ -156,13 +156,7 @@ void HwTools::getAdcChannel(uint8_t pin, AdcConfig& config) {
double HwTools::getVcc() { double HwTools::getVcc() {
double volts = 0.0; double volts = 0.0;
if(config->vccPin != 0xFF) { if(config->vccPin != 0xFF) {
#if defined(ESP8266) #if defined(CONFIG_IDF_TARGET_ESP32)
uint32_t x = 0;
for (int i = 0; i < 10; i++) {
x += analogRead(config->vccPin);
}
volts = x / 10240;
#elif defined(ESP32)
if(voltAdc.unit != 0xFF) { if(voltAdc.unit != 0xFF) {
uint32_t x = 0; uint32_t x = 0;
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
@@ -184,6 +178,12 @@ double HwTools::getVcc() {
} }
volts = x / 40950; volts = x / 40950;
} }
#else
uint32_t x = 0;
for (int i = 0; i < 10; i++) {
x += analogRead(config->vccPin);
}
volts = x / 10240;
#endif #endif
} else { } else {
#if defined(ESP8266) #if defined(ESP8266)

View File

@@ -7,7 +7,7 @@ IEC6205621::IEC6205621(String payload) {
lastUpdateMillis = millis(); lastUpdateMillis = millis();
listId = payload.substring(payload.startsWith("/") ? 1 : 0, payload.indexOf("\n")); listId = payload.substring(payload.startsWith("/") ? 1 : 0, payload.indexOf("\n"));
if(listId.startsWith("ADN")) { if(listId.startsWith("ADN")) {
meterType == AmsTypeAidon; meterType = AmsTypeAidon;
listId = listId.substring(0,4); listId = listId.substring(0,4);
} else if(listId.startsWith("KFM")) { } else if(listId.startsWith("KFM")) {
meterType = AmsTypeKaifa; meterType = AmsTypeKaifa;

View File

@@ -3,14 +3,14 @@
#include "Timezone.h" #include "Timezone.h"
IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, CosemDateTime packageTimestamp) { IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, CosemDateTime packageTimestamp) {
uint32_t u32; uint32_t ui;
int32_t s32; double val;
char str[64]; char str[64];
this->packageTimestamp = getTimestamp(packageTimestamp); this->packageTimestamp = getTimestamp(packageTimestamp);
u32 = getUnsignedNumber(AMS_OBIS_ACTIVE_IMPORT, sizeof(AMS_OBIS_ACTIVE_IMPORT), ((char *) (d))); ui = getNumber(AMS_OBIS_ACTIVE_IMPORT, sizeof(AMS_OBIS_ACTIVE_IMPORT), ((char *) (d)));
if(u32 == 0xFFFFFFFF) { if(ui == 0xFFFFFFFF) {
CosemData* data = getCosemDataAt(1, ((char *) (d))); CosemData* data = getCosemDataAt(1, ((char *) (d)));
// Kaifa special case... // Kaifa special case...
@@ -124,7 +124,7 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, CosemDateTime packag
// Kaifa end // Kaifa end
} else { } else {
listType = 1; listType = 1;
activeImportPower = u32; activeImportPower = ui;
meterType = AmsTypeUnknown; meterType = AmsTypeUnknown;
CosemData* version = findObis(AMS_OBIS_VERSION, sizeof(AMS_OBIS_VERSION), d); CosemData* version = findObis(AMS_OBIS_VERSION, sizeof(AMS_OBIS_VERSION), d);
@@ -151,119 +151,95 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, CosemDateTime packag
this->packageTimestamp = this->packageTimestamp > 0 ? tz.toUTC(this->packageTimestamp) : 0; this->packageTimestamp = this->packageTimestamp > 0 ? tz.toUTC(this->packageTimestamp) : 0;
} }
u32 = getString(AMS_OBIS_VERSION, sizeof(AMS_OBIS_VERSION), ((char *) (d)), str); ui = getString(AMS_OBIS_VERSION, sizeof(AMS_OBIS_VERSION), ((char *) (d)), str);
if(u32 > 0) { if(ui > 0) {
listId = String(str); listId = String(str);
} }
u32 = getUnsignedNumber(AMS_OBIS_ACTIVE_EXPORT, sizeof(AMS_OBIS_ACTIVE_EXPORT), ((char *) (d))); ui = getNumber(AMS_OBIS_ACTIVE_EXPORT, sizeof(AMS_OBIS_ACTIVE_EXPORT), ((char *) (d)));
if(u32 != 0xFFFFFFFF) { if(ui != 0xFFFFFFFF) {
activeExportPower = u32; activeExportPower = ui;
} }
u32 = getUnsignedNumber(AMS_OBIS_REACTIVE_IMPORT, sizeof(AMS_OBIS_REACTIVE_IMPORT), ((char *) (d))); ui = getNumber(AMS_OBIS_REACTIVE_IMPORT, sizeof(AMS_OBIS_REACTIVE_IMPORT), ((char *) (d)));
if(u32 != 0xFFFFFFFF) { if(ui != 0xFFFFFFFF) {
reactiveImportPower = u32; reactiveImportPower = ui;
} }
u32 = getUnsignedNumber(AMS_OBIS_REACTIVE_EXPORT, sizeof(AMS_OBIS_REACTIVE_EXPORT), ((char *) (d))); ui = getNumber(AMS_OBIS_REACTIVE_EXPORT, sizeof(AMS_OBIS_REACTIVE_EXPORT), ((char *) (d)));
if(u32 != 0xFFFFFFFF) { if(ui != 0xFFFFFFFF) {
reactiveExportPower = u32; reactiveExportPower = ui;
} }
u32 = getUnsignedNumber(AMS_OBIS_VOLTAGE_L1, sizeof(AMS_OBIS_VOLTAGE_L1), ((char *) (d))); val = getNumber(AMS_OBIS_VOLTAGE_L1, sizeof(AMS_OBIS_VOLTAGE_L1), ((char *) (d)));
if(u32 != 0xFFFFFFFF) { if(val != 0xFFFFFFFF) {
listType = 2; listType = 2;
l1voltage = u32; l1voltage = val;
} }
u32 = getUnsignedNumber(AMS_OBIS_VOLTAGE_L2, sizeof(AMS_OBIS_VOLTAGE_L2), ((char *) (d))); val = getNumber(AMS_OBIS_VOLTAGE_L2, sizeof(AMS_OBIS_VOLTAGE_L2), ((char *) (d)));
if(u32 != 0xFFFFFFFF) { if(val != 0xFFFFFFFF) {
listType = 2; listType = 2;
l2voltage = u32; l2voltage = val;
} }
u32 = getUnsignedNumber(AMS_OBIS_VOLTAGE_L3, sizeof(AMS_OBIS_VOLTAGE_L3), ((char *) (d))); val = getNumber(AMS_OBIS_VOLTAGE_L3, sizeof(AMS_OBIS_VOLTAGE_L3), ((char *) (d)));
if(u32 != 0xFFFFFFFF) { if(val != 0xFFFFFFFF) {
listType = 2; listType = 2;
l3voltage = u32; l3voltage = val;
} }
s32 = getSignedNumber(AMS_OBIS_CURRENT_L1, sizeof(AMS_OBIS_CURRENT_L1), ((char *) (d))); val = getNumber(AMS_OBIS_CURRENT_L1, sizeof(AMS_OBIS_CURRENT_L1), ((char *) (d)));
if(s32 != 0xFFFFFFFF) { if(val != 0xFFFFFFFF) {
listType = 2; listType = 2;
l1current = s32; l1current = val;
} }
s32 = getSignedNumber(AMS_OBIS_CURRENT_L2, sizeof(AMS_OBIS_CURRENT_L2), ((char *) (d))); val = getNumber(AMS_OBIS_CURRENT_L2, sizeof(AMS_OBIS_CURRENT_L2), ((char *) (d)));
if(s32 != 0xFFFFFFFF) { if(val != 0xFFFFFFFF) {
listType = 2; listType = 2;
l2current = s32; l2current = val;
} }
s32 = getSignedNumber(AMS_OBIS_CURRENT_L3, sizeof(AMS_OBIS_CURRENT_L3), ((char *) (d))); val = getNumber(AMS_OBIS_CURRENT_L3, sizeof(AMS_OBIS_CURRENT_L3), ((char *) (d)));
if(s32 != 0xFFFFFFFF) { if(val != 0xFFFFFFFF) {
listType = 2; listType = 2;
l3current = s32; l3current = val;
} }
if(listType == 2) { val = getNumber(AMS_OBIS_ACTIVE_IMPORT_COUNT, sizeof(AMS_OBIS_ACTIVE_IMPORT_COUNT), ((char *) (d)));
int vdiv = 1; if(val != 0xFFFFFFFF) {
int voltage = l1voltage == 0 ? l2voltage == 0 ? l3voltage == 0 ? 0 : l3voltage : l2voltage : l1voltage;
while(voltage > 1000) {
vdiv *= 10;
voltage /= 10;
}
l1voltage = l1voltage != 0 ? l1voltage / vdiv : 0;
l2voltage = l2voltage != 0 ? l2voltage / vdiv : 0;
l3voltage = l3voltage != 0 ? l3voltage / vdiv : 0;
int adiv = 1;
int watt = (l1voltage * l1current) + (l2voltage * l2current) + (l3voltage * l3current);
while(activeImportPower > 0 && watt / activeImportPower > 2) {
adiv *= 10;
watt /= 10;
}
l1current = l1current != 0 ? l1current / adiv : 0;
l2current = l2current != 0 ? l2current / adiv : 0;
l3current = l3current != 0 ? l3current / adiv : 0;
}
u32 = getUnsignedNumber(AMS_OBIS_ACTIVE_IMPORT_COUNT, sizeof(AMS_OBIS_ACTIVE_IMPORT_COUNT), ((char *) (d)));
if(u32 != 0xFFFFFFFF) {
listType = 3; listType = 3;
activeImportCounter = u32 / 100.0; activeImportCounter = val / 1000.0;
} }
u32 = getUnsignedNumber(AMS_OBIS_ACTIVE_EXPORT_COUNT, sizeof(AMS_OBIS_ACTIVE_EXPORT_COUNT), ((char *) (d))); val = getNumber(AMS_OBIS_ACTIVE_EXPORT_COUNT, sizeof(AMS_OBIS_ACTIVE_EXPORT_COUNT), ((char *) (d)));
if(u32 != 0xFFFFFFFF) { if(val != 0xFFFFFFFF) {
listType = 3; listType = 3;
activeExportCounter = u32 / 100.0; activeExportCounter = val / 1000.0;
} }
u32 = getUnsignedNumber(AMS_OBIS_REACTIVE_IMPORT_COUNT, sizeof(AMS_OBIS_REACTIVE_IMPORT_COUNT), ((char *) (d))); val = getNumber(AMS_OBIS_REACTIVE_IMPORT_COUNT, sizeof(AMS_OBIS_REACTIVE_IMPORT_COUNT), ((char *) (d)));
if(u32 != 0xFFFFFFFF) { if(val != 0xFFFFFFFF) {
listType = 3; listType = 3;
reactiveImportCounter = u32 / 100.0; reactiveImportCounter = val / 1000.0;
} }
u32 = getUnsignedNumber(AMS_OBIS_REACTIVE_EXPORT_COUNT, sizeof(AMS_OBIS_REACTIVE_EXPORT_COUNT), ((char *) (d))); val = getNumber(AMS_OBIS_REACTIVE_EXPORT_COUNT, sizeof(AMS_OBIS_REACTIVE_EXPORT_COUNT), ((char *) (d)));
if(u32 != 0xFFFFFFFF) { if(val != 0xFFFFFFFF) {
listType = 3; listType = 3;
reactiveExportCounter = u32 / 100.0; reactiveExportCounter = val / 1000.0;
} }
u32 = getString(AMS_OBIS_METER_MODEL, sizeof(AMS_OBIS_METER_MODEL), ((char *) (d)), str); ui = getString(AMS_OBIS_METER_MODEL, sizeof(AMS_OBIS_METER_MODEL), ((char *) (d)), str);
if(u32 > 0) { if(ui > 0) {
meterModel = String(str); meterModel = String(str);
} else { } else {
u32 = getString(AMS_OBIS_METER_MODEL_2, sizeof(AMS_OBIS_METER_MODEL_2), ((char *) (d)), str); ui = getString(AMS_OBIS_METER_MODEL_2, sizeof(AMS_OBIS_METER_MODEL_2), ((char *) (d)), str);
if(u32 > 0) { if(ui > 0) {
meterModel = String(str); meterModel = String(str);
} }
} }
u32 = getString(AMS_OBIS_METER_ID, sizeof(AMS_OBIS_METER_ID), ((char *) (d)), str); ui = getString(AMS_OBIS_METER_ID, sizeof(AMS_OBIS_METER_ID), ((char *) (d)), str);
if(u32 > 0) { if(ui > 0) {
meterId = String(str); meterId = String(str);
} else { } else {
u32 = getString(AMS_OBIS_METER_ID_2, sizeof(AMS_OBIS_METER_ID_2), ((char *) (d)), str); ui = getString(AMS_OBIS_METER_ID_2, sizeof(AMS_OBIS_METER_ID_2), ((char *) (d)), str);
if(u32 > 0) { if(ui > 0) {
meterId = String(str); meterId = String(str);
} }
} }
@@ -279,21 +255,35 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, CosemDateTime packag
} }
} }
u32 = getUnsignedNumber(AMS_OBIS_POWER_FACTOR, sizeof(AMS_OBIS_POWER_FACTOR), ((char *) (d))); val = getNumber(AMS_OBIS_POWER_FACTOR, sizeof(AMS_OBIS_POWER_FACTOR), ((char *) (d)));
if(u32 != 0xFFFFFFFF) { if(val != 0xFFFFFFFF) {
powerFactor = u32 / 100.0; powerFactor = val;
} }
u32 = getUnsignedNumber(AMS_OBIS_POWER_FACTOR_L1, sizeof(AMS_OBIS_POWER_FACTOR_L1), ((char *) (d))); val = getNumber(AMS_OBIS_POWER_FACTOR_L1, sizeof(AMS_OBIS_POWER_FACTOR_L1), ((char *) (d)));
if(u32 != 0xFFFFFFFF) { if(val != 0xFFFFFFFF) {
l1PowerFactor = u32 / 100.0; l1PowerFactor = val;
} }
u32 = getUnsignedNumber(AMS_OBIS_POWER_FACTOR_L2, sizeof(AMS_OBIS_POWER_FACTOR_L2), ((char *) (d))); val = getNumber(AMS_OBIS_POWER_FACTOR_L2, sizeof(AMS_OBIS_POWER_FACTOR_L2), ((char *) (d)));
if(u32 != 0xFFFFFFFF) { if(val != 0xFFFFFFFF) {
l2PowerFactor = u32 / 100.0; l2PowerFactor = val;
} }
u32 = getUnsignedNumber(AMS_OBIS_POWER_FACTOR_L3, sizeof(AMS_OBIS_POWER_FACTOR_L3), ((char *) (d))); val = getNumber(AMS_OBIS_POWER_FACTOR_L3, sizeof(AMS_OBIS_POWER_FACTOR_L3), ((char *) (d)));
if(u32 != 0xFFFFFFFF) { if(val != 0xFFFFFFFF) {
l3PowerFactor = u32 / 100.0; l3PowerFactor = val;
}
if(meterType == AmsTypeKamstrup) {
activeImportCounter *= 10;
activeExportCounter *= 10;
reactiveImportCounter *= 10;
reactiveExportCounter *= 10;
l1current /= 100;
l2current /= 100;
l3current /= 100;
powerFactor /= 100;
l1PowerFactor /= 100;
l2PowerFactor /= 100;
l3PowerFactor /= 100;
} }
lastUpdateMillis = millis(); lastUpdateMillis = millis();
@@ -403,32 +393,35 @@ uint8_t IEC6205675::getString(uint8_t* obis, int matchlength, const char* ptr, c
return 0; return 0;
} }
uint32_t IEC6205675::getSignedNumber(uint8_t* obis, int matchlength, const char* ptr) { double IEC6205675::getNumber(uint8_t* obis, int matchlength, const char* ptr) {
CosemData* item = findObis(obis, matchlength, ptr); CosemData* item = findObis(obis, matchlength, ptr);
if(item != NULL) { return getNumber(item);
switch(item->base.type) {
case CosemTypeLongUnsigned:
return ntohs(item->lu.data);
case CosemTypeDLongUnsigned:
return ntohl(item->dlu.data);
case CosemTypeLongSigned:
return ntohs(item->lu.data);
}
}
return 0xFFFFFFFF;
} }
uint32_t IEC6205675::getUnsignedNumber(uint8_t* obis, int matchlength, const char* ptr) { double IEC6205675::getNumber(CosemData* item) {
CosemData* item = findObis(obis, matchlength, ptr); double val = 0xFFFFFFFF;
if(item != NULL) { if(item != NULL) {
char* pos = ((char*) item);
switch(item->base.type) { switch(item->base.type) {
case CosemTypeLongUnsigned: case CosemTypeLongUnsigned:
return ntohs(item->lu.data); val = ntohs(item->lu.data);
pos += 3;
break;
case CosemTypeDLongUnsigned: case CosemTypeDLongUnsigned:
return ntohl(item->dlu.data); val = ntohl(item->dlu.data);
pos += 5;
break;
case CosemTypeLongSigned:
val = ntohs(item->lu.data);
pos += 3;
break;
}
if(*pos++ == 0x02 && *pos++ == 0x02) {
int8_t scale = *++pos;
val *= pow(10, scale);
} }
} }
return 0xFFFFFFFF; return val;
} }
time_t IEC6205675::getTimestamp(uint8_t* obis, int matchlength, const char* ptr) { time_t IEC6205675::getTimestamp(uint8_t* obis, int matchlength, const char* ptr) {

View File

@@ -17,8 +17,8 @@ private:
CosemData* getCosemDataAt(uint8_t index, const char* ptr); CosemData* getCosemDataAt(uint8_t index, const char* ptr);
CosemData* findObis(uint8_t* obis, int matchlength, const char* ptr); CosemData* findObis(uint8_t* obis, int matchlength, const char* ptr);
uint8_t getString(uint8_t* obis, int matchlength, const char* ptr, char* target); uint8_t getString(uint8_t* obis, int matchlength, const char* ptr, char* target);
uint32_t getSignedNumber(uint8_t* obis, int matchlength, const char* ptr); double getNumber(uint8_t* obis, int matchlength, const char* ptr);
uint32_t getUnsignedNumber(uint8_t* obis, int matchlength, const char* ptr); double getNumber(CosemData*);
time_t getTimestamp(uint8_t* obis, int matchlength, const char* ptr); time_t getTimestamp(uint8_t* obis, int matchlength, const char* ptr);
time_t getTimestamp(CosemDateTime timestamp); time_t getTimestamp(CosemDateTime timestamp);

View File

@@ -2,7 +2,7 @@
#include "HardwareSerial.h" #include "HardwareSerial.h"
EntsoeA44Parser::EntsoeA44Parser() { EntsoeA44Parser::EntsoeA44Parser() {
for(int i = 0; i < 24; i++) points[i] = 0.0; for(int i = 0; i < 24; i++) points[i] = ENTSOE_NO_VALUE;
} }
char* EntsoeA44Parser::getCurrency() { char* EntsoeA44Parser::getCurrency() {

View File

@@ -9,6 +9,8 @@
#define DOCPOS_POSITION 3 #define DOCPOS_POSITION 3
#define DOCPOS_AMOUNT 4 #define DOCPOS_AMOUNT 4
#define ENTSOE_NO_VALUE -127
class EntsoeA44Parser: public Stream { class EntsoeA44Parser: public Stream {
public: public:
EntsoeA44Parser(); EntsoeA44Parser();

View File

@@ -1,7 +1,7 @@
#include "EntsoeApi.h" #include "EntsoeApi.h"
#include <EEPROM.h> #include <EEPROM.h>
#include "Uptime.h" #include "Uptime.h"
#include "Time.h" #include "TimeLib.h"
#include "DnbCurrParser.h" #include "DnbCurrParser.h"
#if defined(ESP8266) #if defined(ESP8266)
@@ -93,14 +93,14 @@ bool EntsoeApi::loop() {
tmElements_t tm; tmElements_t tm;
breakTime(epoch, tm); breakTime(epoch, tm);
if(tm.Year > 50) { // Make sure we are in 2021 or later (years after 1970) if(tm.Year > 50) { // Make sure we are in 2021 or later (years after 1970)
uint64_t curDeviceMillis = millis64();
uint32_t curDayMillis = (((((tm.Hour * 60) + tm.Minute) * 60) + tm.Second) * 1000); uint32_t curDayMillis = (((((tm.Hour * 60) + tm.Minute) * 60) + tm.Second) * 1000);
midnightMillis = curDeviceMillis + (SECS_PER_DAY * 1000) - curDayMillis; midnightMillis = now + (SECS_PER_DAY * 1000) - curDayMillis + 1000; // Adding 1s to ensure we have passed midnight
printI("Setting midnight millis " + String((uint32_t) midnightMillis)); if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Setting midnight millis %lu\n", midnightMillis);
} }
} else if(now > midnightMillis) { } else if(now > midnightMillis) {
printI("Rotating price objects"); time_t t = time(nullptr);
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Rotating price objects at %lu\n", t);
delete today; delete today;
today = tomorrow; today = tomorrow;
tomorrow = NULL; tomorrow = NULL;
@@ -121,21 +121,21 @@ bool EntsoeApi::loop() {
d2.Year+1970, d2.Month, d2.Day, 23, 00, d2.Year+1970, d2.Month, d2.Day, 23, 00,
config->area, config->area); config->area, config->area);
printI("Fetching prices for today"); if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Fetching prices for today\n");
printD(url); if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) url: %s\n", url);
EntsoeA44Parser* a44 = new EntsoeA44Parser(); EntsoeA44Parser* a44 = new EntsoeA44Parser();
if(retrieve(url, a44)) { if(retrieve(url, a44) && a44->getPoint(0) != ENTSOE_NO_VALUE) {
today = a44; today = a44;
ret = true; ret = true;
} else { } else if(a44 != NULL) {
delete a44; delete a44;
today = NULL; today = NULL;
} }
} }
if(tomorrow == NULL if(tomorrow == NULL
&& midnightMillis - now < 39600000 && midnightMillis - now < 39600000 // Fetch 11hrs before midnight (13:00 CE(S)T)
&& (lastTomorrowFetch == 0 || now - lastTomorrowFetch > 60000) && (lastTomorrowFetch == 0 || now - lastTomorrowFetch > 300000) // Retry every 5min
) { ) {
lastTomorrowFetch = now; lastTomorrowFetch = now;
time_t e1 = time(nullptr); time_t e1 = time(nullptr);
@@ -151,13 +151,13 @@ bool EntsoeApi::loop() {
d2.Year+1970, d2.Month, d2.Day, 23, 00, d2.Year+1970, d2.Month, d2.Day, 23, 00,
config->area, config->area); config->area, config->area);
printI("Fetching prices for tomorrow"); if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Fetching prices for tomorrow\n");
printD(url); if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) url: %s\n", url);
EntsoeA44Parser* a44 = new EntsoeA44Parser(); EntsoeA44Parser* a44 = new EntsoeA44Parser();
if(retrieve(url, a44)) { if(retrieve(url, a44) && a44->getPoint(0) != ENTSOE_NO_VALUE) {
tomorrow = a44; tomorrow = a44;
ret = true; ret = true;
} else { } else if(a44 != NULL) {
delete a44; delete a44;
tomorrow = NULL; tomorrow = NULL;
} }
@@ -262,20 +262,6 @@ void EntsoeApi::printD(String fmt, ...) {
va_end(args); va_end(args);
} }
void EntsoeApi::printI(String fmt, ...) {
va_list args;
va_start(args, fmt);
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf(String("(EntsoeApi)" + fmt + "\n").c_str(), args);
va_end(args);
}
void EntsoeApi::printW(String fmt, ...) {
va_list args;
va_start(args, fmt);
if(debugger->isActive(RemoteDebug::WARNING)) debugger->printf(String("(EntsoeApi)" + fmt + "\n").c_str(), args);
va_end(args);
}
void EntsoeApi::printE(String fmt, ...) { void EntsoeApi::printE(String fmt, ...) {
va_list args; va_list args;
va_start(args, fmt); va_start(args, fmt);

View File

@@ -1,13 +1,12 @@
#ifndef _ENTSOEAPI_H #ifndef _ENTSOEAPI_H
#define _ENTSOEAPI_H #define _ENTSOEAPI_H
#include "time.h" #include "TimeLib.h"
#include "Timezone.h" #include "Timezone.h"
#include "RemoteDebug.h" #include "RemoteDebug.h"
#include "EntsoeA44Parser.h" #include "EntsoeA44Parser.h"
#include "AmsConfiguration.h" #include "AmsConfiguration.h"
#define ENTSOE_NO_VALUE -127
#define ENTSOE_DEFAULT_MULTIPLIER 1.00 #define ENTSOE_DEFAULT_MULTIPLIER 1.00
#define SSL_BUF_SIZE 512 #define SSL_BUF_SIZE 512
@@ -41,8 +40,6 @@ private:
float getCurrencyMultiplier(const char* from, const char* to); float getCurrencyMultiplier(const char* from, const char* to);
void printD(String fmt, ...); void printD(String fmt, ...);
void printI(String fmt, ...);
void printW(String fmt, ...);
void printE(String fmt, ...); void printE(String fmt, ...);
}; };
#endif #endif

View File

@@ -1,4 +1,4 @@
#include "hexutils.h"; #include "hexutils.h"
String toHex(uint8_t* in) { String toHex(uint8_t* in) {
return toHex(in, sizeof(in)*2); return toHex(in, sizeof(in)*2);

View File

@@ -4,6 +4,7 @@
#include "web/root/json1_json.h" #include "web/root/json1_json.h"
#include "web/root/json2_json.h" #include "web/root/json2_json.h"
#include "web/root/json3_json.h" #include "web/root/json3_json.h"
#include "web/root/json3pf_json.h"
#include "web/root/jsonsys_json.h" #include "web/root/jsonsys_json.h"
#include "web/root/jsonprices_json.h" #include "web/root/jsonprices_json.h"
@@ -50,35 +51,71 @@ bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState) {
); );
return mqtt->publish(topic, json); return mqtt->publish(topic, json);
} else if(data->getListType() == 3) { } else if(data->getListType() == 3) {
char json[512]; if(data->getPowerFactor() == 0) {
snprintf_P(json, sizeof(json), JSON3_JSON, char json[512];
WiFi.macAddress().c_str(), snprintf_P(json, sizeof(json), JSON3_JSON,
clientId.c_str(), WiFi.macAddress().c_str(),
(uint32_t) (millis64()/1000), clientId.c_str(),
data->getPackageTimestamp(), (uint32_t) (millis64()/1000),
hw->getVcc(), data->getPackageTimestamp(),
hw->getWifiRssi(), hw->getVcc(),
hw->getTemperature(), hw->getWifiRssi(),
data->getListId().c_str(), hw->getTemperature(),
data->getMeterId().c_str(), data->getListId().c_str(),
data->getMeterModel().c_str(), data->getMeterId().c_str(),
data->getActiveImportPower(), data->getMeterModel().c_str(),
data->getReactiveImportPower(), data->getActiveImportPower(),
data->getActiveExportPower(), data->getReactiveImportPower(),
data->getReactiveExportPower(), data->getActiveExportPower(),
data->getL1Current(), data->getReactiveExportPower(),
data->getL2Current(), data->getL1Current(),
data->getL3Current(), data->getL2Current(),
data->getL1Voltage(), data->getL3Current(),
data->getL2Voltage(), data->getL1Voltage(),
data->getL3Voltage(), data->getL2Voltage(),
data->getActiveImportCounter(), data->getL3Voltage(),
data->getActiveExportCounter(), data->getActiveImportCounter(),
data->getReactiveImportCounter(), data->getActiveExportCounter(),
data->getReactiveExportCounter(), data->getReactiveImportCounter(),
data->getMeterTimestamp() data->getReactiveExportCounter(),
); data->getMeterTimestamp()
return mqtt->publish(topic, json); );
return mqtt->publish(topic, json);
} else {
char json[768];
snprintf_P(json, sizeof(json), JSON3PF_JSON,
WiFi.macAddress().c_str(),
clientId.c_str(),
(uint32_t) (millis64()/1000),
data->getPackageTimestamp(),
hw->getVcc(),
hw->getWifiRssi(),
hw->getTemperature(),
data->getListId().c_str(),
data->getMeterId().c_str(),
data->getMeterModel().c_str(),
data->getActiveImportPower(),
data->getReactiveImportPower(),
data->getActiveExportPower(),
data->getReactiveExportPower(),
data->getL1Current(),
data->getL2Current(),
data->getL3Current(),
data->getL1Voltage(),
data->getL2Voltage(),
data->getL3Voltage(),
data->getPowerFactor(),
data->getL1PowerFactor(),
data->getL2PowerFactor(),
data->getL3PowerFactor(),
data->getActiveImportCounter(),
data->getActiveExportCounter(),
data->getReactiveImportCounter(),
data->getReactiveExportCounter(),
data->getMeterTimestamp()
);
return mqtt->publish(topic, json);
}
} }
return false; return false;
} }

View File

@@ -26,10 +26,10 @@ bool RawMqttHandler::publish(AmsData* data, AmsData* meterState) {
mqtt->publish(topic + "/meter/l1/powerfactor", String(data->getL1PowerFactor(), 2)); mqtt->publish(topic + "/meter/l1/powerfactor", String(data->getL1PowerFactor(), 2));
} }
if(full || meterState->getL2PowerFactor() != data->getL2PowerFactor()) { if(full || meterState->getL2PowerFactor() != data->getL2PowerFactor()) {
mqtt->publish(topic + "/meter/l1/powerfactor", String(data->getL1PowerFactor(), 2)); mqtt->publish(topic + "/meter/l2/powerfactor", String(data->getL2PowerFactor(), 2));
} }
if(full || meterState->getL3PowerFactor() != data->getL3PowerFactor()) { if(full || meterState->getL3PowerFactor() != data->getL3PowerFactor()) {
mqtt->publish(topic + "/meter/l1/powerfactor", String(data->getL1PowerFactor(), 2)); mqtt->publish(topic + "/meter/l3/powerfactor", String(data->getL3PowerFactor(), 2));
} }
case 2: case 2:
// Only send data if changed. ID and Type is sent on the 10s interval only if changed // Only send data if changed. ID and Type is sent on the 10s interval only if changed

View File

@@ -247,7 +247,7 @@ void AmsWebServer::temperatureJson() {
conf == NULL || conf->common ? 1 : 0, conf == NULL || conf->common ? 1 : 0,
data->lastRead data->lastRead
); );
delay(1); delay(10);
} }
char* pos = buf+strlen(buf); char* pos = buf+strlen(buf);
snprintf(count == 0 ? pos : pos-1, 8, "]}"); snprintf(count == 0 ? pos : pos-1, 8, "]}");
@@ -691,7 +691,7 @@ void AmsWebServer::dataJson() {
uint8_t espStatus; uint8_t espStatus;
#if defined(ESP8266) #if defined(ESP8266)
if(vcc == 0) { if(vcc == 0) {
espStatus = 0; espStatus = 1;
} else if(vcc > 3.1 && vcc < 3.5) { } else if(vcc > 3.1 && vcc < 3.5) {
espStatus = 1; espStatus = 1;
} else if(vcc > 3.0 && vcc < 3.6) { } else if(vcc > 3.0 && vcc < 3.6) {
@@ -701,7 +701,7 @@ void AmsWebServer::dataJson() {
} }
#elif defined(ESP32) #elif defined(ESP32)
if(vcc == 0) { if(vcc == 0) {
espStatus = 0; espStatus = 1;
} else if(vcc > 2.8 && vcc < 3.5) { } else if(vcc > 2.8 && vcc < 3.5) {
espStatus = 1; espStatus = 1;
} else if(vcc > 2.2 && vcc < 3.6) { } else if(vcc > 2.2 && vcc < 3.6) {
@@ -780,7 +780,8 @@ void AmsWebServer::dataJson() {
wifiStatus, wifiStatus,
mqttStatus, mqttStatus,
mqtt == NULL ? 0 : (int) mqtt->lastError(), mqtt == NULL ? 0 : (int) mqtt->lastError(),
price == ENTSOE_NO_VALUE ? "null" : String(price, 2).c_str() price == ENTSOE_NO_VALUE ? "null" : String(price, 2).c_str(),
time(nullptr)
); );
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");

View File

@@ -18,6 +18,7 @@ var po = {
enableInteractivity: false, enableInteractivity: false,
}; };
var pl = null; // Last price var pl = null; // Last price
var tl = null; // Last temperature
// Day plot // Day plot
var ep; var ep;
@@ -150,6 +151,25 @@ var xo = {
} }
}; };
// Temperature plot
var td = false; // Disable temperature
var tp;
var ta;
var to = {
title: 'Temperature sensors',
titleTextStyle: {
fontSize: 14
},
bar: { groupWidth: '90%' },
legend: { position: 'none' },
vAxis: {
title: '°C',
viewWindowMode: 'maximized'
},
tooltip: { trigger: 'none'},
enableInteractivity: false,
};
$(function() { $(function() {
var meters = $('.plot1'); var meters = $('.plot1');
@@ -265,9 +285,7 @@ $(function() {
url: swv.data('url'), url: swv.data('url'),
dataType: 'json' dataType: 'json'
}).done(function(releases) { }).done(function(releases) {
if(!swv.text().match("^v\d{1,2}\.\d{1,2}\.\d{1,2}$")) { if(/^v\d{1,2}\.\d{1,2}\.\d{1,2}$/.test(swv.text()) && fwl.length == 0) {
nextVersion = releases[0];
} else {
releases.reverse(); releases.reverse();
var next_patch; var next_patch;
var next_minor; var next_minor;
@@ -308,7 +326,8 @@ $(function() {
} else if(next_patch) { } else if(next_patch) {
nextVersion = next_patch; nextVersion = next_patch;
} }
} else {
nextVersion = releases[0];
} }
if(nextVersion) { if(nextVersion) {
if(fwl.length > 0) { if(fwl.length > 0) {
@@ -365,6 +384,7 @@ var setupChart = function() {
ap = new google.visualization.ColumnChart(document.getElementById('ap')); ap = new google.visualization.ColumnChart(document.getElementById('ap'));
ip = new google.visualization.PieChart(document.getElementById('ip')); ip = new google.visualization.PieChart(document.getElementById('ip'));
xp = new google.visualization.PieChart(document.getElementById('xp')); xp = new google.visualization.PieChart(document.getElementById('xp'));
tp = new google.visualization.ColumnChart(document.getElementById('tp'));
fetch(); fetch();
drawDay(); drawDay();
drawMonth(); drawMonth();
@@ -380,6 +400,10 @@ var redraw = function() {
ap.draw(aa, ao); ap.draw(aa, ao);
ip.draw(ia, io); ip.draw(ia, io);
xp.draw(xa, xo); xp.draw(xa, xo);
tp.draw(ta, to);
if(tl != null) {
tp.draw(ta, to);
}
}; };
var drawPrices = function() { var drawPrices = function() {
@@ -476,6 +500,37 @@ var drawMonth = function() {
}); });
}; };
var drawTemperature = function() {
if(td) return;
$.ajax({
url: '/temperature.json',
timeout: 30000,
dataType: 'json',
}).done(function(json) {
if(json.c > 1) {
$('#tpc').show();
var r = 1;
var min = 0;
data = [['Sensor','°C', { role: 'style' }, { role: 'annotation' }]];
$.each(json.s, function(i, o) {
var name = o.n ? o.n : o.a;
data[r++] = [name, o.v, "color: #6f42c1;opacity: 0.9;", o.v.toFixed(1)];
Math.min(0, o.v);
});
if(min == 0)
to.vAxis.minValue = 0;
ta = google.visualization.arrayToDataTable(data);
ta.sort("Sensor");
tp.draw(ta, to);
td = false;
} else {
td = true;
}
});
};
var setStatus = function(id, sid) { var setStatus = function(id, sid) {
var item = $('#'+id); var item = $('#'+id);
item.removeClass('d-none'); item.removeClass('d-none');
@@ -515,6 +570,7 @@ var ampcol = function(pct) {
else return '#32d900'; else return '#32d900';
}; };
var retrycount = 0;
var interval = 5000; var interval = 5000;
var fetch = function() { var fetch = function() {
$.ajax({ $.ajax({
@@ -522,6 +578,7 @@ var fetch = function() {
timeout: 10000, timeout: 10000,
dataType: 'json', dataType: 'json',
}).done(function(json) { }).done(function(json) {
retrycount = 0;
if(im) { if(im) {
$(".SimpleMeter").hide(); $(".SimpleMeter").hide();
im.show(); im.show();
@@ -668,10 +725,35 @@ var fetch = function() {
$('#ml').html(json.me); $('#ml').html(json.me);
} }
var temp = parseInt(json.t); var temp = parseFloat(json.t);
if(temp == -127) { if(temp == -127.0) {
$('.jt').html("N/A"); $('.jt').html("N/A");
$('.rt').hide();
} else {
$('.rt').show();
if(tl != temp) {
drawTemperature();
}
tl = temp;
} }
var vcc = parseFloat(json.v);
if(vcc > 0.0) {
$('.rv').show();
} else {
$('.rv').hide();
}
var unixtime = moment().unix();
var ts = parseInt(json.c);
if(Math.abs(unixtime-ts) < 300) {
$('.jc').html(moment(ts * 1000).format('DD. MMM HH:mm'));
$('.jc').removeClass('text-danger');
} else {
$('.jc').html(moment(ts * 1000).format('DD.MM.YYYY HH:mm'));
$('.jc').addClass('text-danger');
}
setTimeout(fetch, interval); setTimeout(fetch, interval);
var price = parseFloat(json.p); var price = parseFloat(json.p);
@@ -680,15 +762,20 @@ var fetch = function() {
drawPrices(); drawPrices();
} }
}).fail(function(x, text, error) { }).fail(function(x, text, error) {
console.log("Failed request"); if(retrycount > 2) {
console.log(text); console.log("Failed request");
console.log(error); console.log(text);
setTimeout(fetch, interval*4); console.log(error);
setTimeout(fetch, interval*4);
setStatus("mqtt", 0); setStatus("mqtt", 0);
setStatus("wifi", 0); setStatus("wifi", 0);
setStatus("han", 0); setStatus("han", 0);
setStatus("esp", 3); setStatus("esp", 3);
} else {
setTimeout(fetch, interval);
}
retrycount++;
}); });
} }

View File

@@ -30,5 +30,6 @@
"wm" : %d, "wm" : %d,
"mm" : %d, "mm" : %d,
"me" : %d, "me" : %d,
"p" : %s "p" : %s,
"c" : %lu
} }

View File

@@ -50,9 +50,6 @@
</a> </a>
<div class="navbar-nav-scroll"> <div class="navbar-nav-scroll">
<ul class="navbar-nav bd-navbar-nav flex-row"> <ul class="navbar-nav bd-navbar-nav flex-row">
<li class="nav-item">
<a id="temp-link" class="nav-link" href="/temperature">Temp<span class="d-none d-sm-inline">erature</span></a>
</li>
<li class="nav-item"> <li class="nav-item">
<div class="dropdown"> <div class="dropdown">
<a class="dropdown-toggle nav-link" href="#" role="button" id="config-link" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <a class="dropdown-toggle nav-link" href="#" role="button" id="config-link" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">

View File

@@ -50,9 +50,6 @@
</a> </a>
<div class="navbar-nav-scroll"> <div class="navbar-nav-scroll">
<ul class="navbar-nav bd-navbar-nav flex-row"> <ul class="navbar-nav bd-navbar-nav flex-row">
<li class="nav-item">
<a id="temp-link" class="nav-link" href="/temperature">Temp<span class="d-none d-sm-inline">erature</span></a>
</li>
<li class="nav-item"> <li class="nav-item">
<div class="dropdown"> <div class="dropdown">
<a class="dropdown-toggle nav-link" href="#" role="button" id="config-link" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <a class="dropdown-toggle nav-link" href="#" role="button" id="config-link" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">

View File

@@ -3,10 +3,10 @@
<div class="col-md-2 col-6"> <div class="col-md-2 col-6">
<div class="text-center">Up <span class="ju">{cs}</span></div> <div class="text-center">Up <span class="ju">{cs}</span></div>
</div> </div>
<div class="col-md-3 col-6"> <div class="col-md-3 col-6 rt ">
<div class="text-center">Temperature: <span class="jt">{temp}</span>&deg;C</div> <div class="text-center">Temperature: <span class="jt">{temp}</span>&deg;C</div>
</div> </div>
<div class="col-md-2 col-6"> <div class="col-md-2 col-6 rv">
<div class="text-center">ESP volt: <span class="jv">{vcc}</span>V</div> <div class="text-center">ESP volt: <span class="jv">{vcc}</span>V</div>
</div> </div>
<div class="col-md-3 col-6"> <div class="col-md-3 col-6">
@@ -15,6 +15,9 @@
<div class="col-md-2 col-6"> <div class="col-md-2 col-6">
<div class="text-center">Free mem: <span class="jm">{mem}</span>kb</div> <div class="text-center">Free mem: <span class="jm">{mem}</span>kb</div>
</div> </div>
<div class="col-md-3 col-6 rc">
<div class="text-center"><span class="jc"></span></div>
</div>
</div> </div>
</div> </div>
@@ -116,6 +119,12 @@
<div class="col-xl-12 mb-3"> <div class="col-xl-12 mb-3">
<div class="bg-white rounded shadow" id="mp" style="width: 100%; height: 224px;"></div> <div class="bg-white rounded shadow" id="mp" style="width: 100%; height: 224px;"></div>
</div> </div>
<div id="tpc" class="col-xl-12 mb-3" style="display: none;">
<div class="bg-white rounded shadow pb-3">
<div id="tp" style="width: 100%; height: 224px;"></div>
<a class="m-4" href="/temperature">Configuration</a>
</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="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-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> <div class="d-none badge badge-danger me me-3">MQTT failed to connect</div>

33
web/json3pf.json Normal file
View File

@@ -0,0 +1,33 @@
{
"id" : "%s",
"name" : "%s",
"up" : %lu,
"t" : %lu,
"vcc" : %.3f,
"rssi": %d,
"temp": %.2f,
"data" : {
"lv" : "%s",
"id" : "%s",
"type" : "%s",
"P" : %d,
"Q" : %d,
"PO" : %d,
"QO" : %d,
"I1" : %.2f,
"I2" : %.2f,
"I3" : %.2f,
"U1" : %.2f,
"U2" : %.2f,
"U3" : %.2f,
"PF" : %.2f,
"PF1" : %.2f,
"PF2" : %.2f,
"PF3" : %.2f,
"tPI" : %.2f,
"tPO" : %.2f,
"tQI" : %.2f,
"tQO" : %.2f,
"rtc" : %lu
}
}