From 0d923e30d6b926a35733f3b06dfe4e291f12b5a2 Mon Sep 17 00:00:00 2001 From: Gunnar Skjold Date: Thu, 30 Mar 2023 08:59:56 +0200 Subject: [PATCH] Added another proprietary L&G --- frames/austria.raw | 2 +- frames/lng2.raw | 33 ++++++++++++++++++++++++ lib/AmsDecoder/src/GcmParser.cpp | 4 +++ src/AmsToMqttBridge.ino | 15 ++++++++++- src/LNG2.cpp | 43 ++++++++++++++++++++++++++++++++ src/LNG2.h | 37 +++++++++++++++++++++++++++ 6 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 frames/lng2.raw create mode 100644 src/LNG2.cpp create mode 100644 src/LNG2.h diff --git a/frames/austria.raw b/frames/austria.raw index ba1c2112..1e95ed80 100644 --- a/frames/austria.raw +++ b/frames/austria.raw @@ -42,4 +42,4 @@ DB 09 06 01 00 47 07 00 FF 12 00 54 02 02 0F FE 16 21 09 06 01 00 0D 07 00 FF 10 03 CF 02 02 0F FD 16 FF // Power factor 09 0C 31 37 38 32 31 30 30 31 35 31 36 35 // Meter ID -01 67 \ No newline at end of file +01 67 diff --git a/frames/lng2.raw b/frames/lng2.raw new file mode 100644 index 00000000..b76f759c --- /dev/null +++ b/frames/lng2.raw @@ -0,0 +1,33 @@ +7E +A0 76 CE FF 03 13 3C 02 E6 E7 00 + +DB +08 4C 47 5A 67 72 A9 A1 11 +5E 30 00 21 80 F7 FE B8 07 C6 +72 B1 90 AE AC 15 D0 AD 95 7B AC 13 7E 67 D8 A2 +F0 43 51 3C 63 B6 A1 89 10 AE 9A 7E 55 4A 12 49 +B9 6D EB A5 7B 57 03 69 9A BF 16 5E AD 2A 54 41 +65 5E 79 C6 95 71 92 46 A2 3F 5B 63 0D 53 96 7D +42 52 1F A3 80 1C 00 E8 E3 +A4 B3 9B 86 CB E5 2D 2C CA B0 E2 B7 +AE 4D +7E + + +0f00057e41 // UI Frame header +0c07e60c0c010c232dff800000 // Date & time +020e // Structure with 14 items +1200ec // U1 = 236 V +1200ec // U2 = 236 V +1200ec // U3 = 236 V +120000 // I1 = 0.00 A +12002e // I2 = 0.46 A +12001a // I3 = 0.26 A +060000007d // Active import = 125 W +0600000000 // Active export = 0 W +0601a96ebd // Accumulated import = 27881.149 kWh +0600001dc3 // Accumulated export = 7.619 kWh +120190 // 400 ? +120003 // 3 ? +120120 // 288 ? +09083330313337313831 // Meter ID = 30137181 diff --git a/lib/AmsDecoder/src/GcmParser.cpp b/lib/AmsDecoder/src/GcmParser.cpp index 5f9e2bd5..ad27ffa1 100644 --- a/lib/AmsDecoder/src/GcmParser.cpp +++ b/lib/AmsDecoder/src/GcmParser.cpp @@ -44,6 +44,10 @@ int8_t GCMParser::parse(uint8_t *d, DataParserContext &ctx) { ptr += 3; headersize += 3; + } else { + len = *ptr; + ptr++; + headersize++; } if(len + headersize > ctx.length) return DATA_PARSE_INCOMPLETE; diff --git a/src/AmsToMqttBridge.ino b/src/AmsToMqttBridge.ino index 161ca9bb..ed1339c7 100644 --- a/src/AmsToMqttBridge.ino +++ b/src/AmsToMqttBridge.ino @@ -70,6 +70,7 @@ ADC_MODE(ADC_VCC); #include "IEC6205621.h" #include "IEC6205675.h" #include "LNG.h" +#include "LNG2.h" #include "DataParsers.h" #include "Timezones.h" @@ -918,10 +919,22 @@ bool readHanPort() { debugV("Using application data:"); if(Debug.isActive(RemoteDebug::VERBOSE)) debugPrint((byte*) payload, 0, ctx.length); - // Rudimentary detector for L&G proprietary format + // Rudimentary detector for L&G proprietary format, this is terrible code... Fix later if(payload[0] == CosemTypeStructure && payload[2] == CosemTypeArray && payload[1] == payload[3]) { + debugV("LNG"); data = LNG(payload, meterState.getMeterType(), &meterConfig, ctx, &Debug); + } else if(payload[0] == CosemTypeStructure && + payload[2] == CosemTypeLongUnsigned && + payload[5] == CosemTypeLongUnsigned && + payload[8] == CosemTypeLongUnsigned && + payload[11] == CosemTypeLongUnsigned && + payload[14] == CosemTypeLongUnsigned && + payload[17] == CosemTypeLongUnsigned + ) { + debugV("LNG2"); + data = LNG2(payload, meterState.getMeterType(), &meterConfig, ctx, &Debug); } else { + debugV("DLMS"); // TODO: Split IEC6205675 into DataParserKaifa and DataParserObis. This way we can add other means of parsing, for those other proprietary formats data = IEC6205675(payload, meterState.getMeterType(), &meterConfig, ctx); } diff --git a/src/LNG2.cpp b/src/LNG2.cpp new file mode 100644 index 00000000..e408e4ec --- /dev/null +++ b/src/LNG2.cpp @@ -0,0 +1,43 @@ +#include "LNG2.h" + +LNG2::LNG2(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, RemoteDebug* debugger) { + CosemBasic* h = (CosemBasic*) payload; + if(h->length == 0x0e) { + Lng2Data_3p* d = (Lng2Data_3p*) payload; + this->l1voltage = ntohs(d->u1.data); + this->l2voltage = ntohs(d->u2.data); + this->l3voltage = ntohs(d->u3.data); + + this->l1current = ntohs(d->i1.data) / 100.0; + this->l2current = ntohs(d->i2.data) / 100.0; + this->l3current = ntohs(d->i3.data) / 100.0; + + this->activeImportPower = ntohl(d->activeImport.data); + this->activeExportPower = ntohl(d->activeExport.data); + this->activeImportCounter = ntohl(d->acumulatedImport.data) / 1000.0; + this->activeExportCounter = ntohl(d->accumulatedExport.data) / 1000.0; + + char str[64]; + uint8_t str_len = getString((CosemData*) &d->meterId, str); + if(str_len > 0) { + this->meterId = String(str); + } + listType = 3; + lastUpdateMillis = millis(); + } +} + +uint8_t LNG2::getString(CosemData* item, char* target) { + 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; + +} diff --git a/src/LNG2.h b/src/LNG2.h new file mode 100644 index 00000000..6f8aa174 --- /dev/null +++ b/src/LNG2.h @@ -0,0 +1,37 @@ +#ifndef _LNG2_H +#define _LNG2_H + +#include "AmsData.h" +#include "AmsConfiguration.h" +#include "DataParser.h" +#include "Cosem.h" +#include "RemoteDebug.h" + +struct Lng2Data_3p { + CosemBasic header; + CosemLongUnsigned u1; + CosemLongUnsigned u2; + CosemLongUnsigned u3; + CosemLongUnsigned i1; + CosemLongUnsigned i2; + CosemLongUnsigned i3; + CosemDLongUnsigned activeImport; + CosemDLongUnsigned activeExport; + CosemDLongUnsigned acumulatedImport; + CosemDLongUnsigned accumulatedExport; + CosemLongUnsigned x; + CosemLongUnsigned y; + CosemLongUnsigned z; + CosemString meterId; +} __attribute__((packed)); + +class LNG2 : public AmsData { +public: + LNG2(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, RemoteDebug* debugger); + +private: + uint8_t getString(CosemData* item, char* target); + +}; + +#endif