mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-03-21 00:57:46 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70f5b0f912 | ||
|
|
d724a90085 | ||
|
|
1ab529785a | ||
|
|
3a81e62bbe | ||
|
|
4882916b5c | ||
|
|
1f7e43256a | ||
|
|
a0d3632fd7 | ||
|
|
b25564a89f | ||
|
|
191d9fa562 | ||
|
|
e009b4e54e |
@@ -6,7 +6,7 @@ DB // Encrypted
|
|||||||
08 4B 41 4D 45 01 AC 4D 6E // System title
|
08 4B 41 4D 45 01 AC 4D 6E // System title
|
||||||
82 // Prefix for 2-byte length
|
82 // Prefix for 2-byte length
|
||||||
01 D0 // Length 464
|
01 D0 // Length 464
|
||||||
30 // Security tag 0011 0000, 0=Compression off, 0=Unicast, 1=Encryption, 0=Authentication, 0000= Security Suite ID
|
30 // Security tag 0011 0000, 0=Compression off, 0=Unicast, 1=Encryption, 1=Authentication, 0000= Security Suite ID
|
||||||
00 00 A3 2F // Frame counter
|
00 00 A3 2F // Frame counter
|
||||||
|
|
||||||
// Decrypted frame below
|
// Decrypted frame below
|
||||||
|
|||||||
@@ -776,8 +776,21 @@ bool readHanPort() {
|
|||||||
}
|
}
|
||||||
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) {
|
||||||
else currentMeterType = 2;
|
debugD("HDLC or MBUS");
|
||||||
|
currentMeterType = 1;
|
||||||
|
} else if(flag == 0xDB) {
|
||||||
|
debugD("Encrypted DSMR");
|
||||||
|
hc = new HDLCConfig();
|
||||||
|
memcpy(hc->encryption_key, meterConfig.encryptionKey, 16);
|
||||||
|
memcpy(hc->authentication_key, meterConfig.authenticationKey, 16);
|
||||||
|
currentMeterType = 2;
|
||||||
|
} else if(flag == 0x2F) {
|
||||||
|
debugD("DSMR");
|
||||||
|
currentMeterType = 2;
|
||||||
|
} else {
|
||||||
|
currentMeterType = -1;
|
||||||
|
}
|
||||||
hanSerial->readBytes(hanBuffer, BUF_SIZE_HAN);
|
hanSerial->readBytes(hanBuffer, BUF_SIZE_HAN);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -855,70 +868,58 @@ bool readHanPort() {
|
|||||||
debugD("Valid data, start at byte %d", pos);
|
debugD("Valid data, start at byte %d", pos);
|
||||||
data = IEC6205675(((char *) (hanBuffer)) + pos, meterState.getMeterType(), &meterConfig, timestamp, hc);
|
data = IEC6205675(((char *) (hanBuffer)) + pos, meterState.getMeterType(), &meterConfig, timestamp, hc);
|
||||||
} else {
|
} else {
|
||||||
if(Debug.isActive(RemoteDebug::WARNING)) {
|
printHanReadError(pos);
|
||||||
switch(pos) {
|
|
||||||
case HDLC_BOUNDRY_FLAG_MISSING:
|
|
||||||
debugW("Boundry flag missing");
|
|
||||||
break;
|
|
||||||
case HDLC_HCS_ERROR:
|
|
||||||
debugW("Header checksum error");
|
|
||||||
break;
|
|
||||||
case HDLC_FCS_ERROR:
|
|
||||||
debugW("Frame checksum error");
|
|
||||||
break;
|
|
||||||
case HDLC_FRAME_INCOMPLETE:
|
|
||||||
debugW("Received frame is incomplete");
|
|
||||||
break;
|
|
||||||
case HDLC_ENCRYPTION_CONFIG_MISSING:
|
|
||||||
debugI("Encryption configuration requested, initializing");
|
|
||||||
break;
|
|
||||||
case HDLC_ENCRYPTION_AUTH_FAILED:
|
|
||||||
debugW("Decrypt authentication failed");
|
|
||||||
break;
|
|
||||||
case HDLC_ENCRYPTION_KEY_FAILED:
|
|
||||||
debugW("Setting decryption key failed");
|
|
||||||
break;
|
|
||||||
case HDLC_ENCRYPTION_DECRYPT_FAILED:
|
|
||||||
debugW("Decryption failed");
|
|
||||||
break;
|
|
||||||
case MBUS_FRAME_LENGTH_NOT_EQUAL:
|
|
||||||
debugW("Frame length mismatch");
|
|
||||||
break;
|
|
||||||
case MBUS_FRAME_INTERMEDIATE_SEGMENT:
|
|
||||||
case MBUS_FRAME_LAST_SEGMENT:
|
|
||||||
debugW("Partial frame dropped");
|
|
||||||
break;
|
|
||||||
case HDLC_TIMESTAMP_UNKNOWN:
|
|
||||||
debugW("Frame timestamp is not correctly formatted");
|
|
||||||
break;
|
|
||||||
case HDLC_UNKNOWN_DATA:
|
|
||||||
debugW("Unknown data format %02X", hanBuffer[0]);
|
|
||||||
currentMeterType = 0;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
debugW("Unspecified error while reading data: %d", pos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if(currentMeterType == 2) {
|
} else if(currentMeterType == 2) {
|
||||||
String payload = hanSerial->readString();
|
int pos = HDLC_FRAME_INCOMPLETE;
|
||||||
if(mqttEnabled && mqtt != NULL && mqttHandler == NULL) {
|
if(hc != NULL) {
|
||||||
mqtt->publish(topic.c_str(), payload);
|
while(hanSerial->available() && pos == HDLC_FRAME_INCOMPLETE) {
|
||||||
|
hanBuffer[len++] = hanSerial->read();
|
||||||
|
pos = mbus_decrypt((uint8_t *) hanBuffer, len, hc);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
while(hanSerial->available()) {
|
||||||
|
hanBuffer[len++] = hanSerial->read();
|
||||||
|
}
|
||||||
|
if(len > 10) {
|
||||||
|
String end = String((char*) hanBuffer+len-5);
|
||||||
|
if(end.startsWith("!")) pos = 0;
|
||||||
|
while(hanSerial->available()) hanSerial->read();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
data = IEC6205621(payload);
|
if(len == 0) return false;
|
||||||
|
if(pos == HDLC_FRAME_INCOMPLETE) return false;
|
||||||
|
if(len >= BUF_SIZE_HAN) {
|
||||||
|
len = 0;
|
||||||
|
debugI("Buffer overflow, resetting");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(pos < 0) {
|
||||||
|
printHanReadError(pos);
|
||||||
|
while(hanSerial->available()) hanSerial->read();
|
||||||
|
len = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mqttEnabled && mqtt != NULL && mqttHandler == NULL) {
|
||||||
|
mqtt->publish(topic.c_str(), (char*) hanBuffer);
|
||||||
|
}
|
||||||
|
len = 0;
|
||||||
|
data = IEC6205621(((char *) (hanBuffer)) + pos);
|
||||||
if(data.getListType() == 0) {
|
if(data.getListType() == 0) {
|
||||||
currentMeterType = 1;
|
currentMeterType = 0;
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
if(Debug.isActive(RemoteDebug::DEBUG)) {
|
if(Debug.isActive(RemoteDebug::DEBUG)) {
|
||||||
debugD("Frame dump: %d", payload.length());
|
debugD("Frame dump: %d", strlen((char*) (hanBuffer+pos)));
|
||||||
debugD("%s", payload.c_str());
|
debugD("%s", hanBuffer+pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for(int i = len; i<BUF_SIZE_HAN; i++) hanBuffer[i] = 0x00;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(data.getListType() > 0) {
|
if(data.getListType() > 0) {
|
||||||
@@ -978,6 +979,53 @@ bool readHanPort() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void printHanReadError(int pos) {
|
||||||
|
if(Debug.isActive(RemoteDebug::WARNING)) {
|
||||||
|
switch(pos) {
|
||||||
|
case HDLC_BOUNDRY_FLAG_MISSING:
|
||||||
|
debugW("Boundry flag missing");
|
||||||
|
break;
|
||||||
|
case HDLC_HCS_ERROR:
|
||||||
|
debugW("Header checksum error");
|
||||||
|
break;
|
||||||
|
case HDLC_FCS_ERROR:
|
||||||
|
debugW("Frame checksum error");
|
||||||
|
break;
|
||||||
|
case HDLC_FRAME_INCOMPLETE:
|
||||||
|
debugW("Received frame is incomplete");
|
||||||
|
break;
|
||||||
|
case HDLC_ENCRYPTION_CONFIG_MISSING:
|
||||||
|
debugI("Encryption configuration requested, initializing");
|
||||||
|
break;
|
||||||
|
case HDLC_ENCRYPTION_AUTH_FAILED:
|
||||||
|
debugW("Decrypt authentication failed");
|
||||||
|
break;
|
||||||
|
case HDLC_ENCRYPTION_KEY_FAILED:
|
||||||
|
debugW("Setting decryption key failed");
|
||||||
|
break;
|
||||||
|
case HDLC_ENCRYPTION_DECRYPT_FAILED:
|
||||||
|
debugW("Decryption failed");
|
||||||
|
break;
|
||||||
|
case MBUS_FRAME_LENGTH_NOT_EQUAL:
|
||||||
|
debugW("Frame length mismatch");
|
||||||
|
break;
|
||||||
|
case MBUS_FRAME_INTERMEDIATE_SEGMENT:
|
||||||
|
case MBUS_FRAME_LAST_SEGMENT:
|
||||||
|
debugW("Partial frame dropped");
|
||||||
|
break;
|
||||||
|
case HDLC_TIMESTAMP_UNKNOWN:
|
||||||
|
debugW("Frame timestamp is not correctly formatted");
|
||||||
|
break;
|
||||||
|
case HDLC_UNKNOWN_DATA:
|
||||||
|
debugW("Unknown data format %02X", hanBuffer[0]);
|
||||||
|
currentMeterType = 0;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
debugW("Unspecified error while reading data: %d", pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void debugPrint(byte *buffer, int start, int length) {
|
void debugPrint(byte *buffer, int start, int length) {
|
||||||
for (int i = start; i < start + length; i++) {
|
for (int i = start; i < start + length; i++) {
|
||||||
if (buffer[i] < 0x10)
|
if (buffer[i] < 0x10)
|
||||||
@@ -1149,15 +1197,12 @@ void MQTT_connect() {
|
|||||||
mqtt->disconnect();
|
mqtt->disconnect();
|
||||||
yield();
|
yield();
|
||||||
} else {
|
} else {
|
||||||
uint16_t size = 128;
|
uint16_t size = 256;
|
||||||
switch(mqttConfig.payloadFormat) {
|
switch(mqttConfig.payloadFormat) {
|
||||||
case 0: // JSON
|
case 0: // JSON
|
||||||
case 4: // Home Assistant
|
case 4: // Home Assistant
|
||||||
size = 768;
|
size = 768;
|
||||||
break;
|
break;
|
||||||
case 3: // Domoticz
|
|
||||||
size = 256;
|
|
||||||
break;
|
|
||||||
case 255: // Raw frame
|
case 255: // Raw frame
|
||||||
size = 1024;
|
size = 1024;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -14,10 +14,14 @@ void HwTools::setup(GpioConfig* config, AmsConfiguration* amsConf) {
|
|||||||
config->tempSensorPin = 0xFF;
|
config->tempSensorPin = 0xFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(ESP32)
|
||||||
|
analogReadResolution(12);
|
||||||
|
analogRange = 4096;
|
||||||
|
#endif
|
||||||
if(config->vccPin > 0 && config->vccPin < 40) {
|
if(config->vccPin > 0 && config->vccPin < 40) {
|
||||||
getAdcChannel(config->vccPin, voltAdc);
|
#if defined(CONFIG_IDF_TARGET_ESP32)
|
||||||
if(voltAdc.unit != 0xFF) {
|
getAdcChannel(config->vccPin, voltAdc);
|
||||||
#if defined(CONFIG_IDF_TARGET_ESP32)
|
if(voltAdc.unit != 0xFF) {
|
||||||
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);
|
||||||
@@ -27,10 +31,10 @@ void HwTools::setup(GpioConfig* config, AmsConfiguration* amsConf) {
|
|||||||
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);
|
||||||
adc2_config_channel_atten((adc2_channel_t) voltAdc.channel, ADC_ATTEN_DB_6);
|
adc2_config_channel_atten((adc2_channel_t) voltAdc.channel, ADC_ATTEN_DB_6);
|
||||||
}
|
}
|
||||||
#endif
|
}
|
||||||
} else {
|
#else
|
||||||
pinMode(config->vccPin, INPUT);
|
pinMode(config->vccPin, INPUT);
|
||||||
}
|
#endif
|
||||||
} else {
|
} else {
|
||||||
voltAdc.unit = 0xFF;
|
voltAdc.unit = 0xFF;
|
||||||
voltAdc.channel = 0xFF;
|
voltAdc.channel = 0xFF;
|
||||||
@@ -176,20 +180,22 @@ double HwTools::getVcc() {
|
|||||||
for (int i = 0; i < 10; i++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
x += analogRead(config->vccPin);
|
x += analogRead(config->vccPin);
|
||||||
}
|
}
|
||||||
volts = x / 40950;
|
volts = (x * 3.3) / 10.0 / analogRange;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
uint32_t x = 0;
|
uint32_t x = 0;
|
||||||
for (int i = 0; i < 10; i++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
x += analogRead(config->vccPin);
|
x += analogRead(config->vccPin);
|
||||||
}
|
}
|
||||||
volts = x / 10240;
|
volts = (x * 3.3) / 10.0 / analogRange;
|
||||||
#endif
|
#endif
|
||||||
} else {
|
} else {
|
||||||
#if defined(ESP8266)
|
#if defined(ESP8266)
|
||||||
volts = ESP.getVcc() / 1024.0;
|
volts = ESP.getVcc() / 1024.0;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
if(volts == 0.0) return 0.0;
|
||||||
|
|
||||||
if(config->vccResistorGnd > 0 && config->vccResistorVcc > 0) {
|
if(config->vccResistorGnd > 0 && config->vccResistorVcc > 0) {
|
||||||
volts *= ((double) (config->vccResistorGnd + config->vccResistorVcc) / config->vccResistorGnd);
|
volts *= ((double) (config->vccResistorGnd + config->vccResistorVcc) / config->vccResistorGnd);
|
||||||
}
|
}
|
||||||
@@ -303,12 +309,7 @@ double HwTools::getTemperature() {
|
|||||||
double HwTools::getTemperatureAnalog() {
|
double HwTools::getTemperatureAnalog() {
|
||||||
if(config->tempAnalogSensorPin != 0xFF) {
|
if(config->tempAnalogSensorPin != 0xFF) {
|
||||||
float adcCalibrationFactor = 1.06587;
|
float adcCalibrationFactor = 1.06587;
|
||||||
int volts;
|
int volts = ((double) analogRead(config->tempAnalogSensorPin) / analogRange) * 3.3;
|
||||||
#if defined(ESP8266)
|
|
||||||
volts = (analogRead(config->tempAnalogSensorPin) / 1024.0) * 3.3;
|
|
||||||
#elif defined(ESP32)
|
|
||||||
volts = (analogRead(config->tempAnalogSensorPin) / 4095.0) * 3.3;
|
|
||||||
#endif
|
|
||||||
return ((volts * adcCalibrationFactor) - 0.4) / 0.0195;
|
return ((volts * adcCalibrationFactor) - 0.4) / 0.0195;
|
||||||
}
|
}
|
||||||
return DEVICE_DISCONNECTED_C;
|
return DEVICE_DISCONNECTED_C;
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ public:
|
|||||||
|
|
||||||
HwTools() {};
|
HwTools() {};
|
||||||
private:
|
private:
|
||||||
|
uint16_t analogRange = 1024;
|
||||||
AdcConfig voltAdc, tempAdc;
|
AdcConfig voltAdc, tempAdc;
|
||||||
#if defined(ESP32)
|
#if defined(ESP32)
|
||||||
esp_adc_cal_characteristics_t* voltAdcChar, tempAdcChar;
|
esp_adc_cal_characteristics_t* voltAdcChar, tempAdcChar;
|
||||||
|
|||||||
@@ -1,11 +1,20 @@
|
|||||||
#include "IEC6205621.h"
|
#include "IEC6205621.h"
|
||||||
|
#include "ams/crc.h"
|
||||||
|
|
||||||
IEC6205621::IEC6205621(String payload) {
|
IEC6205621::IEC6205621(const char* p) {
|
||||||
if(payload.length() < 16)
|
if(strlen(p) < 16)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
String payload(p+1);
|
||||||
|
int crc_pos = payload.lastIndexOf("!");
|
||||||
|
String crc = payload.substring(crc_pos+1, crc_pos+5);
|
||||||
|
//uint16_t crc_calc = crc16_x25((uint8_t*) (payload.startsWith("/") ? p+1 : p), crc_pos);
|
||||||
|
|
||||||
|
//Serial.printf("CRC %s :: %04X\n", crc.c_str(), crc_calc);
|
||||||
|
|
||||||
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);
|
||||||
@@ -21,7 +30,7 @@ IEC6205621::IEC6205621(String payload) {
|
|||||||
} else if(listId.startsWith("XMX")) {
|
} else if(listId.startsWith("XMX")) {
|
||||||
meterType = AmsTypeLandis;
|
meterType = AmsTypeLandis;
|
||||||
listId = listId.substring(0,6);
|
listId = listId.substring(0,6);
|
||||||
} else if(listId.startsWith("Ene")) {
|
} else if(listId.startsWith("Ene") || listId.startsWith("EST")) {
|
||||||
meterType = AmsTypeSagemcom;
|
meterType = AmsTypeSagemcom;
|
||||||
listId = listId.substring(0,4);
|
listId = listId.substring(0,4);
|
||||||
} else {
|
} else {
|
||||||
@@ -55,10 +64,10 @@ IEC6205621::IEC6205621(String payload) {
|
|||||||
meterTimestamp = makeTime(tm); // TODO: Adjust for time zone
|
meterTimestamp = makeTime(tm); // TODO: Adjust for time zone
|
||||||
}
|
}
|
||||||
|
|
||||||
activeImportPower = (uint16_t) (extractDouble(payload, "1.7.0") * 1000);
|
activeImportPower = (uint16_t) (extractDouble(payload, "1.7.0"));
|
||||||
activeExportPower = (uint16_t) (extractDouble(payload, "2.7.0") * 1000);
|
activeExportPower = (uint16_t) (extractDouble(payload, "2.7.0"));
|
||||||
reactiveImportPower = (uint16_t) (extractDouble(payload, "3.7.0") * 1000);
|
reactiveImportPower = (uint16_t) (extractDouble(payload, "3.7.0"));
|
||||||
reactiveExportPower = (uint16_t) (extractDouble(payload, "4.7.0") * 1000);
|
reactiveExportPower = (uint16_t) (extractDouble(payload, "4.7.0"));
|
||||||
|
|
||||||
if(activeImportPower > 0)
|
if(activeImportPower > 0)
|
||||||
listType = 1;
|
listType = 1;
|
||||||
@@ -73,11 +82,40 @@ IEC6205621::IEC6205621(String payload) {
|
|||||||
|
|
||||||
if(l1voltage > 0 || l2voltage > 0 || l3voltage > 0)
|
if(l1voltage > 0 || l2voltage > 0 || l3voltage > 0)
|
||||||
listType = 2;
|
listType = 2;
|
||||||
|
|
||||||
|
double val = 0.0;
|
||||||
|
|
||||||
activeImportCounter = extractDouble(payload, "1.8.0");
|
val = extractDouble(payload, "1.8.0");
|
||||||
activeExportCounter = extractDouble(payload, "2.8.0");
|
if(val == 0) {
|
||||||
reactiveImportCounter = extractDouble(payload, "3.8.0");
|
for(int i = 1; i < 9; i++) {
|
||||||
reactiveExportCounter = extractDouble(payload, "4.8.0");
|
val += extractDouble(payload, "1.8." + String(i,10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(val > 0) activeImportCounter = val / 1000;
|
||||||
|
|
||||||
|
val = extractDouble(payload, "2.8.0");
|
||||||
|
if(val == 0) {
|
||||||
|
for(int i = 1; i < 9; i++) {
|
||||||
|
val += extractDouble(payload, "2.8." + String(i,10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(val > 0) activeExportCounter = val / 1000;
|
||||||
|
|
||||||
|
val = extractDouble(payload, "3.8.0");
|
||||||
|
if(val == 0) {
|
||||||
|
for(int i = 1; i < 9; i++) {
|
||||||
|
val += extractDouble(payload, "3.8." + String(i,10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(val > 0) reactiveImportCounter = val / 1000;
|
||||||
|
|
||||||
|
val = extractDouble(payload, "4.8.0");
|
||||||
|
if(val == 0) {
|
||||||
|
for(int i = 1; i < 9; i++) {
|
||||||
|
val += extractDouble(payload, "4.8." + String(i,10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(val > 0) reactiveExportCounter = val / 1000;
|
||||||
|
|
||||||
if(activeImportCounter > 0 || activeExportCounter > 0 || reactiveImportCounter > 0 || reactiveExportCounter > 0)
|
if(activeImportCounter > 0 || activeExportCounter > 0 || reactiveImportCounter > 0 || reactiveExportCounter > 0)
|
||||||
listType = 3;
|
listType = 3;
|
||||||
@@ -104,5 +142,14 @@ String IEC6205621::extract(String payload, String obis) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
double IEC6205621::extractDouble(String payload, String obis) {
|
double IEC6205621::extractDouble(String payload, String obis) {
|
||||||
return extract(payload, obis).toDouble();
|
String str = extract(payload, obis);
|
||||||
|
if(str.isEmpty()) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int a = str.indexOf("*");
|
||||||
|
String val = str.substring(0,a);
|
||||||
|
String unit = str.substring(a+1);
|
||||||
|
|
||||||
|
return unit.startsWith("k") ? val.toDouble() * 1000 : val.toDouble();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
#ifndef _IEC62056_21_H
|
#ifndef _IEC62056_21_H
|
||||||
#define _IEC62056_21_H
|
#define _IEC62056_21_H
|
||||||
|
|
||||||
|
#include "Arduino.h"
|
||||||
#include "AmsData.h"
|
#include "AmsData.h"
|
||||||
|
|
||||||
class IEC6205621 : public AmsData {
|
class IEC6205621 : public AmsData {
|
||||||
public:
|
public:
|
||||||
IEC6205621(String payload);
|
IEC6205621(const char* payload);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
String extract(String payload, String obis);
|
String extract(String payload, String obis);
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
|
|||||||
}
|
}
|
||||||
// Try system title
|
// Try system title
|
||||||
if(meterType == AmsTypeUnknown && hc != NULL) {
|
if(meterType == AmsTypeUnknown && hc != NULL) {
|
||||||
if(memcmp(hc->system_title, "SAGY", 4)) {
|
if(memcmp(hc->system_title, "SAGY", 4) == 0) {
|
||||||
meterType = AmsTypeSagemcom;
|
meterType = AmsTypeSagemcom;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
277
src/ams/hdlc.cpp
277
src/ams/hdlc.cpp
@@ -133,167 +133,156 @@ int HDLC_validate(const uint8_t* d, int length, HDLCConfig* config, CosemDateTim
|
|||||||
return HDLC_UNKNOWN_DATA;
|
return HDLC_UNKNOWN_DATA;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(((*ptr) & 0xFF) == 0x0F) {
|
Serial.flush();
|
||||||
// Unencrypted APDU
|
|
||||||
HDLCADPU* adpu = (HDLCADPU*) (ptr);
|
|
||||||
ptr += sizeof *adpu;
|
|
||||||
|
|
||||||
// ADPU timestamp
|
if(((*ptr) & 0xFF) == 0xDB) {
|
||||||
CosemData* dateTime = (CosemData*) ptr;
|
|
||||||
if(dateTime->base.type == CosemTypeOctetString) {
|
|
||||||
if(dateTime->base.length == 0x0C) {
|
|
||||||
memcpy(timestamp, ptr+1, dateTime->base.length+1);
|
|
||||||
}
|
|
||||||
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, 13);
|
|
||||||
ptr += 13;
|
|
||||||
} else {
|
|
||||||
return HDLC_TIMESTAMP_UNKNOWN;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ptr-d;
|
|
||||||
} else if(((*ptr) & 0xFF) == 0xDB) {
|
|
||||||
if(length < headersize + 18)
|
if(length < headersize + 18)
|
||||||
return HDLC_FRAME_INCOMPLETE;
|
return HDLC_FRAME_INCOMPLETE;
|
||||||
|
|
||||||
ptr++;
|
int ret = mbus_decrypt(ptr, length - headersize - footersize, config);
|
||||||
// Encrypted APDU
|
if(ret < 0) return ret;
|
||||||
// http://www.weigu.lu/tutorials/sensors2bus/04_encryption/index.html
|
ptr += ret;
|
||||||
if(config == NULL)
|
}
|
||||||
return HDLC_ENCRYPTION_CONFIG_MISSING;
|
|
||||||
|
|
||||||
uint8_t systemTitleLength = *ptr;
|
HDLCADPU* adpu = (HDLCADPU*) (ptr);
|
||||||
ptr++;
|
ptr += sizeof *adpu;
|
||||||
memcpy(config->system_title, ptr, systemTitleLength);
|
|
||||||
memcpy(config->initialization_vector, config->system_title, systemTitleLength);
|
|
||||||
|
|
||||||
headersize += 2 + systemTitleLength;
|
// ADPU timestamp
|
||||||
ptr += systemTitleLength;
|
CosemData* dateTime = (CosemData*) ptr;
|
||||||
if(((*ptr) & 0xFF) == 0x81) {
|
if(dateTime->base.type == CosemTypeOctetString) {
|
||||||
ptr++;
|
if(dateTime->base.length == 0x0C) {
|
||||||
len = *ptr;
|
memcpy(timestamp, ptr+1, dateTime->base.length+1);
|
||||||
// 1-byte payload length
|
|
||||||
ptr++;
|
|
||||||
headersize += 2;
|
|
||||||
} else if(((*ptr) & 0xFF) == 0x82) {
|
|
||||||
HDLCHeader* h = (HDLCHeader*) ptr;
|
|
||||||
|
|
||||||
// 2-byte payload length
|
|
||||||
len = (ntohs(h->format) & 0xFFFF);
|
|
||||||
|
|
||||||
ptr += 3;
|
|
||||||
headersize += 3;
|
|
||||||
}
|
}
|
||||||
if(len + headersize + footersize > length)
|
ptr += 2 + dateTime->base.length;
|
||||||
return HDLC_FRAME_INCOMPLETE;
|
} else if(dateTime->base.type == CosemTypeNull) {
|
||||||
|
timestamp = 0;
|
||||||
//Serial.printf("\nL: %d : %d, %d : %d\n", length, len, headersize, footersize);
|
|
||||||
|
|
||||||
memcpy(config->additional_authenticated_data, ptr, 1);
|
|
||||||
|
|
||||||
// Security tag
|
|
||||||
uint8_t sec = *ptr;
|
|
||||||
ptr++;
|
ptr++;
|
||||||
headersize++;
|
} else if(dateTime->base.type == CosemTypeDateTime) {
|
||||||
|
memcpy(timestamp, ptr, dateTime->base.length);
|
||||||
|
} else if(dateTime->base.type == 0x0C) { // Kamstrup bug...
|
||||||
|
memcpy(timestamp, ptr, 13);
|
||||||
|
ptr += 13;
|
||||||
|
} else {
|
||||||
|
return HDLC_TIMESTAMP_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
// Frame counter
|
return ptr-d;
|
||||||
memcpy(config->initialization_vector + 8, ptr, 4);
|
}
|
||||||
ptr += 4;
|
|
||||||
headersize += 4;
|
|
||||||
|
|
||||||
// Authentication enabled
|
int mbus_decrypt(const uint8_t* d, int length, HDLCConfig* config) {
|
||||||
uint8_t authkeylen = 0, aadlen = 0;
|
if(length < 12) return HDLC_FRAME_INCOMPLETE;
|
||||||
if((sec & 0x10) == 0x10) {
|
|
||||||
authkeylen = 12;
|
uint8_t* ptr = (uint8_t*) d;
|
||||||
aadlen = 17;
|
if(*ptr != 0xDB) return HDLC_ENCRYPTION_INVALID;
|
||||||
footersize += authkeylen;
|
ptr++;
|
||||||
memcpy(config->additional_authenticated_data + 1, config->authentication_key, 16);
|
// Encrypted APDU
|
||||||
memcpy(config->authentication_tag, ptr + len - footersize - 2, authkeylen);
|
// http://www.weigu.lu/tutorials/sensors2bus/04_encryption/index.html
|
||||||
|
if(config == NULL)
|
||||||
|
return HDLC_ENCRYPTION_CONFIG_MISSING;
|
||||||
|
|
||||||
|
uint8_t systemTitleLength = *ptr;
|
||||||
|
ptr++;
|
||||||
|
memcpy(config->system_title, ptr, systemTitleLength);
|
||||||
|
memcpy(config->initialization_vector, config->system_title, systemTitleLength);
|
||||||
|
|
||||||
|
int len;
|
||||||
|
int headersize = 2 + systemTitleLength;
|
||||||
|
ptr += systemTitleLength;
|
||||||
|
if(((*ptr) & 0xFF) == 0x81) {
|
||||||
|
ptr++;
|
||||||
|
len = *ptr;
|
||||||
|
// 1-byte payload length
|
||||||
|
ptr++;
|
||||||
|
headersize += 2;
|
||||||
|
} else if(((*ptr) & 0xFF) == 0x82) {
|
||||||
|
HDLCHeader* h = (HDLCHeader*) ptr;
|
||||||
|
|
||||||
|
// 2-byte payload length
|
||||||
|
len = (ntohs(h->format) & 0xFFFF);
|
||||||
|
|
||||||
|
ptr += 3;
|
||||||
|
headersize += 3;
|
||||||
|
}
|
||||||
|
if(len + headersize > length)
|
||||||
|
return HDLC_FRAME_INCOMPLETE;
|
||||||
|
|
||||||
|
//Serial.printf("\nL: %d : %d, %d\n", length, len, headersize);
|
||||||
|
|
||||||
|
memcpy(config->additional_authenticated_data, ptr, 1);
|
||||||
|
|
||||||
|
// Security tag
|
||||||
|
uint8_t sec = *ptr;
|
||||||
|
ptr++;
|
||||||
|
headersize++;
|
||||||
|
|
||||||
|
// Frame counter
|
||||||
|
memcpy(config->initialization_vector + 8, ptr, 4);
|
||||||
|
ptr += 4;
|
||||||
|
headersize += 4;
|
||||||
|
|
||||||
|
int footersize = 0;
|
||||||
|
|
||||||
|
// Authentication enabled
|
||||||
|
uint8_t authkeylen = 0, aadlen = 0;
|
||||||
|
if((sec & 0x10) == 0x10) {
|
||||||
|
authkeylen = 12;
|
||||||
|
aadlen = 17;
|
||||||
|
footersize += authkeylen;
|
||||||
|
memcpy(config->additional_authenticated_data + 1, config->authentication_key, 16);
|
||||||
|
memcpy(config->authentication_tag, ptr + len - footersize - 5, authkeylen);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(ESP8266)
|
||||||
|
br_gcm_context gcmCtx;
|
||||||
|
br_aes_ct_ctr_keys bc;
|
||||||
|
br_aes_ct_ctr_init(&bc, config->encryption_key, 16);
|
||||||
|
br_gcm_init(&gcmCtx, &bc.vtable, br_ghash_ctmul32);
|
||||||
|
br_gcm_reset(&gcmCtx, config->initialization_vector, sizeof(config->initialization_vector));
|
||||||
|
if(authkeylen > 0) {
|
||||||
|
br_gcm_aad_inject(&gcmCtx, config->additional_authenticated_data, aadlen);
|
||||||
}
|
}
|
||||||
|
br_gcm_flip(&gcmCtx);
|
||||||
|
br_gcm_run(&gcmCtx, 0, (void*) (ptr), len - authkeylen - 5); // 5 == security tag and frame counter
|
||||||
|
if(authkeylen > 0 && br_gcm_check_tag_trunc(&gcmCtx, config->authentication_tag, authkeylen) != 1) {
|
||||||
|
return HDLC_ENCRYPTION_AUTH_FAILED;
|
||||||
|
}
|
||||||
|
#elif defined(ESP32)
|
||||||
|
uint8_t cipher_text[len - authkeylen - 5];
|
||||||
|
memcpy(cipher_text, ptr, len - authkeylen - 5);
|
||||||
|
|
||||||
#if defined(ESP8266)
|
mbedtls_gcm_context m_ctx;
|
||||||
br_gcm_context gcmCtx;
|
mbedtls_gcm_init(&m_ctx);
|
||||||
br_aes_ct_ctr_keys bc;
|
int success = mbedtls_gcm_setkey(&m_ctx, MBEDTLS_CIPHER_ID_AES, config->encryption_key, 128);
|
||||||
br_aes_ct_ctr_init(&bc, config->encryption_key, 16);
|
if (0 != success) {
|
||||||
br_gcm_init(&gcmCtx, &bc.vtable, br_ghash_ctmul32);
|
return HDLC_ENCRYPTION_KEY_FAILED;
|
||||||
br_gcm_reset(&gcmCtx, config->initialization_vector, sizeof(config->initialization_vector));
|
}
|
||||||
if(authkeylen > 0) {
|
if (0 < authkeylen) {
|
||||||
br_gcm_aad_inject(&gcmCtx, config->additional_authenticated_data, aadlen);
|
success = mbedtls_gcm_auth_decrypt(&m_ctx, sizeof(cipher_text), config->initialization_vector, sizeof(config->initialization_vector),
|
||||||
}
|
config->additional_authenticated_data, aadlen, config->authentication_tag, authkeylen,
|
||||||
br_gcm_flip(&gcmCtx);
|
cipher_text, (unsigned char*)(ptr));
|
||||||
br_gcm_run(&gcmCtx, 0, (void*) (ptr), len - authkeylen - 5); // 5 == security tag and frame counter
|
if (authkeylen > 0 && success == MBEDTLS_ERR_GCM_AUTH_FAILED) {
|
||||||
if(authkeylen > 0 && br_gcm_check_tag_trunc(&gcmCtx, config->authentication_tag, authkeylen) != 1) {
|
mbedtls_gcm_free(&m_ctx);
|
||||||
return HDLC_ENCRYPTION_AUTH_FAILED;
|
return HDLC_ENCRYPTION_AUTH_FAILED;
|
||||||
|
} else if(success == MBEDTLS_ERR_GCM_BAD_INPUT) {
|
||||||
|
mbedtls_gcm_free(&m_ctx);
|
||||||
|
return HDLC_ENCRYPTION_DECRYPT_FAILED;
|
||||||
}
|
}
|
||||||
#elif defined(ESP32)
|
|
||||||
uint8_t cipher_text[len - authkeylen - 5];
|
|
||||||
memcpy(cipher_text, ptr, len - authkeylen - 5);
|
|
||||||
|
|
||||||
mbedtls_gcm_context m_ctx;
|
|
||||||
mbedtls_gcm_init(&m_ctx);
|
|
||||||
int success = mbedtls_gcm_setkey(&m_ctx, MBEDTLS_CIPHER_ID_AES, config->encryption_key, 128);
|
|
||||||
if (0 != success) {
|
|
||||||
return HDLC_ENCRYPTION_KEY_FAILED;
|
|
||||||
}
|
|
||||||
if (0 < authkeylen) {
|
|
||||||
success = mbedtls_gcm_auth_decrypt(&m_ctx, sizeof(cipher_text), config->initialization_vector, sizeof(config->initialization_vector),
|
|
||||||
config->additional_authenticated_data, aadlen, config->authentication_tag, authkeylen,
|
|
||||||
cipher_text, (unsigned char*)(ptr));
|
|
||||||
if (authkeylen > 0 && success == MBEDTLS_ERR_GCM_AUTH_FAILED) {
|
|
||||||
mbedtls_gcm_free(&m_ctx);
|
|
||||||
return HDLC_ENCRYPTION_AUTH_FAILED;
|
|
||||||
} else if(success == MBEDTLS_ERR_GCM_BAD_INPUT) {
|
|
||||||
mbedtls_gcm_free(&m_ctx);
|
|
||||||
return HDLC_ENCRYPTION_DECRYPT_FAILED;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
success = mbedtls_gcm_starts(&m_ctx, MBEDTLS_GCM_DECRYPT, config->initialization_vector, sizeof(config->initialization_vector),NULL, 0);
|
|
||||||
if (0 != success) {
|
|
||||||
mbedtls_gcm_free(&m_ctx);
|
|
||||||
return HDLC_ENCRYPTION_DECRYPT_FAILED;
|
|
||||||
}
|
|
||||||
success = mbedtls_gcm_update(&m_ctx, sizeof(cipher_text), cipher_text, (unsigned char*)(ptr));
|
|
||||||
if (0 != success) {
|
|
||||||
mbedtls_gcm_free(&m_ctx);
|
|
||||||
return HDLC_ENCRYPTION_DECRYPT_FAILED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mbedtls_gcm_free(&m_ctx);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
HDLCADPU* adpu = (HDLCADPU*) (ptr);
|
|
||||||
ptr += sizeof *adpu;
|
|
||||||
|
|
||||||
// ADPU timestamp
|
|
||||||
CosemData* dateTime = (CosemData*) ptr;
|
|
||||||
if(dateTime->base.type == CosemTypeOctetString) {
|
|
||||||
if(dateTime->base.length == 0x0C) {
|
|
||||||
memcpy(timestamp, ptr+1, dateTime->base.length);
|
|
||||||
}
|
|
||||||
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, 13);
|
|
||||||
ptr += 13;
|
|
||||||
} else {
|
} else {
|
||||||
return HDLC_TIMESTAMP_UNKNOWN;
|
success = mbedtls_gcm_starts(&m_ctx, MBEDTLS_GCM_DECRYPT, config->initialization_vector, sizeof(config->initialization_vector),NULL, 0);
|
||||||
|
if (0 != success) {
|
||||||
|
mbedtls_gcm_free(&m_ctx);
|
||||||
|
return HDLC_ENCRYPTION_DECRYPT_FAILED;
|
||||||
|
}
|
||||||
|
success = mbedtls_gcm_update(&m_ctx, sizeof(cipher_text), cipher_text, (unsigned char*)(ptr));
|
||||||
|
if (0 != success) {
|
||||||
|
mbedtls_gcm_free(&m_ctx);
|
||||||
|
return HDLC_ENCRYPTION_DECRYPT_FAILED;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
mbedtls_gcm_free(&m_ctx);
|
||||||
|
#endif
|
||||||
|
|
||||||
return ptr-d;
|
return ptr-d;
|
||||||
}
|
|
||||||
|
|
||||||
// Unknown payload
|
|
||||||
return HDLC_UNKNOWN_DATA;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t mbusChecksum(const uint8_t* p, int len) {
|
uint8_t mbusChecksum(const uint8_t* p, int len) {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
#define HDLC_ENCRYPTION_AUTH_FAILED -91
|
#define HDLC_ENCRYPTION_AUTH_FAILED -91
|
||||||
#define HDLC_ENCRYPTION_KEY_FAILED -92
|
#define HDLC_ENCRYPTION_KEY_FAILED -92
|
||||||
#define HDLC_ENCRYPTION_DECRYPT_FAILED -93
|
#define HDLC_ENCRYPTION_DECRYPT_FAILED -93
|
||||||
|
#define HDLC_ENCRYPTION_INVALID -98
|
||||||
#define HDLC_TIMESTAMP_UNKNOWN -99
|
#define HDLC_TIMESTAMP_UNKNOWN -99
|
||||||
|
|
||||||
#define MBUS_START 0x68
|
#define MBUS_START 0x68
|
||||||
@@ -158,7 +159,8 @@ typedef union {
|
|||||||
} CosemData;
|
} CosemData;
|
||||||
|
|
||||||
void mbus_hexdump(const uint8_t* buf, int len);
|
void mbus_hexdump(const uint8_t* buf, int len);
|
||||||
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);
|
||||||
|
int mbus_decrypt(const uint8_t* d, int length, HDLCConfig* config);
|
||||||
|
|
||||||
uint8_t mbusChecksum(const uint8_t* p, int len);
|
uint8_t mbusChecksum(const uint8_t* p, int len);
|
||||||
|
|
||||||
|
|||||||
@@ -91,34 +91,39 @@ bool EntsoeApi::loop() {
|
|||||||
|
|
||||||
time_t t = time(nullptr);
|
time_t t = time(nullptr);
|
||||||
if(t < BUILD_EPOCH) return false;
|
if(t < BUILD_EPOCH) return false;
|
||||||
|
|
||||||
|
bool ret = false;
|
||||||
tmElements_t tm;
|
tmElements_t tm;
|
||||||
|
breakTime(tz->toLocal(t), tm);
|
||||||
|
if(currentHour != tm.Hour) {
|
||||||
|
currentHour = tm.Hour;
|
||||||
|
ret = today != NULL; // Only trigger MQTT publish if we have todays prices.
|
||||||
|
}
|
||||||
|
|
||||||
if(midnightMillis == 0) {
|
if(midnightMillis == 0) {
|
||||||
if(t <= 0) return false; // NTP not ready
|
uint32_t curDayMillis = (((((tm.Hour * 60) + tm.Minute) * 60) + tm.Second) * 1000);
|
||||||
|
midnightMillis = now + (SECS_PER_DAY * 1000) - curDayMillis;
|
||||||
time_t epoch = tz->toLocal(t);
|
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Setting midnight millis %lu\n", midnightMillis);
|
||||||
|
currentDay = tm.Day;
|
||||||
breakTime(epoch, tm);
|
return false;
|
||||||
if(tm.Year > 50) { // Make sure we are in 2021 or later (years after 1970)
|
} else if(now > midnightMillis && currentDay != tm.Day) {
|
||||||
uint32_t curDayMillis = (((((tm.Hour * 60) + tm.Minute) * 60) + tm.Second) * 1000);
|
|
||||||
|
|
||||||
midnightMillis = now + (SECS_PER_DAY * 1000) - curDayMillis + 1000; // Adding 1s to ensure we have passed midnight
|
|
||||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Setting midnight millis %lu\n", midnightMillis);
|
|
||||||
}
|
|
||||||
} else if(now > midnightMillis) {
|
|
||||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Rotating price objects at %lu\n", t);
|
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Rotating price objects at %lu\n", t);
|
||||||
delete today;
|
if(today != NULL) delete today;
|
||||||
today = tomorrow;
|
if(tomorrow != NULL) {
|
||||||
tomorrow = NULL;
|
today = tomorrow;
|
||||||
|
tomorrow = NULL;
|
||||||
|
}
|
||||||
|
currentDay = tm.Day;
|
||||||
midnightMillis = 0; // Force new midnight millis calculation
|
midnightMillis = 0; // Force new midnight millis calculation
|
||||||
|
return true;
|
||||||
} else {
|
} else {
|
||||||
breakTime(t, tm);
|
breakTime(t, tm); // Break UTC to find UTC midnight
|
||||||
if(today == NULL && (lastTodayFetch == 0 || now - lastTodayFetch > 60000)) {
|
if(today == NULL && (lastTodayFetch == 0 || now - lastTodayFetch > 60000)) {
|
||||||
lastTodayFetch = now;
|
lastTodayFetch = now;
|
||||||
time_t e1 = t - (tm.Hour * 3600) - (tm.Minute * 60) - tm.Second;
|
time_t e1 = t - (tm.Hour * 3600) - (tm.Minute * 60) - tm.Second; // UTC midnight
|
||||||
time_t e2 = e1 + SECS_PER_DAY;
|
time_t e2 = e1 + SECS_PER_DAY;
|
||||||
tmElements_t d1, d2;
|
tmElements_t d1, d2;
|
||||||
breakTime(tz->toUTC(e1), d1);
|
breakTime(tz->toUTC(e1), d1); // To get day and hour for CET/CEST at UTC midnight
|
||||||
breakTime(tz->toUTC(e2), d2);
|
breakTime(tz->toUTC(e2), d2);
|
||||||
|
|
||||||
snprintf(buf, BufferSize, "%s?securityToken=%s&documentType=A44&periodStart=%04d%02d%02d%02d%02d&periodEnd=%04d%02d%02d%02d%02d&in_Domain=%s&out_Domain=%s",
|
snprintf(buf, BufferSize, "%s?securityToken=%s&documentType=A44&periodStart=%04d%02d%02d%02d%02d&periodEnd=%04d%02d%02d%02d%02d&in_Domain=%s&out_Domain=%s",
|
||||||
@@ -185,7 +190,7 @@ bool EntsoeApi::loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EntsoeApi::retrieve(const char* url, Stream* doc) {
|
bool EntsoeApi::retrieve(const char* url, Stream* doc) {
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ private:
|
|||||||
RemoteDebug* debugger;
|
RemoteDebug* debugger;
|
||||||
EntsoeConfig* config = NULL;
|
EntsoeConfig* config = NULL;
|
||||||
|
|
||||||
|
uint8_t currentDay = 0, currentHour = 0;
|
||||||
uint32_t tomorrowFetchMillis = 36000000; // Number of ms before midnight. Default fetch 10hrs before midnight (14:00 CE(S)T)
|
uint32_t tomorrowFetchMillis = 36000000; // Number of ms before midnight. Default fetch 10hrs before midnight (14:00 CE(S)T)
|
||||||
uint64_t midnightMillis = 0;
|
uint64_t midnightMillis = 0;
|
||||||
uint64_t lastTodayFetch = 0;
|
uint64_t lastTodayFetch = 0;
|
||||||
|
|||||||
@@ -92,7 +92,8 @@ bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) {
|
|||||||
float min1hr, min3hr, min6hr;
|
float min1hr, min3hr, min6hr;
|
||||||
int8_t min1hrIdx = -1, min3hrIdx = -1, min6hrIdx = -1;
|
int8_t min1hrIdx = -1, min3hrIdx = -1, min6hrIdx = -1;
|
||||||
float min = INT16_MAX, max = INT16_MIN;
|
float min = INT16_MAX, max = INT16_MIN;
|
||||||
float values[24] = {0};
|
float values[24];
|
||||||
|
for(int i = 0;i < 24; i++) values[i] = ENTSOE_NO_VALUE;
|
||||||
for(uint8_t i = 0; i < 24; i++) {
|
for(uint8_t i = 0; i < 24; i++) {
|
||||||
float val = eapi->getValueForHour(now, i);
|
float val = eapi->getValueForHour(now, i);
|
||||||
values[i] = val;
|
values[i] = val;
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccou
|
|||||||
data->getActiveImportPower(),
|
data->getActiveImportPower(),
|
||||||
ea->getUseThisHour(),
|
ea->getUseThisHour(),
|
||||||
ea->getUseToday(),
|
ea->getUseToday(),
|
||||||
ea->getCurrentThreshold()
|
ea->getCurrentThreshold(),
|
||||||
|
ea->getMonthMax()
|
||||||
);
|
);
|
||||||
return mqtt->publish(topic, json);
|
return mqtt->publish(topic, json);
|
||||||
} else if(data->getListType() == 2) {
|
} else if(data->getListType() == 2) {
|
||||||
@@ -51,7 +52,8 @@ bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccou
|
|||||||
data->getL3Voltage(),
|
data->getL3Voltage(),
|
||||||
ea->getUseThisHour(),
|
ea->getUseThisHour(),
|
||||||
ea->getUseToday(),
|
ea->getUseToday(),
|
||||||
ea->getCurrentThreshold()
|
ea->getCurrentThreshold(),
|
||||||
|
ea->getMonthMax()
|
||||||
);
|
);
|
||||||
return mqtt->publish(topic, json);
|
return mqtt->publish(topic, json);
|
||||||
} else if(data->getListType() == 3) {
|
} else if(data->getListType() == 3) {
|
||||||
@@ -83,7 +85,8 @@ bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccou
|
|||||||
data->getMeterTimestamp(),
|
data->getMeterTimestamp(),
|
||||||
ea->getUseThisHour(),
|
ea->getUseThisHour(),
|
||||||
ea->getUseToday(),
|
ea->getUseToday(),
|
||||||
ea->getCurrentThreshold()
|
ea->getCurrentThreshold(),
|
||||||
|
ea->getMonthMax()
|
||||||
);
|
);
|
||||||
return mqtt->publish(topic, json);
|
return mqtt->publish(topic, json);
|
||||||
} else if(data->getListType() == 4) {
|
} else if(data->getListType() == 4) {
|
||||||
@@ -119,7 +122,8 @@ bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccou
|
|||||||
data->getMeterTimestamp(),
|
data->getMeterTimestamp(),
|
||||||
ea->getUseThisHour(),
|
ea->getUseThisHour(),
|
||||||
ea->getUseToday(),
|
ea->getUseToday(),
|
||||||
ea->getCurrentThreshold()
|
ea->getCurrentThreshold(),
|
||||||
|
ea->getMonthMax()
|
||||||
);
|
);
|
||||||
return mqtt->publish(topic, json);
|
return mqtt->publish(topic, json);
|
||||||
}
|
}
|
||||||
@@ -161,7 +165,8 @@ bool JsonMqttHandler::publishPrices(EntsoeApi* eapi) {
|
|||||||
float min1hr, min3hr, min6hr;
|
float min1hr, min3hr, min6hr;
|
||||||
int8_t min1hrIdx = -1, min3hrIdx = -1, min6hrIdx = -1;
|
int8_t min1hrIdx = -1, min3hrIdx = -1, min6hrIdx = -1;
|
||||||
float min = INT16_MAX, max = INT16_MIN;
|
float min = INT16_MAX, max = INT16_MIN;
|
||||||
float values[24] = {0};
|
float values[24];
|
||||||
|
for(int i = 0;i < 24; i++) values[i] = ENTSOE_NO_VALUE;
|
||||||
for(uint8_t i = 0; i < 24; i++) {
|
for(uint8_t i = 0; i < 24; i++) {
|
||||||
float val = eapi->getValueForHour(now, i);
|
float val = eapi->getValueForHour(now, i);
|
||||||
values[i] = val;
|
values[i] = val;
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ bool RawMqttHandler::publish(AmsData* data, AmsData* meterState, EnergyAccountin
|
|||||||
mqtt->publish(topic + "/realtime/import/hour", String(ea->getUseThisHour(), 3));
|
mqtt->publish(topic + "/realtime/import/hour", String(ea->getUseThisHour(), 3));
|
||||||
mqtt->publish(topic + "/realtime/import/day", String(ea->getUseToday(), 2));
|
mqtt->publish(topic + "/realtime/import/day", String(ea->getUseToday(), 2));
|
||||||
mqtt->publish(topic + "/realtime/import/threshold", String(ea->getCurrentThreshold(), 10), true, 0);
|
mqtt->publish(topic + "/realtime/import/threshold", String(ea->getCurrentThreshold(), 10), true, 0);
|
||||||
|
mqtt->publish(topic + "/realtime/import/monthmax", String(ea->getMonthMax(), 3), true, 0);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,8 +104,8 @@ bool RawMqttHandler::publishPrices(EntsoeApi* eapi) {
|
|||||||
float min1hr, min3hr, min6hr;
|
float min1hr, min3hr, min6hr;
|
||||||
int8_t min1hrIdx = -1, min3hrIdx = -1, min6hrIdx = -1;
|
int8_t min1hrIdx = -1, min3hrIdx = -1, min6hrIdx = -1;
|
||||||
float min = INT16_MAX, max = INT16_MIN;
|
float min = INT16_MAX, max = INT16_MIN;
|
||||||
float values[34] = {0};
|
float values[34];
|
||||||
memset(values, ENTSOE_NO_VALUE, 34);
|
for(int i = 0;i < 34; i++) values[i] = ENTSOE_NO_VALUE;
|
||||||
for(uint8_t i = 0; i < 34; i++) {
|
for(uint8_t i = 0; i < 34; i++) {
|
||||||
float val = eapi->getValueForHour(now, i);
|
float val = eapi->getValueForHour(now, i);
|
||||||
values[i] = val;
|
values[i] = val;
|
||||||
@@ -180,7 +181,6 @@ bool RawMqttHandler::publishPrices(EntsoeApi* eapi) {
|
|||||||
float val = values[i];
|
float val = values[i];
|
||||||
if(val == ENTSOE_NO_VALUE) {
|
if(val == ENTSOE_NO_VALUE) {
|
||||||
mqtt->publish(topic + "/price/" + String(i), "", true, 0);
|
mqtt->publish(topic + "/price/" + String(i), "", true, 0);
|
||||||
break;
|
|
||||||
} else {
|
} else {
|
||||||
mqtt->publish(topic + "/price/" + String(i), String(val, 4), true, 0);
|
mqtt->publish(topic + "/price/" + String(i), String(val, 4), true, 0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"realtime" : {
|
"realtime" : {
|
||||||
"h" : %.2f,
|
"h" : %.2f,
|
||||||
"d" : %.1f,
|
"d" : %.1f,
|
||||||
"t" : %d
|
"t" : %d,
|
||||||
|
"x" : %.2f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
"realtime" : {
|
"realtime" : {
|
||||||
"h" : %.2f,
|
"h" : %.2f,
|
||||||
"d" : %.1f,
|
"d" : %.1f,
|
||||||
"t" : %d
|
"t" : %d,
|
||||||
|
"x" : %.2f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
"realtime" : {
|
"realtime" : {
|
||||||
"h" : %.2f,
|
"h" : %.2f,
|
||||||
"d" : %.1f,
|
"d" : %.1f,
|
||||||
"t" : %d
|
"t" : %d,
|
||||||
|
"x" : %.2f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
"realtime" : {
|
"realtime" : {
|
||||||
"h" : %.2f,
|
"h" : %.2f,
|
||||||
"d" : %.1f,
|
"d" : %.1f,
|
||||||
"t" : %d
|
"t" : %d,
|
||||||
|
"x" : %.2f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user