diff --git a/Makefile b/Makefile index 485a827..096b4d6 100644 --- a/Makefile +++ b/Makefile @@ -39,6 +39,7 @@ METERS_OBJS:=\ $(BUILD)/meters.o \ $(BUILD)/meter_multical21.o \ $(BUILD)/meter_multical302.o \ + $(BUILD)/meter_omnipower.o \ $(BUILD)/printer.o \ $(BUILD)/serial.o \ $(BUILD)/util.o \ diff --git a/README.md b/README.md index f6f3841..a46bace 100644 --- a/README.md +++ b/README.md @@ -26,14 +26,32 @@ Add --verbose for detailed debug information. Specifying auto as the device will automatically look for usb wmbus dongles on /dev/im871a and /dev/amb8465. -Two meter types are supported: multical21 and multical302 (multical302 is still work in progress). +The meter type: multical21 (a water meter) is supported. +The meter types: multical302 (heat) and omnipower (electricity) +are work in progress. ``` -Currently the meters are hardcoded for the European default setting that specifies what extra data -is sent in the telegrams. If someone has a non-default meter that sends other extra data, then this -will show up as a warning when a long telegram is received (but not in the short telegrams, where wrong -values might be printed instead!). -If this happens to someone, then we need to implement a way to pass the meter configuration as a parameter. +Currently the meters are hardcoded for the European default setting +that specifies what extra data is sent in the telegrams. If someone +has a non-default meter that sends other extra data, then this will +show up as a warning when a long telegram is received (but not in the +short telegrams, where wrong values might be printed instead!). If +this happens to someone, then we need to implement a way to pass the +meter configuration as a parameter. + +Actually, the mbus (and consequently the wmbus) protocol is a standard +that is self-describing. Thus in reality it should not be necessary +to supply exactly which kind of meter we expect for a given id. This +should be possible to figure out when we receive the first telegram. + +Thus, strictly speaking, it should not be necessary to specify the +exact meter type. A more generic meter type might be just "water", +"heat" or electricity. But for the moment, the separation of meter +types will remain in the code. Thus even though the meter type right +now is named multical302, the other heat meters (multical-402 and +multical-602) might be compatible as well. The same is true for the +omnipower meter type, which might include the electricity meters +Kamstrup-162 Kamstrup-382, Kamstrup-351 etc). No meter quadruplets means listen for telegram traffic and print any id heard. diff --git a/cmdline.cc b/cmdline.cc index 8ac3bd2..6896c30 100644 --- a/cmdline.cc +++ b/cmdline.cc @@ -19,6 +19,7 @@ // SOFTWARE. #include"cmdline.h" +#include"meters.h" #include"util.h" using namespace std; @@ -142,7 +143,9 @@ CommandLine *parseCommandLine(int argc, char **argv) { char *id = argv[m*4+i+2]; char *key = argv[m*4+i+3]; - if (!isValidType(type)) error("Not a valid meter type \"%s\"\n", type); + MeterType mt = toMeterType(type); + + if (mt == UNKNOWN_METER) error("Not a valid meter type \"%s\"\n", type); if (!isValidId(id)) error("Not a valid meter id \"%s\"\n", id); if (!isValidKey(key)) error("Not a valid meter key \"%s\"\n", key); c->meters.push_back(MeterInfo(name,type,id,key)); diff --git a/main.cc b/main.cc index 9ce0dba..e3640e0 100644 --- a/main.cc +++ b/main.cc @@ -51,7 +51,9 @@ int main(int argc, char **argv) " or 10m for ten minutes or 5s for five seconds.\n"); printf("Specifying auto as the device will automatically look for usb\n"); printf("wmbus dongles on /dev/im871a and /dev/amb8465\n\n"); - printf("Two meter types are supported: multical21 and multical302 (work in progress).\n\n"); + printf("The meter type: multical21 (a water meter) is supported.\n" + "The meter types: multical302 (heat) and omnipower (electricity)\n" + "are work in progress.\n\n"); exit(0); } // We want the data visible in the log file asap! @@ -116,6 +118,10 @@ int main(int argc, char **argv) m.meter = createMultical302(wmbus, m.name, m.id, m.key); verbose("(multical302) configured \"%s\" \"multical302\" \"%s\" \"%s\"\n", m.name, m.id, m.key); break; + case OMNIPOWER_METER: + m.meter = createOmnipower(wmbus, m.name, m.id, m.key); + verbose("(omnipower) configured \"%s\" \"omnipower\" \"%s\" \"%s\"\n", m.name, m.id, m.key); + break; case UNKNOWN_METER: error("No such meter type \"%s\"\n", m.type); break; diff --git a/meter_multical21.cc b/meter_multical21.cc index 81e3059..cc17cec 100644 --- a/meter_multical21.cc +++ b/meter_multical21.cc @@ -185,7 +185,8 @@ void MeterMultical21::processContent(Telegram *t) { int crc0 = t->content[0]; int crc1 = t->content[1]; - t->addExplanation(full_content, 2, "%02x%02x plcrc", crc0, crc1); + t->addExplanation(full_content, 2, "%02x%02x payload crc %02x%02x", crc0, crc1); + int frame_type = t->content[2]; t->addExplanation(full_content, 1, "%02x frame type (%s)", frame_type, frameTypeKamstrupC1(frame_type).c_str()); diff --git a/meter_multical302.cc b/meter_multical302.cc index fdd57c1..68bf8c9 100644 --- a/meter_multical302.cc +++ b/meter_multical302.cc @@ -33,7 +33,7 @@ struct MeterMultical302 : public virtual HeatMeter, public virtual MeterCommonImplementation { MeterMultical302(WMBus *bus, const char *name, const char *id, const char *key); - float totalPowerConsumption(); + float totalEnergyConsumption(); float currentPowerConsumption(); float totalVolume(); @@ -45,7 +45,7 @@ private: void handleTelegram(Telegram *t); void processContent(Telegram *t); - float total_power_ {}; + float total_energy_ {}; float current_power_ {}; float total_volume_ {}; }; @@ -56,9 +56,9 @@ MeterMultical302::MeterMultical302(WMBus *bus, const char *name, const char *id, MeterCommonImplementation::bus()->onTelegram(calll(this,handleTelegram,Telegram*)); } -float MeterMultical302::totalPowerConsumption() +float MeterMultical302::totalEnergyConsumption() { - return total_power_; + return total_energy_; } float MeterMultical302::currentPowerConsumption() @@ -117,7 +117,7 @@ void MeterMultical302::processContent(Telegram *t) { int crc0 = t->content[0]; int crc1 = t->content[1]; - t->addExplanation(full_content, 2, "%02x%02x plcrc", crc0, crc1); + t->addExplanation(full_content, 2, "%02x%02x payload crc", crc0, crc1); int frame_type = t->content[2]; t->addExplanation(full_content, 1, "%02x frame type (%s)", frame_type, frameTypeKamstrupC1(frame_type).c_str()); @@ -136,10 +136,10 @@ void MeterMultical302::processContent(Telegram *t) { t->addExplanation(full_content, 4, "%02x%02x%02x unknown", t->content[10], t->content[11], t->content[12]); - int total_power_raw = rec1val2*256*256 + rec1val1*256 + rec1val0; - total_power_ = total_power_raw; + int total_energy_raw = rec1val2*256*256 + rec1val1*256 + rec1val0; + total_energy_ = total_energy_raw; t->addExplanation(full_content, 3, "%02x%02x%02x total power (%d)", - rec1val0, rec1val1, rec1val2, total_power_raw); + rec1val0, rec1val1, rec1val2, total_energy_raw); int rec2val0 = t->content[13]; int rec2val1 = t->content[14]; @@ -185,7 +185,7 @@ void MeterMultical302::printMeterHumanReadable(FILE *output) fprintf(output, "%s\t%s\t% 3.3f kwh\t% 3.3f m3\t% 3.3f kwh\t%s\n", name().c_str(), id().c_str(), - totalPowerConsumption(), + totalEnergyConsumption(), totalVolume(), currentPowerConsumption(), datetimeOfUpdateHumanReadable().c_str()); @@ -196,7 +196,7 @@ void MeterMultical302::printMeterFields(FILE *output, char separator) fprintf(output, "%s%c%s%c%3.3f%c%3.3f%c%3.3f%c%s\n", name().c_str(), separator, id().c_str(), separator, - totalPowerConsumption(), separator, + totalEnergyConsumption(), separator, totalVolume(), separator, currentPowerConsumption(), separator, datetimeOfUpdateRobot().c_str()); @@ -218,7 +218,7 @@ void MeterMultical302::printMeterJSON(FILE *output) "}\n", name().c_str(), id().c_str(), - totalPowerConsumption(), + totalEnergyConsumption(), totalVolume(), currentPowerConsumption(), datetimeOfUpdateRobot().c_str()); diff --git a/meter_omnipower.cc b/meter_omnipower.cc new file mode 100644 index 0000000..22be783 --- /dev/null +++ b/meter_omnipower.cc @@ -0,0 +1,171 @@ +// Copyright (c) 2018 Fredrik Öhrström +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include"meters.h" +#include"meters_common_implementation.h" +#include"wmbus.h" +#include"wmbus_utils.h" +#include"util.h" + +#include +#include +#include +#include +#include + +struct MeterOmnipower : public virtual ElectricityMeter, public virtual MeterCommonImplementation { + MeterOmnipower(WMBus *bus, const char *name, const char *id, const char *key); + + float totalEnergyConsumption(); + float currentPowerConsumption(); + + void printMeterHumanReadable(FILE *output); + void printMeterFields(FILE *output, char separator); + void printMeterJSON(FILE *output); + +private: + void handleTelegram(Telegram *t); + void processContent(Telegram *t); + + float total_energy_ {}; + float current_power_ {}; +}; + +MeterOmnipower::MeterOmnipower(WMBus *bus, const char *name, const char *id, const char *key) : + MeterCommonImplementation(bus, name, id, key, OMNIPOWER_METER, MANUFACTURER_KAM, 0x04) +{ + MeterCommonImplementation::bus()->onTelegram(calll(this,handleTelegram,Telegram*)); +} + +float MeterOmnipower::totalEnergyConsumption() +{ + return total_energy_; +} + +float MeterOmnipower::currentPowerConsumption() +{ + return current_power_; +} + +void MeterOmnipower::handleTelegram(Telegram *t) { + + if (!isTelegramForMe(t)) { + // This telegram is not intended for this meter. + return; + } + + verbose("(omnipower) %s %02x%02x%02x%02x ", + name().c_str(), + t->a_field_address[0], t->a_field_address[1], t->a_field_address[2], + t->a_field_address[3]); + + if (t->a_field_device_type != 0x02) { + warning("(omnipower) expected telegram for electricity media, but got \"%s\"!\n", + mediaType(t->m_field, t->a_field_device_type).c_str()); + } + + if (t->m_field != manufacturer() || + t->a_field_version != 0x01) { + warning("(omnipower) expected telegram from KAM meter with version 0x01, but got \"%s\" version 0x2x !\n", + manufacturerFlag(t->m_field).c_str(), t->a_field_version); + } + + if (useAes()) { + vector aeskey = key(); + // Proper decryption not yet implemented! + decryptKamstrupC1(t, aeskey); + } else { + t->content = t->payload; + } + logTelegram("(omnipower) log", t->parsed, t->content); + int content_start = t->parsed.size(); + processContent(t); + if (isDebugEnabled()) { + t->explainParse("(omnipower)", content_start); + } + triggerUpdate(t); +} + +void MeterOmnipower::processContent(Telegram *t) { + vector full_content; + full_content.insert(full_content.end(), t->parsed.begin(), t->parsed.end()); + full_content.insert(full_content.end(), t->content.begin(), t->content.end()); + + int rec1dif = t->content[0]; + t->addExplanation(full_content, 1, "%02x dif (%s)", rec1dif, difType(rec1dif).c_str()); + int rec1vif = t->content[1]; + t->addExplanation(full_content, 1, "%02x vif (%s)", rec1vif, vifType(rec1vif).c_str()); + int rec1vife = t->content[2]; + t->addExplanation(full_content, 1, "%02x vife (%s)", rec1vife, vifeType(rec1vif, rec1vife).c_str()); + + int rec1val0 = t->content[3]; + int rec1val1 = t->content[4]; + int rec1val2 = t->content[5]; + int rec1val3 = t->content[6]; + + int total_energy_raw = rec1val3*256*256*256 + rec1val2*256*256 + rec1val1*256 + rec1val0; + total_energy_ = ((float)total_energy_raw)/1000.0; + t->addExplanation(full_content, 4, "%02x%02x%02x%02x total power (%d)", + rec1val0, rec1val1, rec1val2, rec1val3, total_energy_raw); +} + +ElectricityMeter *createOmnipower(WMBus *bus, const char *name, const char *id, const char *key) { + return new MeterOmnipower(bus,name,id,key); +} + +void MeterOmnipower::printMeterHumanReadable(FILE *output) +{ + fprintf(output, "%s\t%s\t% 3.3f kwh\t% 3.3f kwh\t%s\n", + name().c_str(), + id().c_str(), + totalEnergyConsumption(), + currentPowerConsumption(), + datetimeOfUpdateHumanReadable().c_str()); +} + +void MeterOmnipower::printMeterFields(FILE *output, char separator) +{ + fprintf(output, "%s%c%s%c%3.3f%c%3.3f%c%s\n", + name().c_str(), separator, + id().c_str(), separator, + totalEnergyConsumption(), separator, + currentPowerConsumption(), separator, + datetimeOfUpdateRobot().c_str()); +} + +#define Q(x,y) "\""#x"\":"#y"," +#define QS(x,y) "\""#x"\":\""#y"\"," +#define QSE(x,y) "\""#x"\":\""#y"\"" + +void MeterOmnipower::printMeterJSON(FILE *output) +{ + fprintf(output, "{media:\"electricity\",meter:\"omnipower\"," + QS(name,%s) + QS(id,%s) + Q(total_kwh,%.3f) + QS(current_kw,%.3f) + QSE(timestamp,%s) + "}\n", + name().c_str(), + id().c_str(), + totalEnergyConsumption(), + currentPowerConsumption(), + datetimeOfUpdateRobot().c_str()); +} diff --git a/meters.cc b/meters.cc index 2c6caef..ddf11fa 100644 --- a/meters.cc +++ b/meters.cc @@ -97,6 +97,10 @@ MeterType toMeterType(const char *type) { if (!strcmp(type, "multical21")) return MULTICAL21_METER; if (!strcmp(type, "multical302")) return MULTICAL302_METER; + if (!strcmp(type, "omnipower")) return OMNIPOWER_METER; + if (!strcmp(type, "water")) return MULTICAL21_METER; + if (!strcmp(type, "heat")) return MULTICAL302_METER; + if (!strcmp(type, "electricity")) return OMNIPOWER_METER; return UNKNOWN_METER; } diff --git a/meters.h b/meters.h index 84175b3..620495c 100644 --- a/meters.h +++ b/meters.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017 Fredrik Öhrström +// Copyright (c) 2018 Fredrik Öhrström // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -27,7 +27,7 @@ #include #include -#define LIST_OF_METERS X(MULTICAL21_METER)X(MULTICAL302_METER)X(UNKNOWN_METER) +#define LIST_OF_METERS X(MULTICAL21_METER)X(MULTICAL302_METER)X(OMNIPOWER_METER)X(UNKNOWN_METER) enum MeterType { #define X(name) name, @@ -79,14 +79,20 @@ struct WaterMeter : public virtual Meter { }; struct HeatMeter : public virtual Meter { - virtual float totalPowerConsumption() = 0; // kwh + virtual float totalEnergyConsumption() = 0; // kwh virtual float currentPowerConsumption() = 0; // kw virtual float totalVolume() = 0; // m3 }; +struct ElectricityMeter : public virtual Meter { + virtual float totalEnergyConsumption() = 0; // kwh + virtual float currentPowerConsumption() = 0; // kw +}; + MeterType toMeterType(const char *type); WaterMeter *createMultical21(WMBus *bus, const char *name, const char *id, const char *key); HeatMeter *createMultical302(WMBus *bus, const char *name, const char *id, const char *key); +ElectricityMeter *createOmnipower(WMBus *bus, const char *name, const char *id, const char *key); #endif diff --git a/simulation.txt b/simulation.txt index d3d72c7..a36ee3e 100644 --- a/simulation.txt +++ b/simulation.txt @@ -18,3 +18,8 @@ telegram=|25442D2C785634121b048D2093E13CBA20|0000790000000000000000000000000000| # full telegram # Test Multical302 T1 telegrams + +# Test Omnipower C1 telegrams + +telegram=|1E442D2C0771941501027A|B300108504833B08340500| +{media:"electricity",meter:"omnipower","name":"MyElectricity","id":"15947107","total_kwh":341.000,"current_kw":"0.000","timestamp":"1111-11-11T11:11:11Z"} diff --git a/test.sh b/test.sh index 7373cc7..6bb3e8f 100755 --- a/test.sh +++ b/test.sh @@ -4,8 +4,9 @@ PROG="$1" cat simulation.txt | grep '^{' > test_expected.txt $PROG --robot=json simulation.txt \ - MyTapWater multical21 76348799 "" \ - MyHeater multical302 12345678 "" \ + MyTapWater water 76348799 "" \ + MyHeater heat 12345678 "" \ + MyElectricity electricity 15947107 "" \ > test_output.txt if [ "$?" == "0" ] then diff --git a/util.cc b/util.cc index 2d0073c..e7830fd 100644 --- a/util.cc +++ b/util.cc @@ -185,13 +185,6 @@ void debug(const char* fmt, ...) { } } -bool isValidType(char *type) -{ - if (!strcmp(type, "multical21")) return true; - if (!strcmp(type, "multical302")) return true; - return false; -} - bool isValidId(char *id) { if (strlen(id) == 0) return true; @@ -359,3 +352,34 @@ int parseTime(string time) { int n = atoi(time.c_str()); return n*mul; } + +#define CRC16_EN_13757 0x3D65 + +uint16_t crc16_EN13757_per_byte(uint16_t crc, uchar b) +{ + unsigned char i; + + for (i = 0; i < 8; i++) { + + if (((crc & 0x8000) >> 8) ^ (b & 0x80)){ + crc = (crc << 1) ^ CRC16_EN_13757; + }else{ + crc = (crc << 1); + } + + b <<= 1; + } + + return crc; +} + +uint16_t crc16_EN13757(uchar *data, size_t len) +{ + uint16_t crc = 0x0000; + + for (size_t i=0; i &payload); void logTelegram(std::string intro, std::vector &header, std::vector &content); -bool isValidType(char *type); bool isValidId(char *id); bool isValidKey(char *key); @@ -71,4 +70,6 @@ void padWithZeroesTo(std::vector *content, size_t len, std::vector int parseTime(std::string time); +uint16_t crc_16_EN_13757(uchar *data, size_t len); + #endif diff --git a/wmbus.cc b/wmbus.cc index 9c1c3dc..ad288e5 100644 --- a/wmbus.cc +++ b/wmbus.cc @@ -77,12 +77,29 @@ void Telegram::verboseFields() { ci_field, ciType(ci_field).c_str()); + if (ci_field == 0x78) { + // No data error and no encryption possible. + } + + if (ci_field == 0x7a) { + // Short data header + verbose(" CC-field=%02x (%s) ACC=%02x ", + cc_field, ccType(cc_field).c_str(), + acc, + sn[3],sn[2],sn[1],sn[0]); + } + if (ci_field == 0x8d) { verbose(" CC-field=%02x (%s) ACC=%02x SN=%02x%02x%02x%02x", cc_field, ccType(cc_field).c_str(), acc, sn[3],sn[2],sn[1],sn[0]); } + if (ci_field == 0x8c) { + verbose(" CC-field=%02x (%s) ACC=%02x", + cc_field, ccType(cc_field).c_str(), + acc); + } verbose("\n"); } @@ -297,20 +314,39 @@ void Telegram::parse(vector &frame) ci_field=frame[10]; addExplanation(frame, 1, "%02x ci-field (%s)", ci_field, ciType(ci_field).c_str()); - if (ci_field == 0x8d) { + int header_size = 0; + if (ci_field == 0x78) { + header_size = 0; // And no encryption possible. + } else + if (ci_field == 0x7a) { + acc = frame[11]; + addExplanation(frame, 1, "%02x acc", acc); + status = frame[12]; + addExplanation(frame, 1, "%02x status ()", status); + configuration = frame[13]<<8 | frame[14]; + addExplanation(frame, 2, "%02x%02x configuration ()", frame[13], frame[14]); + header_size = 4; + } else + if (ci_field == 0x8d || ci_field == 0x8c) { cc_field = frame[11]; addExplanation(frame, 1, "%02x cc-field (%s)", cc_field, ccType(cc_field).c_str()); acc = frame[12]; addExplanation(frame, 1, "%02x acc", acc); - sn[0] = frame[13]; - sn[1] = frame[14]; - sn[2] = frame[15]; - sn[3] = frame[16]; - addExplanation(frame, 4, "%02x%02x%02x%02x sn", sn[0], sn[1], sn[2], sn[3]); + header_size = 2; + if (ci_field == 0x8d) { + sn[0] = frame[13]; + sn[1] = frame[14]; + sn[2] = frame[15]; + sn[3] = frame[16]; + addExplanation(frame, 4, "%02x%02x%02x%02x sn", sn[0], sn[1], sn[2], sn[3]); + header_size = 6; + } + } else { + warning("Unknown ci-field %02x\n", ci_field); } payload.clear(); - payload.insert(payload.end(), frame.begin()+17, frame.end()); + payload.insert(payload.end(), frame.begin()+(11+header_size), frame.end()); verbose("(wmbus) received telegram"); verboseFields(); debugPayload("(wmbus) frame", frame); @@ -564,6 +600,9 @@ string vifeType(int vif, int vife) //int extension = vif & 0x80; //int t = vif & 0x7f; + if (vif == 0x83 && vife == 0x3b) { + return "Forward flow contribution only"; + } if (vif == 0xff) { return "?"; } diff --git a/wmbus.h b/wmbus.h index 4b2d2ee..6dc22a4 100644 --- a/wmbus.h +++ b/wmbus.h @@ -49,22 +49,28 @@ LIST_OF_LINK_MODES using namespace std; struct Telegram { - int len; // The length of the telegram, 1 byte. - int c_field; // 1 byte (0x44=telegram, no response expected!) - int m_field; // Manufacturer 2 bytes + int len {}; // The length of the telegram, 1 byte. + int c_field {}; // 1 byte (0x44=telegram, no response expected!) + int m_field {}; // Manufacturer 2 bytes vector a_field; // A field 6 bytes // The 6 a field bytes are composed of: vector a_field_address; // Address in BCD = 8 decimal 00000000...99999999 digits. - int a_field_version; // 1 byte - int a_field_device_type; // 1 byte + int a_field_version {}; // 1 byte + int a_field_device_type {}; // 1 byte - int ci_field; // 1 byte - // When ci_field==0x8d then there are 8 extra header bytes (ELL header?) - int cc_field; // 1 byte - int acc; // 1 byte - uchar sn[4]; // 4 bytes - // That is 6 bytes (not 8), perhaps the next two bytes (the plcrc?) are - // part of this ELL header, even though they are inside the encrypted payload? + int ci_field {}; // 1 byte + + // When ci_field==0x7a then there are 4 extra header bytes, short data header + int acc {}; // 1 byte + int status {}; // 1 byte + int configuration {}; // 2 bytes + + // When ci_field==0x8d then there are 8 extra header bytes (ELL header) + int cc_field {}; // 1 byte + // acc; // 1 byte + uchar sn[4] {}; // 4 bytes + // That is 6 bytes (not 8), the next two bytes, the payload crc + // part of this ELL header, even though they are inside the encrypted payload. vector parsed; // Parsed fields vector payload; // To be parsed. @@ -74,7 +80,6 @@ struct Telegram { // The id as written on the physical meter device. string id() { return bin2hex(a_field_address); } - void parse(vector &payload); void print(); void verboseFields();