diff --git a/doc/Aidon-RJ12.pdf b/doc/Aidon-RJ12.pdf new file mode 100644 index 00000000..45404cff Binary files /dev/null and b/doc/Aidon-RJ12.pdf differ diff --git a/doc/Slimme_meter_15_a727fce1f1.pdf b/doc/Slimme_meter_15_a727fce1f1.pdf new file mode 100644 index 00000000..39e2a33b Binary files /dev/null and b/doc/Slimme_meter_15_a727fce1f1.pdf differ diff --git a/frames/Kaifa-TN-3p.raw b/frames/Kaifa-TN-3p.raw index 71b02162..74117e95 100644 --- a/frames/Kaifa-TN-3p.raw +++ b/frames/Kaifa-TN-3p.raw @@ -23,4 +23,12 @@ T FF FF DA SA SA C HC HC LD LS LQ AT AI AI AI AI AD C9 95 7E // CRC and end tag -7E A0 9A 01 02 01 10 AA A5 E6 E7 00 0F 40 00 00 00 09 0C 07 E5 03 17 02 13 00 0A FF 80 00 00 02 12 09 07 4B 46 4D 5F 30 30 31 09 10 XX XX XX XX XX XX XX XX XX XX 35 33 34 34 39 33 09 07 4D 41 33 30 34 48 34 06 00 00 09 99 06 00 00 00 00 06 00 00 00 00 06 00 00 01 67 06 00 00 03 BF 06 00 00 05 05 06 00 00 24 34 06 00 00 09 45 06 00 00 09 4F 06 00 00 09 3B 09 0C 07 E5 03 17 02 13 00 0A FF 80 00 00 06 01 34 3B 5D 06 00 00 00 00 06 00 00 09 36 06 00 3C 7A 98 DA 15 7E +7E A0 9A 01 02 01 10 AA A5 E6 E7 00 0F 40 00 00 00 09 0C 07 E5 03 17 02 13 00 0A FF 80 00 00 +02 12 09 07 4B 46 4D 5F 30 30 31 +09 10 XX XX XX XX XX XX XX XX XX XX 35 33 34 34 39 33 +09 07 4D 41 33 30 34 48 34 +06 00 00 09 99 +06 00 00 00 00 06 00 00 00 00 06 00 00 01 67 06 00 00 03 BF 06 00 00 05 05 +06 00 00 24 34 06 00 00 09 45 06 00 00 09 4F 06 00 00 09 3B +09 0C 07 E5 03 17 02 13 00 0A FF 80 00 00 06 01 34 3B 5D 06 00 00 00 00 06 00 00 09 36 06 00 3C 7A 98 DA 15 7E +7E A0 79 01 02 01 10 80 93 E6 E7 00 0F 40 00 00 00 09 0C 07 E1 09 0E 04 15 1F 14 FF 80 00 00 02 0D 09 07 4B 46 4D 5F 30 30 31 09 10 36 39 37 30 36 33 31 34 30 31 37 35 33 39 38 35 09 08 4D 41 33 30 34 48 33 45 06 00 00 04 0C 06 00 00 00 00 06 00 00 00 00 06 00 00 00 4E 06 00 00 07 C1 06 00 00 0C 9E 06 00 00 0D 7E 06 00 00 09 5F 06 00 00 00 00 06 00 00 09 66 87 96 7E \ No newline at end of file diff --git a/src/AmsConfiguration.cpp b/src/AmsConfiguration.cpp index a81ecc77..b6ffac5c 100644 --- a/src/AmsConfiguration.cpp +++ b/src/AmsConfiguration.cpp @@ -738,9 +738,15 @@ bool AmsConfiguration::relocateConfig87() { MeterConfig meter; EEPROM.begin(EEPROM_SIZE); EEPROM.get(CONFIG_METER_START_87, meter87); - meter.baud = 2400; - meter.parity = meter87.type == 3 || meter87.type == 4 ? 3 : 11; - meter.invert = false; + if(meter87.type < 5) { + meter.baud = 2400; + meter.parity = meter87.type == 3 || meter87.type == 4 ? 3 : 11; + meter.invert = false; + } else { + meter.baud = 115200; + meter.parity = 3; + meter.invert = meter87.type == 6; + } meter.distributionSystem = meter87.distributionSystem; meter.mainFuse = meter87.mainFuse; meter.productionCapacity = meter87.productionCapacity; diff --git a/src/AmsData.cpp b/src/AmsData.cpp index adeb2db7..c89604d1 100644 --- a/src/AmsData.cpp +++ b/src/AmsData.cpp @@ -1,195 +1,7 @@ #include "AmsData.h" -#include "ams/ams.h" - -uint8_t AMS_OBIS_VERSION[6] = { 1, 1, 0, 2, 129, 255 }; -uint8_t AMS_OBIS_METER_MODEL[6] = { 0, 0, 96, 1, 7, 255 }; -uint8_t AMS_OBIS_METER_ID[6] = { 0, 0, 96, 1, 0, 255 }; -uint8_t AMS_OBIS_METER_TIMESTAMP[4] = { 1, 0, 0, 255 }; -uint8_t AMS_OBIS_ACTIVE_IMPORT[4] = { 1, 7, 0, 255 }; -uint8_t AMS_OBIS_ACTIVE_IMPORT_L1[4] = { 21, 7, 0, 255 }; -uint8_t AMS_OBIS_ACTIVE_IMPORT_L2[4] = { 41, 7, 0, 255 }; -uint8_t AMS_OBIS_ACTIVE_IMPORT_L3[4] = { 61, 7, 0, 255 }; -uint8_t AMS_OBIS_ACTIVE_EXPORT[4] = { 2, 7, 0, 255 }; -uint8_t AMS_OBIS_ACTIVE_EXPORT_L1[4] = { 22, 7, 0, 255 }; -uint8_t AMS_OBIS_ACTIVE_EXPORT_L2[4] = { 42, 7, 0, 255 }; -uint8_t AMS_OBIS_ACTIVE_EXPORT_L3[4] = { 62, 7, 0, 255 }; -uint8_t AMS_OBIS_REACTIVE_IMPORT[4] = { 3, 7, 0, 255 }; -uint8_t AMS_OBIS_REACTIVE_IMPORT_L1[4] = { 23, 7, 0, 255 }; -uint8_t AMS_OBIS_REACTIVE_IMPORT_L2[4] = { 43, 7, 0, 255 }; -uint8_t AMS_OBIS_REACTIVE_IMPORT_L3[4] = { 63, 7, 0, 255 }; -uint8_t AMS_OBIS_REACTIVE_EXPORT[4] = { 4, 7, 0, 255 }; -uint8_t AMS_OBIS_REACTIVE_EXPORT_L1[4] = { 24, 7, 0, 255 }; -uint8_t AMS_OBIS_REACTIVE_EXPORT_L2[4] = { 44, 7, 0, 255 }; -uint8_t AMS_OBIS_REACTIVE_EXPORT_L3[4] = { 64, 7, 0, 255 }; -uint8_t AMS_OBIS_CURRENT[4] = { 11, 7, 0, 255 }; -uint8_t AMS_OBIS_CURRENT_L1[4] = { 31, 7, 0, 255 }; -uint8_t AMS_OBIS_CURRENT_L2[4] = { 51, 7, 0, 255 }; -uint8_t AMS_OBIS_CURRENT_L3[4] = { 71, 7, 0, 255 }; -uint8_t AMS_OBIS_VOLTAGE[4] = { 12, 7, 0, 255 }; -uint8_t AMS_OBIS_VOLTAGE_L1[4] = { 32, 7, 0, 255 }; -uint8_t AMS_OBIS_VOLTAGE_L2[4] = { 52, 7, 0, 255 }; -uint8_t AMS_OBIS_VOLTAGE_L3[4] = { 72, 7, 0, 255 }; -uint8_t AMS_OBIS_ACTIVE_IMPORT_COUNT[4] = { 1, 8, 0, 255 }; -uint8_t AMS_OBIS_ACTIVE_EXPORT_COUNT[4] = { 2, 8, 0, 255 }; -uint8_t AMS_OBIS_REACTIVE_IMPORT_COUNT[4] = { 3, 8, 0, 255 }; -uint8_t AMS_OBIS_REACTIVE_EXPORT_COUNT[4] = { 4, 8, 0, 255 }; AmsData::AmsData() {} -AmsData::AmsData(const char* d, bool substituteMissing) { - uint32_t u32; - int32_t s32; - char str[64]; - - u32 = AMS_getUnsignedNumber(AMS_OBIS_ACTIVE_IMPORT, sizeof(AMS_OBIS_ACTIVE_IMPORT), ((char *) (d))); - if(u32 != 0xFFFFFFFF) { - listType = 1; - activeImportPower = u32; - } - - meterType = AmsTypeUnknown; - CosemData* version = AMS_findObis(AMS_OBIS_VERSION, sizeof(AMS_OBIS_VERSION), d); - if(version != NULL && version->base.type == CosemTypeString) { - if(memcmp(version->str.data, "AIDON", 5) == 0) { - meterType = AmsTypeAidon; - } else if(memcmp(version->str.data, "Kamstrup", 8) == 0) { - meterType = AmsTypeKamstrup; - } - } - - u32 = AMS_getString(AMS_OBIS_VERSION, sizeof(AMS_OBIS_VERSION), ((char *) (d)), str); - if(u32 > 0) { - listId = String(str); - } - - u32 = AMS_getUnsignedNumber(AMS_OBIS_ACTIVE_EXPORT, sizeof(AMS_OBIS_ACTIVE_EXPORT), ((char *) (d))); - if(u32 != 0xFFFFFFFF) { - activeExportPower = u32; - } - - u32 = AMS_getUnsignedNumber(AMS_OBIS_REACTIVE_IMPORT, sizeof(AMS_OBIS_REACTIVE_IMPORT), ((char *) (d))); - if(u32 != 0xFFFFFFFF) { - reactiveImportPower = u32; - } - - u32 = AMS_getUnsignedNumber(AMS_OBIS_REACTIVE_EXPORT, sizeof(AMS_OBIS_REACTIVE_EXPORT), ((char *) (d))); - if(u32 != 0xFFFFFFFF) { - reactiveExportPower = u32; - } - - u32 = AMS_getUnsignedNumber(AMS_OBIS_VOLTAGE_L1, sizeof(AMS_OBIS_VOLTAGE_L1), ((char *) (d))); - if(u32 != 0xFFFFFFFF) { - listType = 2; - l1voltage = u32; - } - u32 = AMS_getUnsignedNumber(AMS_OBIS_VOLTAGE_L2, sizeof(AMS_OBIS_VOLTAGE_L2), ((char *) (d))); - if(u32 != 0xFFFFFFFF) { - listType = 2; - l2voltage = u32; - } - u32 = AMS_getUnsignedNumber(AMS_OBIS_VOLTAGE_L3, sizeof(AMS_OBIS_VOLTAGE_L3), ((char *) (d))); - if(u32 != 0xFFFFFFFF) { - listType = 2; - l3voltage = u32; - } - - s32 = AMS_getSignedNumber(AMS_OBIS_CURRENT_L1, sizeof(AMS_OBIS_CURRENT_L1), ((char *) (d))); - if(s32 != 0xFFFFFFFF) { - listType = 2; - l1current = s32; - } - s32 = AMS_getSignedNumber(AMS_OBIS_CURRENT_L2, sizeof(AMS_OBIS_CURRENT_L2), ((char *) (d))); - if(s32 != 0xFFFFFFFF) { - listType = 2; - l2current = s32; - } - s32 = AMS_getSignedNumber(AMS_OBIS_CURRENT_L3, sizeof(AMS_OBIS_CURRENT_L3), ((char *) (d))); - if(s32 != 0xFFFFFFFF) { - listType = 2; - l3current = s32; - } - - if(listType == 2) { - int vdiv = 1; - 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(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 = AMS_getUnsignedNumber(AMS_OBIS_ACTIVE_IMPORT_COUNT, sizeof(AMS_OBIS_ACTIVE_IMPORT_COUNT), ((char *) (d))); - if(u32 != 0xFFFFFFFF) { - listType = 3; - activeImportCounter = u32 / 100.0; - } - u32 = AMS_getUnsignedNumber(AMS_OBIS_ACTIVE_EXPORT_COUNT, sizeof(AMS_OBIS_ACTIVE_EXPORT_COUNT), ((char *) (d))); - if(u32 != 0xFFFFFFFF) { - listType = 3; - activeExportCounter = u32 / 100.0; - } - u32 = AMS_getUnsignedNumber(AMS_OBIS_REACTIVE_IMPORT_COUNT, sizeof(AMS_OBIS_REACTIVE_IMPORT_COUNT), ((char *) (d))); - if(u32 != 0xFFFFFFFF) { - listType = 3; - reactiveImportCounter = u32 / 100.0; - } - u32 = AMS_getUnsignedNumber(AMS_OBIS_REACTIVE_EXPORT_COUNT, sizeof(AMS_OBIS_REACTIVE_EXPORT_COUNT), ((char *) (d))); - if(u32 != 0xFFFFFFFF) { - listType = 3; - reactiveExportCounter = u32 / 100.0; - } - - u32 = AMS_getString(AMS_OBIS_METER_MODEL, sizeof(AMS_OBIS_METER_MODEL), ((char *) (d)), str); - if(u32 > 0) { - meterModel = String(str); - } - - u32 = AMS_getString(AMS_OBIS_METER_ID, sizeof(AMS_OBIS_METER_ID), ((char *) (d)), str); - if(u32 > 0) { - meterId = String(str); - } - - time_t ts = AMS_getTimestamp(AMS_OBIS_METER_TIMESTAMP, sizeof(AMS_OBIS_METER_TIMESTAMP), ((char *) (d))); - if(ts > 0) { - meterTimestamp = ts; - } - - threePhase = l1voltage > 0 && l2voltage > 0 && l3voltage > 0; - twoPhase = (l1voltage > 0 && l2voltage > 0) || (l2voltage > 0 && l3voltage > 0) || (l3voltage > 0 && l1voltage > 0); - - if(threePhase) { - if(substituteMissing && l2current == 0) { - l2current = (((activeImportPower - activeExportPower) * sqrt(3)) - (l1voltage * l1current) - (l3voltage * l3current)) / l2voltage; - } - } - -/* - time_t packageTimestamp = 0; -*/ -/* - CosemData* model = AMS_findObis(AMS_OBIS_METER_MODEL, ((char *) (d))); - if(model != NULL && model->base.type == CosemTypeString) { - this->meterModel = String((const char*) model->str.data); - } -*/ - lastUpdateMillis = millis(); -} - void AmsData::apply(AmsData& other) { if(other.getListType() < 3) { unsigned long ms = this->lastUpdateMillis > other.getLastUpdateMillis() ? 0 : other.getLastUpdateMillis() - this->lastUpdateMillis; diff --git a/src/AmsData.h b/src/AmsData.h index 8d96f8b3..c55d315d 100644 --- a/src/AmsData.h +++ b/src/AmsData.h @@ -8,13 +8,15 @@ enum AmsType { AmsTypeAidon = 0x01, AmsTypeKaifa = 0x02, AmsTypeKamstrup = 0x03, + AmsTypeIskra = 0x08, + AmsTypeLandis = 0x09, + AmsTypeSagemcom = 0x0A, AmsTypeUnknown = 0xFF }; class AmsData { public: AmsData(); - AmsData(const char* d, bool substituteMissing); void apply(AmsData& other); @@ -52,7 +54,7 @@ public: bool isThreePhase(); bool isTwoPhase(); -private: +protected: unsigned long lastUpdateMillis = 0; uint8_t listType = 0, meterType = AmsTypeUnknown; time_t packageTimestamp = 0; diff --git a/src/AmsToMqttBridge.ino b/src/AmsToMqttBridge.ino index 4c0da94c..bfeb1441 100644 --- a/src/AmsToMqttBridge.ino +++ b/src/AmsToMqttBridge.ino @@ -44,6 +44,9 @@ ADC_MODE(ADC_VCC); #define BUF_SIZE (1024) #include "ams/hdlc.h" +#include "IEC6205621.h" +#include "IEC6205675.h" + HwTools hw; DNSServer* dnsServer = NULL; @@ -136,16 +139,30 @@ void setup() { config.getMeterConfig(meterConfig); if(gpioConfig.hanPin == 3) { shared = true; - SerialConfig serialConfig; + #if defined(ESP8266) + SerialConfig serialConfig; + #elif defined(ESP32) + uint32_t serialConfig; + #endif; switch(meterConfig.parity) { + case 2: + serialConfig = SERIAL_7N1; + break; case 3: serialConfig = SERIAL_8N1; break; + case 10: + serialConfig = SERIAL_7E1; + break; default: serialConfig = SERIAL_8E1; break; } - Serial.begin(meterConfig.baud, serialConfig); + #if defined(ESP32) + Serial.begin(meterConfig.baud, serialConfig, -1, -1, meterConfig.invert); + #else + Serial.begin(meterConfig.baud, serialConfig, SERIAL_FULL, 1, meterConfig.invert); + #endif } if(!shared) { @@ -475,18 +492,31 @@ void setupHanPort(uint8_t pin, uint32_t baud, uint8_t parityOrdinal, bool invert if(hwSerial != NULL) { debugD("Hardware serial"); Serial.flush(); - - SerialConfig serialConfig; + #if defined(ESP8266) + SerialConfig serialConfig; + #elif defined(ESP32) + uint32_t serialConfig; + #endif switch(parityOrdinal) { + case 2: + serialConfig = SERIAL_7N1; + break; case 3: serialConfig = SERIAL_8N1; break; + case 10: + serialConfig = SERIAL_7E1; + break; default: serialConfig = SERIAL_8E1; break; } - hwSerial->begin(baud, serialConfig, SERIAL_FULL, -1, invert); + #if defined(ESP32) + hwSerial->begin(baud, serialConfig, -1, -1, invert); + #else + hwSerial->begin(baud, serialConfig, SERIAL_FULL, 1, invert); + #endif hanSerial = hwSerial; } else { debugD("Software serial"); @@ -494,9 +524,15 @@ void setupHanPort(uint8_t pin, uint32_t baud, uint8_t parityOrdinal, bool invert SoftwareSerialConfig serialConfig; switch(parityOrdinal) { + case 2: + serialConfig = SWSERIAL_7N1; + break; case 3: serialConfig = SWSERIAL_8N1; break; + case 10: + serialConfig = SWSERIAL_7E1; + break; default: serialConfig = SWSERIAL_8E1; break; @@ -592,69 +628,101 @@ void mqttMessageReceived(String &topic, String &payload) } HDLCConfig* hc = NULL; -int currentMeterType = 0; +int currentMeterType = -1; void readHanPort() { - uint8_t buf[BUF_SIZE]; if(!hanSerial->available()) return; - size_t len = hanSerial->readBytes(buf, BUF_SIZE); // TODO: read one byte at the time. This blocks up the GUI - if(len > 0) { - int pos = HDLC_validate((uint8_t *) buf, len, hc); - if(pos == HDLC_ENCRYPTION_CONFIG_MISSING) { - hc = new HDLCConfig(); - memcpy(hc->encryption_key, meterConfig.encryptionKey, 16); - memcpy(hc->authentication_key, meterConfig.authenticationKey, 16); - } - if(Debug.isActive(RemoteDebug::DEBUG)) { - debugD("Frame dump:"); - debugPrint(buf, 0, len); - if(hc != NULL) { - debugD("System title:"); - debugPrint(hc->system_title, 0, 8); - debugD("Initialization vector:"); - debugPrint(hc->initialization_vector, 0, 12); - debugD("Additional authenticated data:"); - debugPrint(hc->additional_authenticated_data, 0, 17); - debugD("Authentication tag:"); - debugPrint(hc->authentication_tag, 0, 8); + + if(currentMeterType == -1) { + while(hanSerial->available()) hanSerial->read(); + currentMeterType = 0; + return; + } + if(currentMeterType == 0) { + uint8_t flag = hanSerial->read(); + if(flag == 0x7E) currentMeterType = 1; + else currentMeterType = 2; + while(hanSerial->available()) hanSerial->read(); + return; + } + AmsData data; + if(currentMeterType == 1) { + uint8_t buf[BUF_SIZE]; + size_t len = hanSerial->readBytes(buf, BUF_SIZE); // TODO: read one byte at the time. This blocks up the GUI + if(len > 0) { + int pos = HDLC_validate((uint8_t *) buf, len, hc); + if(pos == HDLC_ENCRYPTION_CONFIG_MISSING) { + hc = new HDLCConfig(); + memcpy(hc->encryption_key, meterConfig.encryptionKey, 16); + memcpy(hc->authentication_key, meterConfig.authenticationKey, 16); } - } - if(pos >= 0) { - debugI("Valid HDLC, start at %d", pos); - if(!hw.ledBlink(LED_GREEN, 1)) - hw.ledBlink(LED_INTERNAL, 1); - AmsData data = AmsData(((char *) (buf)) + pos, true); - if(data.getListType() > 0) { - if(mqttEnabled && mqttHandler != NULL) { - if(mqttHandler->publish(&data, &meterState)) { - if(data.getListType() == 3 && eapi != NULL) { - mqttHandler->publishPrices(eapi); - } - if(data.getListType() >= 2) { - mqttHandler->publishSystem(&hw); - } - time_t now = time(nullptr); - if(now < EPOCH_2021_01_01 && data.getListType() == 3 && !ntpEnabled) { - if(data.getMeterTimestamp() > EPOCH_2021_01_01) { - debugI("Using timestamp from meter"); - now = data.getMeterTimestamp(); - } else if(data.getPackageTimestamp() > EPOCH_2021_01_01) { - debugI("Using timestamp from meter (DLMS)"); - now = data.getPackageTimestamp(); - } - if(now > EPOCH_2021_01_01) { - timeval tv { now, 0}; - settimeofday(&tv, nullptr); - } - } - } - mqtt.loop(); - delay(10); + if(Debug.isActive(RemoteDebug::DEBUG)) { + debugD("Frame dump:"); + debugPrint(buf, 0, len); + if(hc != NULL) { + debugD("System title:"); + debugPrint(hc->system_title, 0, 8); + debugD("Initialization vector:"); + debugPrint(hc->initialization_vector, 0, 12); + debugD("Additional authenticated data:"); + debugPrint(hc->additional_authenticated_data, 0, 17); + debugD("Authentication tag:"); + debugPrint(hc->authentication_tag, 0, 8); } - meterState.apply(data); + } + if(pos >= 0) { + debugI("Valid HDLC, start at %d", pos); + data = IEC6205675(((char *) (buf)) + pos, meterState.getMeterType()); + } else { + debugW("Invalid HDLC, returned with %d", pos); + currentMeterType = 0; + return; } } else { - debugW("Invalid HDLC, returned with %d", pos); + return; } + } else if(currentMeterType == 2) { + String payload = hanSerial->readString(); + data = IEC6205621(payload); + if(data.getListType() == 0) { + currentMeterType = 1; + } else { + if(Debug.isActive(RemoteDebug::DEBUG)) { + debugD("Frame dump: %d", payload.length()); + debugD("%s", payload.c_str()); + } + } + } + + if(data.getListType() > 0) { + if(!hw.ledBlink(LED_GREEN, 1)) + hw.ledBlink(LED_INTERNAL, 1); + if(mqttEnabled && mqttHandler != NULL) { + if(mqttHandler->publish(&data, &meterState)) { + if(data.getListType() == 3 && eapi != NULL) { + mqttHandler->publishPrices(eapi); + } + if(data.getListType() >= 2) { + mqttHandler->publishSystem(&hw); + } + time_t now = time(nullptr); + if(now < EPOCH_2021_01_01 && data.getListType() == 3 && !ntpEnabled) { + if(data.getMeterTimestamp() > EPOCH_2021_01_01) { + debugI("Using timestamp from meter"); + now = data.getMeterTimestamp(); + } else if(data.getPackageTimestamp() > EPOCH_2021_01_01) { + debugI("Using timestamp from meter (DLMS)"); + now = data.getPackageTimestamp(); + } + if(now > EPOCH_2021_01_01) { + timeval tv { now, 0}; + settimeofday(&tv, nullptr); + } + } + } + mqtt.loop(); + delay(10); + } + meterState.apply(data); } } diff --git a/src/IEC6205621.cpp b/src/IEC6205621.cpp new file mode 100644 index 00000000..3ba944a3 --- /dev/null +++ b/src/IEC6205621.cpp @@ -0,0 +1,108 @@ +#include "IEC6205621.h" + +IEC6205621::IEC6205621(String payload) { + if(payload.length() < 16) + return; + + lastUpdateMillis = millis(); + listId = payload.substring(payload.startsWith("/") ? 1 : 0, payload.indexOf("\n")); + if(listId.startsWith("ADN")) { + meterType == AmsTypeAidon; + listId = listId.substring(0,4); + } else if(listId.startsWith("KFM")) { + meterType = AmsTypeKaifa; + listId = listId.substring(0,4); + } else if(listId.startsWith("KMP")) { + meterType = AmsTypeKamstrup; + listId = listId.substring(0,4); + } else if(listId.startsWith("ISk")) { + meterType = AmsTypeIskra; + listId = listId.substring(0,5); + } else if(listId.startsWith("XMX")) { + meterType = AmsTypeLandis; + listId = listId.substring(0,6); + } else if(listId.startsWith("Ene")) { + meterType = AmsTypeSagemcom; + listId = listId.substring(0,4); + } else { + meterType = AmsTypeUnknown; + listId = listId.substring(0,4); + } + + meterId = extract(payload, "96.1.0"); + if(meterId.isEmpty()) { + meterId = extract(payload, "0.0.5"); + } + + meterModel = extract(payload, "96.1.1"); + if(meterModel.isEmpty()) { + meterModel = extract(payload, "96.1.7"); + if(meterModel.isEmpty()) { + meterModel = payload.substring(payload.indexOf(listId) + listId.length(), payload.indexOf("\n")); + meterModel.trim(); + } + } + + String timestamp = extract(payload, "1.0.0"); + if(timestamp.length() > 10) { + tmElements_t tm; + tm.Year = (timestamp.substring(0,2).toInt() + 2000) - 1970; + tm.Month = timestamp.substring(4,6).toInt(); + tm.Day = timestamp.substring(2,4).toInt(); + tm.Hour = timestamp.substring(6,8).toInt(); + tm.Minute = timestamp.substring(8,10).toInt(); + tm.Second = timestamp.substring(10,12).toInt(); + meterTimestamp = makeTime(tm); // TODO: Adjust for time zone + } + + activeImportPower = (uint16_t) (extractDouble(payload, "1.7.0") * 1000); + activeExportPower = (uint16_t) (extractDouble(payload, "2.7.0") * 1000); + reactiveImportPower = (uint16_t) (extractDouble(payload, "3.7.0") * 1000); + reactiveExportPower = (uint16_t) (extractDouble(payload, "4.7.0") * 1000); + + if(activeImportPower > 0) + listType = 1; + + l1voltage = extractDouble(payload, "32.7.0"); + l2voltage = extractDouble(payload, "52.7.0"); + l3voltage = extractDouble(payload, "72.7.0"); + + l1current = extractDouble(payload, "31.7.0"); + l2current = extractDouble(payload, "51.7.0"); + l3current = extractDouble(payload, "71.7.0"); + + if(l1voltage > 0 || l2voltage > 0 || l3voltage > 0) + listType = 2; + + activeImportCounter = extractDouble(payload, "1.8.0"); + activeExportCounter = extractDouble(payload, "2.8.0"); + reactiveImportCounter = extractDouble(payload, "3.8.0"); + reactiveExportCounter = extractDouble(payload, "4.8.0"); + + if(activeImportCounter > 0 || activeExportCounter > 0 || reactiveImportCounter > 0 || reactiveExportCounter > 0) + listType = 3; + + threePhase = l1voltage > 0 && l2voltage > 0 && l3voltage > 0; + twoPhase = (l1voltage > 0 && l2voltage > 0) || (l2voltage > 0 && l3voltage > 0) || (l3voltage > 0 && l1voltage > 0); + + if(threePhase) { + if(l2current == 0 && l1current != 0 && l3current != 0) { + l2current = (((activeImportPower - activeExportPower) * sqrt(3)) - (l1voltage * l1current) - (l3voltage * l3current)) / l2voltage; + } + } +} + +String IEC6205621::extract(String payload, String obis) { + int a = payload.indexOf(String(":" + obis + "(")); + if(a > 0) { + int b = payload.indexOf(")", a); + if(b > a) { + return payload.substring(a+obis.length()+2, b); + } + } + return ""; +} + +double IEC6205621::extractDouble(String payload, String obis) { + return extract(payload, obis).toDouble(); +} diff --git a/src/IEC6205621.h b/src/IEC6205621.h new file mode 100644 index 00000000..76765af6 --- /dev/null +++ b/src/IEC6205621.h @@ -0,0 +1,14 @@ +#ifndef _IEC62056_21_H +#define _IEC62056_21_H + +#include "AmsData.h" + +class IEC6205621 : public AmsData { +public: + IEC6205621(String payload); + +private: + String extract(String payload, String obis); + double extractDouble(String payload, String obis); +}; +#endif diff --git a/src/IEC6205675.cpp b/src/IEC6205675.cpp new file mode 100644 index 00000000..7e6c28d8 --- /dev/null +++ b/src/IEC6205675.cpp @@ -0,0 +1,432 @@ +#include "IEC6205675.h" +#include "lwip/def.h" + +IEC6205675::IEC6205675(const char* d, uint8_t useMeterType) { + uint32_t u32; + int32_t s32; + char str[64]; + + u32 = getUnsignedNumber(AMS_OBIS_ACTIVE_IMPORT, sizeof(AMS_OBIS_ACTIVE_IMPORT), ((char *) (d))); + if(u32 == 0xFFFFFFFF) { + CosemData* data = getCosemDataAt(1, ((char *) (d))); + + // Kaifa special case... + if(data->base.type == CosemTypeOctetString) { + memcpy(str, data->oct.data, data->oct.length); + str[data->oct.length] = 0x00; + String listId = String(str); + if(listId.startsWith("KFM_001")) { + this->listId = listId; + meterType = AmsTypeKaifa; + + int idx = 0; + data = getCosemDataAt(idx, ((char *) (d))); + idx+=2; + if(data->base.length == 0x0D || data->base.length == 0x12) { + listType = data->base.length == 0x12 ? 3 : 2; + + data = getCosemDataAt(idx++, ((char *) (d))); + memcpy(str, data->oct.data, data->oct.length); + str[data->oct.length] = 0x00; + meterId = String(str); + + data = getCosemDataAt(idx++, ((char *) (d))); + memcpy(str, data->oct.data, data->oct.length); + str[data->oct.length] = 0x00; + meterModel = String(str); + + data = getCosemDataAt(idx++, ((char *) (d))); + activeImportPower = ntohl(data->dlu.data); + data = getCosemDataAt(idx++, ((char *) (d))); + activeExportPower = ntohl(data->dlu.data); + + data = getCosemDataAt(idx++, ((char *) (d))); + reactiveImportPower = ntohl(data->dlu.data); + data = getCosemDataAt(idx++, ((char *) (d))); + reactiveExportPower = ntohl(data->dlu.data); + + data = getCosemDataAt(idx++, ((char *) (d))); + l1current = ntohl(data->dlu.data) / 1000.0; + data = getCosemDataAt(idx++, ((char *) (d))); + l2current = ntohl(data->dlu.data) / 1000.0; + data = getCosemDataAt(idx++, ((char *) (d))); + l3current = ntohl(data->dlu.data) / 1000.0; + + data = getCosemDataAt(idx++, ((char *) (d))); + l1voltage = ntohl(data->dlu.data) / 10.0; + data = getCosemDataAt(idx++, ((char *) (d))); + l2voltage = ntohl(data->dlu.data) / 10.0; + data = getCosemDataAt(idx++, ((char *) (d))); + l3voltage = ntohl(data->dlu.data) / 10.0; + } else if(data->base.length == 0x09 || data->base.length == 0x0E) { + listType = data->base.length == 0x0E ? 3 : 2; + + data = getCosemDataAt(idx++, ((char *) (d))); + memcpy(str, data->oct.data, data->oct.length); + str[data->oct.length] = 0x00; + meterId = String(str); + + data = getCosemDataAt(idx++, ((char *) (d))); + memcpy(str, data->oct.data, data->oct.length); + str[data->oct.length] = 0x00; + meterModel = String(str); + + data = getCosemDataAt(idx++, ((char *) (d))); + activeImportPower = ntohl(data->dlu.data); + data = getCosemDataAt(idx++, ((char *) (d))); + activeExportPower = ntohl(data->dlu.data); + + data = getCosemDataAt(idx++, ((char *) (d))); + reactiveImportPower = ntohl(data->dlu.data); + data = getCosemDataAt(idx++, ((char *) (d))); + reactiveExportPower = ntohl(data->dlu.data); + + data = getCosemDataAt(idx++, ((char *) (d))); + l1current = ntohl(data->dlu.data) / 1000.0; + + data = getCosemDataAt(idx++, ((char *) (d))); + l1voltage = ntohl(data->dlu.data) / 10.0; + } + + if(listType == 3) { + data = getCosemDataAt(idx++, ((char *) (d))); + switch(data->base.type) { + case CosemTypeOctetString: { + if(data->oct.length == 0x0C) { + AmsOctetTimestamp* ts = (AmsOctetTimestamp*) data; + tmElements_t tm; + tm.Year = ntohs(ts->year) - 1970; + tm.Month = ts->month; + tm.Day = ts->dayOfMonth; + tm.Hour = ts->hour; + tm.Minute = ts->minute; + tm.Second = ts->second; + + time_t time = makeTime(tm); + int16_t deviation = ntohs(ts->deviation); + if(deviation >= -720 && deviation <= 720) { + time -= deviation * 60; + } + meterTimestamp = time; + } + } + } + + data = getCosemDataAt(idx++, ((char *) (d))); + activeImportCounter = ntohl(data->dlu.data) / 1000.0; + data = getCosemDataAt(idx++, ((char *) (d))); + activeExportCounter = ntohl(data->dlu.data) / 1000.0; + + data = getCosemDataAt(idx++, ((char *) (d))); + reactiveImportCounter = ntohl(data->dlu.data) / 1000.0; + data = getCosemDataAt(idx++, ((char *) (d))); + reactiveExportCounter = ntohl(data->dlu.data) / 1000.0; + } + + lastUpdateMillis = millis(); + } + } else if(useMeterType == AmsTypeKaifa && data->base.type == CosemTypeDLongUnsigned) { + listType = 1; + meterType = AmsTypeKaifa; + activeImportPower = ntohl(data->dlu.data); + lastUpdateMillis = millis(); + } + // Kaifa end + } else { + listType = 1; + activeImportPower = u32; + + meterType = AmsTypeUnknown; + CosemData* version = findObis(AMS_OBIS_VERSION, sizeof(AMS_OBIS_VERSION), d); + if(version != NULL && version->base.type == CosemTypeString) { + if(memcmp(version->str.data, "AIDON", 5) == 0) { + meterType = AmsTypeAidon; + } else if(memcmp(version->str.data, "Kamstrup", 8) == 0) { + meterType = AmsTypeKamstrup; + } + } + + u32 = getString(AMS_OBIS_VERSION, sizeof(AMS_OBIS_VERSION), ((char *) (d)), str); + if(u32 > 0) { + listId = String(str); + } + + u32 = getUnsignedNumber(AMS_OBIS_ACTIVE_EXPORT, sizeof(AMS_OBIS_ACTIVE_EXPORT), ((char *) (d))); + if(u32 != 0xFFFFFFFF) { + activeExportPower = u32; + } + + u32 = getUnsignedNumber(AMS_OBIS_REACTIVE_IMPORT, sizeof(AMS_OBIS_REACTIVE_IMPORT), ((char *) (d))); + if(u32 != 0xFFFFFFFF) { + reactiveImportPower = u32; + } + + u32 = getUnsignedNumber(AMS_OBIS_REACTIVE_EXPORT, sizeof(AMS_OBIS_REACTIVE_EXPORT), ((char *) (d))); + if(u32 != 0xFFFFFFFF) { + reactiveExportPower = u32; + } + + u32 = getUnsignedNumber(AMS_OBIS_VOLTAGE_L1, sizeof(AMS_OBIS_VOLTAGE_L1), ((char *) (d))); + if(u32 != 0xFFFFFFFF) { + listType = 2; + l1voltage = u32; + } + u32 = getUnsignedNumber(AMS_OBIS_VOLTAGE_L2, sizeof(AMS_OBIS_VOLTAGE_L2), ((char *) (d))); + if(u32 != 0xFFFFFFFF) { + listType = 2; + l2voltage = u32; + } + u32 = getUnsignedNumber(AMS_OBIS_VOLTAGE_L3, sizeof(AMS_OBIS_VOLTAGE_L3), ((char *) (d))); + if(u32 != 0xFFFFFFFF) { + listType = 2; + l3voltage = u32; + } + + s32 = getSignedNumber(AMS_OBIS_CURRENT_L1, sizeof(AMS_OBIS_CURRENT_L1), ((char *) (d))); + if(s32 != 0xFFFFFFFF) { + listType = 2; + l1current = s32; + } + s32 = getSignedNumber(AMS_OBIS_CURRENT_L2, sizeof(AMS_OBIS_CURRENT_L2), ((char *) (d))); + if(s32 != 0xFFFFFFFF) { + listType = 2; + l2current = s32; + } + s32 = getSignedNumber(AMS_OBIS_CURRENT_L3, sizeof(AMS_OBIS_CURRENT_L3), ((char *) (d))); + if(s32 != 0xFFFFFFFF) { + listType = 2; + l3current = s32; + } + + if(listType == 2) { + int vdiv = 1; + 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(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; + activeImportCounter = u32 / 100.0; + } + u32 = getUnsignedNumber(AMS_OBIS_ACTIVE_EXPORT_COUNT, sizeof(AMS_OBIS_ACTIVE_EXPORT_COUNT), ((char *) (d))); + if(u32 != 0xFFFFFFFF) { + listType = 3; + activeExportCounter = u32 / 100.0; + } + u32 = getUnsignedNumber(AMS_OBIS_REACTIVE_IMPORT_COUNT, sizeof(AMS_OBIS_REACTIVE_IMPORT_COUNT), ((char *) (d))); + if(u32 != 0xFFFFFFFF) { + listType = 3; + reactiveImportCounter = u32 / 100.0; + } + u32 = getUnsignedNumber(AMS_OBIS_REACTIVE_EXPORT_COUNT, sizeof(AMS_OBIS_REACTIVE_EXPORT_COUNT), ((char *) (d))); + if(u32 != 0xFFFFFFFF) { + listType = 3; + reactiveExportCounter = u32 / 100.0; + } + + u32 = getString(AMS_OBIS_METER_MODEL, sizeof(AMS_OBIS_METER_MODEL), ((char *) (d)), str); + if(u32 > 0) { + meterModel = String(str); + } else { + u32 = getString(AMS_OBIS_METER_MODEL_2, sizeof(AMS_OBIS_METER_MODEL_2), ((char *) (d)), str); + if(u32 > 0) { + meterModel = String(str); + } + } + + u32 = getString(AMS_OBIS_METER_ID, sizeof(AMS_OBIS_METER_ID), ((char *) (d)), str); + if(u32 > 0) { + meterId = String(str); + } else { + u32 = getString(AMS_OBIS_METER_ID_2, sizeof(AMS_OBIS_METER_ID_2), ((char *) (d)), str); + if(u32 > 0) { + meterId = String(str); + } + } + + time_t ts = getTimestamp(AMS_OBIS_METER_TIMESTAMP, sizeof(AMS_OBIS_METER_TIMESTAMP), ((char *) (d))); + if(ts > 0) { + meterTimestamp = ts; + } + + lastUpdateMillis = millis(); + } + + threePhase = l1voltage > 0 && l2voltage > 0 && l3voltage > 0; + twoPhase = (l1voltage > 0 && l2voltage > 0) || (l2voltage > 0 && l3voltage > 0) || (l3voltage > 0 && l1voltage > 0); + + if(threePhase) { + if(l2current == 0 && l1current > 0 && l3current > 0) { + l2current = (((activeImportPower - activeExportPower) * sqrt(3)) - (l1voltage * l1current) - (l3voltage * l3current)) / l2voltage; + } + } +} + +CosemData* IEC6205675::getCosemDataAt(uint8_t index, const char* ptr) { + CosemData* item = (CosemData*) ptr; + int i = 0; + char* pos = (char*) ptr; + do { + item = (CosemData*) pos; + if(i == index) return item; + switch(item->base.type) { + case CosemTypeArray: + case CosemTypeStructure: + pos += 2; + break; + case CosemTypeOctetString: + case CosemTypeString: + pos += 2 + item->base.length; + break; + case CosemTypeLongSigned: + pos += 5; + break; + case CosemTypeLongUnsigned: + pos += 3; + break; + case CosemTypeDLongUnsigned: + pos += 5; + break; + case CosemTypeNull: + return NULL; + default: + pos += 2; + } + i++; + } while(item->base.type != HDLC_FLAG); + return NULL; +} + +CosemData* IEC6205675::findObis(uint8_t* obis, int matchlength, const char* ptr) { + CosemData* item = (CosemData*) ptr; + int ret = 0; + char* pos = (char*) ptr; + do { + item = (CosemData*) pos; + if(ret == 1) return item; + switch(item->base.type) { + case CosemTypeArray: + case CosemTypeStructure: + pos += 2; + break; + case CosemTypeOctetString: { + ret = 1; + uint8_t* found = item->oct.data; + int x = 6 - matchlength; + for(int i = x; i < 6; i++) { + if(found[i] != obis[i-x]) ret = 0; + } + } // Fallthrough + case CosemTypeString: { + pos += 2 + item->base.length; + break; + } + case CosemTypeLongSigned: + pos += 5; + break; + case CosemTypeLongUnsigned: + pos += 3; + break; + case CosemTypeDLongUnsigned: + pos += 5; + break; + case CosemTypeNull: + return NULL; + default: + pos += 2; + } + } while(item->base.type != HDLC_FLAG); + return NULL; +} + +uint8_t IEC6205675::getString(uint8_t* obis, int matchlength, const char* ptr, char* target) { + CosemData* item = findObis(obis, matchlength, ptr); + if(item != NULL) { + switch(item->base.type) { + case CosemTypeString: + memcpy(target, item->str.data, item->str.length); + target[item->str.length] = 0; + return item->str.length; + case CosemTypeOctetString: + memcpy(target, item->oct.data, item->oct.length); + target[item->oct.length] = 0; + return item->oct.length; + } + } + return 0; +} + +uint32_t IEC6205675::getSignedNumber(uint8_t* obis, int matchlength, const char* ptr) { + CosemData* item = findObis(obis, matchlength, ptr); + if(item != NULL) { + 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) { + CosemData* item = findObis(obis, matchlength, ptr); + if(item != NULL) { + switch(item->base.type) { + case CosemTypeLongUnsigned: + return ntohs(item->lu.data); + case CosemTypeDLongUnsigned: + return ntohl(item->dlu.data); + } + } + return 0xFFFFFFFF; +} + +time_t IEC6205675::getTimestamp(uint8_t* obis, int matchlength, const char* ptr) { + CosemData* item = findObis(obis, matchlength, ptr); + if(item != NULL) { + switch(item->base.type) { + case CosemTypeOctetString: { + if(item->oct.length == 0x0C) { + AmsOctetTimestamp* ts = (AmsOctetTimestamp*) item; + tmElements_t tm; + tm.Year = ntohs(ts->year) - 1970; + tm.Month = ts->month; + tm.Day = ts->dayOfMonth; + tm.Hour = ts->hour; + tm.Minute = ts->minute; + tm.Second = ts->second; + + time_t time = makeTime(tm); + int16_t deviation = ntohs(ts->deviation); + if(deviation >= -720 && deviation <= 720) { + time -= deviation * 60; + } + return time; + } + } + } + } + return 0; +} diff --git a/src/IEC6205675.h b/src/IEC6205675.h new file mode 100644 index 00000000..a28f053a --- /dev/null +++ b/src/IEC6205675.h @@ -0,0 +1,68 @@ +#ifndef _IEC62056_7_5_H +#define _IEC62056_7_5_H + +#include "AmsData.h" +#include "ams/hdlc.h" + +struct AmsOctetTimestamp { + uint16_t year; + uint8_t month; + uint8_t dayOfMonth; + uint8_t dayOfWeek; + uint8_t hour; + uint8_t minute; + uint8_t second; + uint8_t hundredths; + int16_t deviation; + uint8_t status; +} __attribute__((packed)); + +class IEC6205675 : public AmsData { +public: + IEC6205675(const char* payload, uint8_t useMeterType); + +private: + CosemData* getCosemDataAt(uint8_t index, 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); + uint32_t getSignedNumber(uint8_t* obis, int matchlength, const char* ptr); + uint32_t getUnsignedNumber(uint8_t* obis, int matchlength, const char* ptr); + time_t getTimestamp(uint8_t* obis, int matchlength, const char* ptr); + + uint8_t AMS_OBIS_VERSION[6] = { 1, 1, 0, 2, 129, 255 }; + uint8_t AMS_OBIS_METER_MODEL[4] = { 96, 1, 1, 255 }; + uint8_t AMS_OBIS_METER_MODEL_2[4] = { 96, 1, 7, 255 }; + uint8_t AMS_OBIS_METER_ID[4] = { 96, 1, 0, 255 }; + uint8_t AMS_OBIS_METER_ID_2[4] = { 0, 0, 5, 255 }; + uint8_t AMS_OBIS_METER_TIMESTAMP[4] = { 1, 0, 0, 255 }; + uint8_t AMS_OBIS_ACTIVE_IMPORT[4] = { 1, 7, 0, 255 }; + uint8_t AMS_OBIS_ACTIVE_IMPORT_L1[4] = { 21, 7, 0, 255 }; + uint8_t AMS_OBIS_ACTIVE_IMPORT_L2[4] = { 41, 7, 0, 255 }; + uint8_t AMS_OBIS_ACTIVE_IMPORT_L3[4] = { 61, 7, 0, 255 }; + uint8_t AMS_OBIS_ACTIVE_EXPORT[4] = { 2, 7, 0, 255 }; + uint8_t AMS_OBIS_ACTIVE_EXPORT_L1[4] = { 22, 7, 0, 255 }; + uint8_t AMS_OBIS_ACTIVE_EXPORT_L2[4] = { 42, 7, 0, 255 }; + uint8_t AMS_OBIS_ACTIVE_EXPORT_L3[4] = { 62, 7, 0, 255 }; + uint8_t AMS_OBIS_REACTIVE_IMPORT[4] = { 3, 7, 0, 255 }; + uint8_t AMS_OBIS_REACTIVE_IMPORT_L1[4] = { 23, 7, 0, 255 }; + uint8_t AMS_OBIS_REACTIVE_IMPORT_L2[4] = { 43, 7, 0, 255 }; + uint8_t AMS_OBIS_REACTIVE_IMPORT_L3[4] = { 63, 7, 0, 255 }; + uint8_t AMS_OBIS_REACTIVE_EXPORT[4] = { 4, 7, 0, 255 }; + uint8_t AMS_OBIS_REACTIVE_EXPORT_L1[4] = { 24, 7, 0, 255 }; + uint8_t AMS_OBIS_REACTIVE_EXPORT_L2[4] = { 44, 7, 0, 255 }; + uint8_t AMS_OBIS_REACTIVE_EXPORT_L3[4] = { 64, 7, 0, 255 }; + uint8_t AMS_OBIS_CURRENT[4] = { 11, 7, 0, 255 }; + uint8_t AMS_OBIS_CURRENT_L1[4] = { 31, 7, 0, 255 }; + uint8_t AMS_OBIS_CURRENT_L2[4] = { 51, 7, 0, 255 }; + uint8_t AMS_OBIS_CURRENT_L3[4] = { 71, 7, 0, 255 }; + uint8_t AMS_OBIS_VOLTAGE[4] = { 12, 7, 0, 255 }; + uint8_t AMS_OBIS_VOLTAGE_L1[4] = { 32, 7, 0, 255 }; + uint8_t AMS_OBIS_VOLTAGE_L2[4] = { 52, 7, 0, 255 }; + uint8_t AMS_OBIS_VOLTAGE_L3[4] = { 72, 7, 0, 255 }; + uint8_t AMS_OBIS_ACTIVE_IMPORT_COUNT[4] = { 1, 8, 0, 255 }; + uint8_t AMS_OBIS_ACTIVE_EXPORT_COUNT[4] = { 2, 8, 0, 255 }; + uint8_t AMS_OBIS_REACTIVE_IMPORT_COUNT[4] = { 3, 8, 0, 255 }; + uint8_t AMS_OBIS_REACTIVE_EXPORT_COUNT[4] = { 4, 8, 0, 255 }; + +}; +#endif diff --git a/src/ams/ams.cpp b/src/ams/ams.cpp deleted file mode 100644 index a0235069..00000000 --- a/src/ams/ams.cpp +++ /dev/null @@ -1,119 +0,0 @@ -#include "ams.h" -#include -#include "lwip/def.h" -#include "Time.h" - -time_t AMS_getTimestamp(uint8_t* obis, int matchlength, const char* ptr) { - CosemData* item = AMS_findObis(obis, matchlength, ptr); - if(item != NULL) { - switch(item->base.type) { - case CosemTypeOctetString: { - if(item->oct.length == 0x0C) { - AmsOctetTimestamp* ts = (AmsOctetTimestamp*) item; - tmElements_t tm; - tm.Year = ntohs(ts->year) - 1970; - tm.Month = ts->month; - tm.Day = ts->dayOfMonth; - tm.Hour = ts->hour; - tm.Minute = ts->minute; - tm.Second = ts->second; - - time_t time = makeTime(tm); - int16_t deviation = ntohs(ts->deviation); - if(deviation >= -720 && deviation <= 720) { - time -= deviation * 60; - } - return time; - } - } - } - } - return 0; -} - -uint8_t AMS_getString(uint8_t* obis, int matchlength, const char* ptr, char* target) { - CosemData* item = AMS_findObis(obis, matchlength, ptr); - if(item != NULL) { - switch(item->base.type) { - case CosemTypeString: - memcpy(target, item->str.data, item->str.length); - target[item->str.length] = 0; - return item->str.length; - case CosemTypeOctetString: - memcpy(target, item->oct.data, item->oct.length); - target[item->oct.length] = 0; - return item->oct.length; - } - } - return 0; -} - -uint32_t AMS_getUnsignedNumber(uint8_t* obis, int matchlength, const char* ptr) { - CosemData* item = AMS_findObis(obis, matchlength, ptr); - if(item != NULL) { - switch(item->base.type) { - case CosemTypeLongUnsigned: - return ntohs(item->lu.data); - case CosemTypeDLongUnsigned: - return ntohl(item->dlu.data); - } - } - return 0xFFFFFFFF; -} - -int32_t AMS_getSignedNumber(uint8_t* obis, int matchlength, const char* ptr) { - CosemData* item = AMS_findObis(obis, matchlength, ptr); - if(item != NULL) { - 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; -} - -CosemData* AMS_findObis(uint8_t* obis, int matchlength, const char* ptr) { - CosemData* item = (CosemData*) ptr; - int ret = 0; - char* pos = (char*) ptr; - do { - item = (CosemData*) pos; - if(ret == 1) return item; - switch(item->base.type) { - case CosemTypeArray: - case CosemTypeStructure: - pos += 2; - break; - case CosemTypeOctetString: { - ret = 1; - uint8_t* found = item->oct.data; - int x = 6 - matchlength; - for(int i = x; i < 6; i++) { - if(found[i] != obis[i-x]) ret = 0; - } - } // Fallthrough - case CosemTypeString: { - pos += 2 + item->base.length; - break; - } - case CosemTypeLongSigned: - pos += 5; - break; - case CosemTypeLongUnsigned: - pos += 3; - break; - case CosemTypeDLongUnsigned: - pos += 5; - break; - case CosemTypeNull: - return NULL; - default: - pos += 2; - } - } while(item->base.type != HDLC_FLAG); - return NULL; -} \ No newline at end of file diff --git a/src/ams/ams.h b/src/ams/ams.h deleted file mode 100644 index 69cc0c8d..00000000 --- a/src/ams/ams.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef _AMS_H -#define _AMS_H - -#include "Arduino.h" -#include "hdlc.h" - -struct AmsOctetTimestamp { - uint16_t year; - uint8_t month; - uint8_t dayOfMonth; - uint8_t dayOfWeek; - uint8_t hour; - uint8_t minute; - uint8_t second; - uint8_t hundredths; - int16_t deviation; - uint8_t status; -} __attribute__((packed)); - - -CosemData* AMS_findObis(uint8_t* obis, int matchlength, const char* ptr); -uint32_t AMS_getUnsignedNumber(uint8_t* obis, int matchlength, const char* ptr); -int32_t AMS_getSignedNumber(uint8_t* obis, int matchlength, const char* ptr); -uint8_t AMS_getString(uint8_t* obis, int matchlength, const char* ptr, char* target); -time_t AMS_getTimestamp(uint8_t* obis, int matchlength, const char* ptr); - -#endif diff --git a/src/ams/hdlc.cpp b/src/ams/hdlc.cpp index 5a1a7058..2c733f16 100644 --- a/src/ams/hdlc.cpp +++ b/src/ams/hdlc.cpp @@ -121,8 +121,8 @@ int HDLC_validate(const uint8_t* d, int len, HDLCConfig* config) { if (0 != success ) { return -92; } - success = mbedtls_gcm_auth_decrypt(&m_ctx, sizeof(cipher_text), initialization_vector, sizeof(initialization_vector), - additional_authenticated_data, sizeof(additional_authenticated_data), authentication_tag, sizeof(authentication_tag), + success = mbedtls_gcm_auth_decrypt(&m_ctx, sizeof(cipher_text), config->initialization_vector, sizeof(config->initialization_vector), + config->additional_authenticated_data, sizeof(config->additional_authenticated_data), config->authentication_tag, sizeof(config->authentication_tag), cipher_text, (unsigned char*)(d + headersize + 18)); if (0 != success) { return -91; diff --git a/src/web/AmsWebServer.cpp b/src/web/AmsWebServer.cpp index 3ea40f5e..cfb32813 100644 --- a/src/web/AmsWebServer.cpp +++ b/src/web/AmsWebServer.cpp @@ -399,9 +399,21 @@ void AmsWebServer::configMeterHtml() { case AmsTypeAidon: manufacturer = "Aidon"; break; + case AmsTypeKaifa: + manufacturer = "Kaifa"; + break; case AmsTypeKamstrup: manufacturer = "Kamstrup"; break; + case AmsTypeIskra: + manufacturer = "Iskra"; + break; + case AmsTypeLandis: + manufacturer = "Landis + Gyro"; + break; + case AmsTypeSagemcom: + manufacturer = "Sagemcom"; + break; default: manufacturer = "Unknown"; break; @@ -412,9 +424,12 @@ void AmsWebServer::configMeterHtml() { html.replace("{mid}", meterState->getMeterId()); html.replace("{b}", String(meterConfig->baud)); html.replace("{b2400}", meterConfig->baud == 2400 ? "selected" : ""); + html.replace("{b9600}", meterConfig->baud == 9600 ? "selected" : ""); html.replace("{b115200}", meterConfig->baud == 115200 ? "selected" : ""); html.replace("{c}", String(meterConfig->baud)); + html.replace("{c2}", meterConfig->parity == 2 ? "selected" : ""); html.replace("{c3}", meterConfig->parity == 3 ? "selected" : ""); + html.replace("{c10}", meterConfig->parity == 10 ? "selected" : ""); html.replace("{c11}", meterConfig->parity == 11 ? "selected" : ""); html.replace("{i}", meterConfig->invert ? "checked" : ""); html.replace("{d}", String(meterConfig->distributionSystem)); @@ -715,7 +730,7 @@ void AmsWebServer::dataJson() { mqttStatus = 3; } - char json[290]; + char json[300]; snprintf_P(json, sizeof(json), DATA_JSON, maxPwr == 0 ? meterState->isThreePhase() ? 20000 : 10000 : maxPwr, meterConfig->productionCapacity, diff --git a/web/meter.html b/web/meter.html index d6d1539e..de89eb88 100644 --- a/web/meter.html +++ b/web/meter.html @@ -36,6 +36,7 @@ @@ -46,7 +47,9 @@ Parity