rtl_433/src/devices/ert_idm.c

646 lines
24 KiB
C

/** @file
ERT Interval Data Message (IDM) and Interval Data Message (IDM) for Net Meters.
Copyright (C) 2020 Peter Shipley <peter.shipley@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
*/
#include "decoder.h"
/*
Freq 912600155
Random information:
This file contains supports callbacks for both IDM and NetIDM Given the similarities.
Currently the code is unable to differentiate between the the two
similar protocols thus both will respond to the same packet. As
of this time I am unable to find any documentation on how to
differentiate IDM and NetIDM packets as both use identical use Sync
ID / Packet Type / length / App Version ID and CRC.
Eventually ert_idm_decode() and ert_netidm_decode() may be merged.
https://github.com/bemasher/rtlamr/wiki/Protocol
http://www.gridinsight.com/community/documentation/itron-ert-technology/
*/
#define IDM_PACKET_BYTES 92
#define IDM_PACKET_BITLEN 720
// 92 * 8
// Least significant nibble of endpoint_type is equivalent to SCM's endpoint type field
// id info from https://github.com/bemasher/rtlamr/wiki/Compatible-Meters
static char const *get_meter_type_name(uint8_t ERTType)
{
switch (ERTType & 0x0f) {
case 4:
case 5:
case 7:
case 8:
return "Electric";
case 0:
case 1:
case 2:
case 9:
case 12:
return "Gas";
case 3:
case 11:
case 13:
return "Water";
default:
return "unknown";
}
}
/**
ERT Interval Data Message (IDM).
IDM layout:
Field | Length | Offset/byte index
--- | --- | ---
pream | 2 |
Sync Word | 2 | 0
Packet Type | 1 | 2
Packet Length | 1 | 3
Hamming Code | 1 | 4
Application Version | 1 | 5
Endpoint Type | 1 | 6
Endpoint ID | 4 | 7
Consumption Interval | 1 | 11
Mod Programming State | 1 | 12
Tamper Count | 6 | 13
Async Count | 2 | 19
Power Outage Flags | 6 | 21
Last Consumption | 4 | 27
Diff Consumption | 53 | 31
Transmit Time Offset | 2 | 84
Meter ID Checksum | 2 | 86
Packet Checksum | 2 | 88
*/
static int ert_idm_decode(r_device *decoder, bitbuffer_t *bitbuffer)
{
uint8_t b[IDM_PACKET_BYTES];
data_t *data;
unsigned sync_index;
const uint8_t idm_frame_sync[] = {0x16, 0xA3, 0x1C};
uint8_t PacketTypeID;
char PacketTypeID_str[5];
uint8_t PacketLength;
// char PacketLength_str[5];
//uint8_t HammingCode;
// char HammingCode_str[5];
uint8_t ApplicationVersion;
// char ApplicationVersion_str[5];
uint8_t ERTType;
// char ERTType_str[5];
uint32_t ERTSerialNumber;
uint8_t ConsumptionIntervalCount;
uint8_t ModuleProgrammingState;
// char ModuleProgrammingState_str[5];
// uint64_t TamperCounters = 0; // 6 bytes
char TamperCounters_str[16];
uint16_t AsynchronousCounters;
// char AsynchronousCounters_str[8];
//uint64_t PowerOutageFlags = 0; // 6 bytes
char PowerOutageFlags_str[16];
uint32_t LastConsumptionCount;
uint32_t DifferentialConsumptionIntervals[47] = {0}; // 47 intervals of 9-bit unsigned integers
uint16_t TransmitTimeOffset;
uint16_t MeterIdCRC;
// char MeterIdCRC_str[8];
uint16_t PacketCRC;
// char PacketCRC_str[8];
if (bitbuffer->bits_per_row[0] > 600) {
decoder_logf(decoder, 1, __func__, "rows=%hu, row0 len=%hu", bitbuffer->num_rows, bitbuffer->bits_per_row[0]);
}
if (bitbuffer->bits_per_row[0] < IDM_PACKET_BITLEN) {
return (DECODE_ABORT_LENGTH);
}
sync_index = bitbuffer_search(bitbuffer, 0, 0, idm_frame_sync, 24);
decoder_logf(decoder, 1, __func__, "sync_index=%u", sync_index);
if (sync_index >= bitbuffer->bits_per_row[0]) {
return DECODE_ABORT_EARLY;
}
if ((bitbuffer->bits_per_row[0] - sync_index) < IDM_PACKET_BITLEN) {
return DECODE_ABORT_LENGTH;
}
// bitbuffer_debug(bitbuffer);
bitbuffer_extract_bytes(bitbuffer, 0, sync_index, b, IDM_PACKET_BITLEN);
decoder_log_bitrow(decoder, 1, __func__, b, IDM_PACKET_BITLEN, "");
// uint32_t t_16; // temp vars
// uint32_t t_32;
// uint64_t t_64;
char *p;
uint16_t crc;
// memcpy(&t_16, &b[88], 2);
// pkt_checksum = ntohs(t_16);
// pkt_checksum = (b[88] << 8 | b[89]);
PacketCRC = (b[88] << 8 | b[89]);
crc = crc16(&b[2], 86, 0x1021, 0xD895);
if (crc != PacketCRC) {
return DECODE_FAIL_MIC;
}
// snprintf(XX_str, sizeof(XX_str), "0x%02X", XX);
PacketTypeID = b[2];
snprintf(PacketTypeID_str, sizeof(PacketTypeID_str), "0x%02X", PacketTypeID);
PacketLength = b[3];
// snprintf(PacketLength_str, sizeof(PacketLength_str), "0x%02X", PacketLength);
//HammingCode = b[4];
// snprintf(HammingCode_str, sizeof(HammingCode_str), "0x%02X", HammingCode);
ApplicationVersion = b[5];
// snprintf(ApplicationVersion_str, sizeof(ApplicationVersion_str), "0x%02X", ApplicationVersion);
ERTType = b[6]; // & 0x0F;
// snprintf(ERTType_str, sizeof(ERTType_str), "0x%02X", ERTType);
// memcpy(&t_32, &b[7], 4);
// ERTSerialNumber = ntohl(t_32);
ERTSerialNumber = ((uint32_t)b[7] << 24) | (b[8] << 16) | (b[9] << 8) | (b[10]);
ConsumptionIntervalCount = b[11];
ModuleProgrammingState = b[12];
// snprintf(ModuleProgrammingState_str, sizeof(ModuleProgrammingState_str), "0x%02X", ModuleProgrammingState);
/*
http://davestech.blogspot.com/2008/02/itron-remote-read-electric-meter.html
SCM1 Counter1 Meter has been inverted
SCM1 Counter2 Meter has been removed
SCM2 Counter3 Meter detected a button-press demand reset
SCM2 Counter4 Meter has a low-battery/end-of-calendar warning
SCM3 Counter5 Meter has an error or a warning that can affect billing
SCM3 Counter6 Meter has a warning that may or may not require a site visit,
*/
p = TamperCounters_str;
strncpy(p, "0x", sizeof(TamperCounters_str));
p += 2;
for (int j = 0; j < 6; j++) {
p += sprintf(p, "%02X", b[13 + j]);
}
decoder_logf_bitrow(decoder, 2, __func__, &b[13], 6 * 8, "TamperCounters_str %s", TamperCounters_str);
AsynchronousCounters = (b[19] << 8 | b[20]);
// snprintf(AsynchronousCounters_str, sizeof(AsynchronousCounters_str), "0x%04X", AsynchronousCounters);
p = PowerOutageFlags_str;
strncpy(p, "0x", sizeof(PowerOutageFlags_str));
p += 2;
for (int j = 0; j < 6; j++) {
p += sprintf(p, "%02X", b[21 + j]);
}
decoder_logf_bitrow(decoder, 2, __func__, &b[21], 6 * 8, "PowerOutageFlags_str %s", PowerOutageFlags_str);
LastConsumptionCount = ((uint32_t)b[27] << 24) | (b[28] << 16) | (b[29] << 8) | (b[30]);
decoder_logf_bitrow(decoder, 1, __func__, &b[27], 32, "LastConsumptionCount %d", LastConsumptionCount);
// DifferentialConsumptionIntervals : 47 intervals of 9-bit unsigned integers
decoder_log_bitrow(decoder, 2, __func__, &b[31], 423, "DifferentialConsumptionIntervals");
unsigned pos = sync_index + (31 * 8);
for (int j = 0; j < 47; j++) {
uint8_t buffy[4] = {0};
bitbuffer_extract_bytes(bitbuffer, 0, pos, buffy, 9);
DifferentialConsumptionIntervals[j] = ((uint16_t)buffy[0] << 1) | (buffy[1] >> 7);
pos += 9;
}
if (decoder_verbose(decoder) > 1) {
decoder_log(decoder, 2, __func__, "DifferentialConsumptionIntervals");
for (int j = 0; j < 47; j++) {
decoder_logf(decoder, 2, __func__, "%d", DifferentialConsumptionIntervals[j]);
}
}
TransmitTimeOffset = (b[84] << 8 | b[85]);
MeterIdCRC = (b[86] << 8 | b[87]);
// snprintf(SerialNumberCRC_str, sizeof(MeterIdCRC_str), "0x%04X", MeterIdCRC);
// snprintf(PacketCRC_str, sizeof(PacketCRC_str), "0x%04X", PacketCRC);
// Least significant nibble of endpoint_type is equivalent to SCM's endpoint type field
// id info from https://github.com/bemasher/rtlamr/wiki/Compatible-Meters
char const *meter_type = get_meter_type_name(ERTType);
// decoder_logf(decoder, 0, __func__, "meter_type = %s", meter_type);
/*
Field key names and format set to match rtlamr field names
{"Time":"2020-06-25T08:22:52.404629556-04:00","Offset":1835008,"Length":229376,"Type":"IDM","Message":
{"Preamble":1431639715,"PacketTypeID":28,"PacketLength":92,"HammingCode":198,"ApplicationVersion":4,"ERTType":7,
"ERTSerialNumber":11278109,"ConsumptionIntervalCount":246,"ModuleProgrammingState":188,
"TamperCounters":"QgUWry0H","AsynchronousCounters":0,"PowerOutageFlags":"QUgmCEEF","LastConsumptionCount":339972,
"DifferentialConsumptionIntervals":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0],
"TransmitTimeOffset":476,"SerialNumberCRC":60090,"PacketCRC":31799}}
*/
/* clang-format off */
data = data_make(
"model", "", DATA_STRING, "IDM",
// "PacketTypeID", "", DATA_FORMAT, "0x%02X", DATA_INT, PacketTypeID,
"PacketTypeID", "", DATA_STRING, PacketTypeID_str,
"PacketLength", "", DATA_INT, PacketLength,
// "HammingCode", "", DATA_INT, HammingCode,
"ApplicationVersion", "", DATA_INT, ApplicationVersion,
"ERTType", "", DATA_FORMAT, "0x%02X", DATA_INT, ERTType,
// "ERTType", "", DATA_INT, ERTType,
"ERTSerialNumber", "", DATA_INT, ERTSerialNumber,
"ConsumptionIntervalCount", "", DATA_INT, ConsumptionIntervalCount,
// "ModuleProgrammingState", "", DATA_FORMAT, "0x%02X", DATA_INT, ModuleProgrammingState,
"ModuleProgrammingState", "", DATA_FORMAT, "0x%02X", DATA_INT, ModuleProgrammingState,
// "ModuleProgrammingState", "", DATA_INT, ModuleProgrammingState,
"TamperCounters", "", DATA_STRING, TamperCounters_str,
"AsynchronousCounters", "", DATA_FORMAT, "0x%02X", DATA_INT, AsynchronousCounters,
// "AsynchronousCounters", "", DATA_INT, AsynchronousCounters,
"PowerOutageFlags", "", DATA_STRING, PowerOutageFlags_str ,
"LastConsumptionCount", "", DATA_INT, LastConsumptionCount,
"DifferentialConsumptionIntervals", "", DATA_ARRAY, data_array(47, DATA_INT, DifferentialConsumptionIntervals),
"TransmitTimeOffset", "", DATA_INT, TransmitTimeOffset,
"MeterIdCRC", "", DATA_FORMAT, "0x%04X", DATA_INT, MeterIdCRC,
"PacketCRC", "", DATA_FORMAT, "0x%04X", DATA_INT, PacketCRC,
"MeterType", "Meter_Type", DATA_STRING, meter_type,
"mic", "Integrity", DATA_STRING, "CRC",
NULL);
/* clang-format on */
decoder_output_data(decoder, data);
return 1;
}
/**
Interval Data Message (IDM) for Net Meters.
NetIDM layout:
Field | Length | Offset/byte index
--- | --- | ---
Preamble | 2
Sync Word | 2 | 0
Protocol ID | 1 | 2
Packet Length | 1 | 3
Hamming Code | 1 | 4
Application Version | 1 | 5
Endpoint Type | 1 | 6
Endpoint ID | 4 | 7
Consumption Interval | 1 | 11
Programming State | 1 | 12
Tamper Count | 6 | 13 - New
Unknown_1 | 7 | 19 - New
Unknown_1 | 13 | 13 - Old
Last Generation Count | 3 | 26
Unknown_2 | 3 | 29
Last Consumption Count| 4 | 32
Differential Cons | 48 | 36 27 intervals of 14-bit unsigned integers.
Transmit Time Offset | 2 | 84
Meter ID Checksum | 2 | 86 CRC-16-CCITT of Meter ID.
Packet Checksum | 2 | 88 CRC-16-CCITT of packet starting at Packet Type.
*/
static int ert_netidm_decode(r_device *decoder, bitbuffer_t *bitbuffer)
{
uint8_t b[IDM_PACKET_BYTES];
data_t *data;
unsigned sync_index;
const uint8_t idm_frame_sync[] = {0x16, 0xA3, 0x1C};
uint8_t PacketTypeID;
char PacketTypeID_str[5];
uint8_t PacketLength;
// char PacketLength_str[5];
//uint8_t HammingCode;
// char HammingCode_str[5];
uint8_t ApplicationVersion;
// char ApplicationVersion_str[5];
uint8_t ERTType;
// char ERTType_str[5];
uint32_t ERTSerialNumber;
uint8_t ConsumptionIntervalCount;
uint8_t ModuleProgrammingState;
// char ModuleProgrammingState_str[5];
//uint8_t Unknown_field_1[13];
char Unknown_field_1_str[32];
uint32_t LastGenerationCount = 0;
//char LastGenerationCount_str[16];
//uint8_t Unknown_field_2[3];
char Unknown_field_2_str[9];
uint32_t LastConsumptionCount;
//char LastConsumptionCount_str[16];
// uint64_t TamperCounters = 0; // 6 bytes
char TamperCounters_str[16];
// uint16_t AsynchronousCounters;
// char AsynchronousCounters_str[8];
// uint64_t PowerOutageFlags = 0; // 6 bytes
// char PowerOutageFlags_str[16];
uint32_t DifferentialConsumptionIntervals[27] = {0}; // 27 intervals of 14-bit unsigned integers
uint16_t TransmitTimeOffset;
uint16_t MeterIdCRC;
// char MeterIdCRC_str[8];
uint16_t PacketCRC;
// char PacketCRC_str[8];
if (bitbuffer->bits_per_row[0] > 600) {
decoder_logf(decoder, 1, __func__, "rows=%d, row0 len=%hu", bitbuffer->num_rows, bitbuffer->bits_per_row[0]);
}
if (bitbuffer->bits_per_row[0] < IDM_PACKET_BITLEN) {
return (DECODE_ABORT_LENGTH);
}
sync_index = bitbuffer_search(bitbuffer, 0, 0, idm_frame_sync, 24);
decoder_logf(decoder, 1, __func__, "sync_index=%u", sync_index);
if (sync_index >= bitbuffer->bits_per_row[0]) {
return DECODE_ABORT_EARLY;
}
if ((bitbuffer->bits_per_row[0] - sync_index) < IDM_PACKET_BITLEN) {
return DECODE_ABORT_LENGTH;
}
bitbuffer_extract_bytes(bitbuffer, 0, sync_index, b, IDM_PACKET_BITLEN);
decoder_log_bitrow(decoder, 1, __func__, b, IDM_PACKET_BITLEN, "");
// uint32_t t_16; // temp vars
// uint32_t t_32;
// uint64_t t_64;
char *p;
uint16_t crc;
// memcpy(&t_16, &b[88], 2);
// pkt_checksum = ntohs(t_16);
// pkt_checksum = (b[88] << 8 | b[89]);
PacketCRC = (b[88] << 8 | b[89]);
crc = crc16(&b[2], 86, 0x1021, 0xD895);
if (crc != PacketCRC) {
return DECODE_FAIL_MIC;
}
// snprintf(PacketCRC_str, sizeof(PacketCRC_str), "0x%04X", PacketCRC);
PacketTypeID = b[2];
snprintf(PacketTypeID_str, sizeof(PacketTypeID_str), "0x%02X", PacketTypeID);
PacketLength = b[3];
// snprintf(PacketLength_str, sizeof(PacketLength_str), "0x%02X", PacketLength);
//HammingCode = b[4];
// snprintf(HammingCode_str, sizeof(HammingCode_str), "0x%02X", HammingCode);
ApplicationVersion = b[5];
// snprintf(ApplicationVersion_str, sizeof(ApplicationVersion_str), "0x%02X", b[5]);
ERTType = b[6]; // & 0x0f;
// snprintf(ERTType_str, sizeof(ERTType_str), "0x%02X", ERTType);
// memcpy(&t_32, &b[7], 4);
// ERTSerialNumber = ntohl(t_32);
ERTSerialNumber = ((uint32_t)b[7] << 24) | (b[8] << 16) | (b[9] << 8) | (b[10]);
ConsumptionIntervalCount = b[11];
ModuleProgrammingState = b[12];
// snprintf(ModuleProgrammingState_str, sizeof(ModuleProgrammingState_str), "0x%02X", ModuleProgrammingState);
/*
http://davestech.blogspot.com/2008/02/itron-remote-read-electric-meter.html
SCM1 Counter1 Meter has been inverted
SCM1 Counter2 Meter has been removed
SCM2 Counter3 Meter detected a button-press demand reset
SCM2 Counter4 Meter has a low-battery/end-of-calendar warning
SCM3 Counter5 Meter has an error or a warning that can affect billing
SCM3 Counter6 Meter has a warning that may or may not require a site visit,
*/
p = TamperCounters_str;
strncpy(p, "0x", sizeof(TamperCounters_str));
p += 2;
for (int j = 0; j < 6; j++) {
p += sprintf(p, "%02X", b[13 + j]);
}
decoder_logf_bitrow(decoder, 2, __func__, &b[13], 6 * 8, "TamperCounters_str %s", TamperCounters_str);
// should this be included ?
p = Unknown_field_1_str;
strncpy(p, "0x", sizeof(Unknown_field_1_str));
p += 2;
for (int j = 0; j < 7; j++) {
p += sprintf(p, "%02X", b[19 + j]);
}
decoder_logf_bitrow(decoder, 1, __func__, &b[19], 7 * 8, "Unknown_field_1 %s", Unknown_field_1_str);
// 3 bytes
LastGenerationCount = ((uint32_t)(b[26] << 16)) | (b[27] << 8) | (b[28]);
// should this be included ?
p = Unknown_field_2_str;
strncpy(p, "0x", sizeof(Unknown_field_2_str));
p += 2;
for (int j = 0; j < 3; j++) {
p += sprintf(p, "%02X", b[29 + j]);
}
decoder_logf_bitrow(decoder, 1, __func__, &b[29], 3 * 8, "Unknown_field_1 %s", Unknown_field_2_str);
LastConsumptionCount = ((uint32_t)b[32] << 24) | (b[33] << 16) | (b[34] << 8) | (b[35]);
decoder_logf_bitrow(decoder, 1, __func__, &b[32], 32, "LastConsumptionCount %d", LastConsumptionCount);
// DifferentialConsumptionIntervals[] = 27 intervals of 14-bit unsigned integers.
unsigned pos = sync_index + (36 * 8);
decoder_log_bitrow(decoder, 1, __func__, &b[36], 48 * 8, "DifferentialConsumptionIntervals");
for (int j = 0; j < 27; j++) {
uint8_t buffy[4] = {0};
bitbuffer_extract_bytes(bitbuffer, 0, pos, buffy, 14);
DifferentialConsumptionIntervals[j] = ((uint16_t)buffy[0] << 6) | (buffy[1] >> 2);
// decoder_logf_bitrow(decoder, 0, __func__, buffy, 14, "%d %d", j, DifferentialConsumptionIntervals[j]);
pos += 14;
}
if (decoder_verbose(decoder)) {
decoder_log(decoder, 1, __func__, "DifferentialConsumptionIntervals");
for (int j = 0; j < 27; j++) {
decoder_logf(decoder, 1, __func__, "%d", DifferentialConsumptionIntervals[j]);
}
}
TransmitTimeOffset = (b[84] << 8 | b[85]);
MeterIdCRC = (b[86] << 8 | b[87]);
// snprintf(MeterIdCRC_str, sizeof(MeterIdCRC_str), "0x%04X", MeterIdCRC);
// Least significant nibble of endpoint_type is equivalent to SCM's endpoint type field
// id info from https://github.com/bemasher/rtlamr/wiki/Compatible-Meters
/*
char *meter_type = get_meter_type_name(ERTType);
switch (ERTType & 0x0f) {
case 4:
case 5:
case 7:
case 8:
meter_type = "Electric";
break;
case 2:
case 9:
case 12:
meter_type = "Gas";
break;
case 11:
case 13:
meter_type = "Water";
break;
default:
meter_type = "unknown";
break;
}
*/
char const *meter_type = get_meter_type_name(ERTType);
// decoder_logf(decoder, 0, __func__, "meter_type = %s", meter_type);
/*
Field key names and format set to match rtlamr field names
{Time":"2020-06-25T08:22:08.569276915-04:00","Offset":1605632,"Length":229376,"Type":"NetIDM","Message":
{"Preamble":1431639715,"ProtocolID":28,"PacketLength":92,"HammingCode":198,"ApplicationVersion":4,"ERTType":7,
"ERTSerialNumber":1550406067,"ConsumptionIntervalCount":30,"ProgrammingState":184,"LastGeneration":125,
"LastConsumption":0,"LastConsumptionNet":2223120656,"DifferentialConsumptionIntervals":
[7695,545,2086,1475,6240,2180,4240,4616,240,7191,609,7224,1603,96,2052,12464,6152,8480,9226,352,12312,833,10292,1795,4248,4613,8416],
"TransmitTimeOffset":2145,"SerialNumberCRC":61178,"PacketCRC":37271}}
*/
/* clang-format off */
data = data_make(
"model", "", DATA_STRING, "NETIDM",
"PacketTypeID", "", DATA_STRING, PacketTypeID_str,
"PacketLength", "", DATA_INT, PacketLength,
// "HammingCode", "", DATA_FORMAT, "0x%02X", DATA_INT, HammingCode,
"ApplicationVersion", "", DATA_INT, ApplicationVersion,
"ERTType", "", DATA_FORMAT, "0x%02X", DATA_INT, ERTType,
"ERTSerialNumber", "", DATA_INT, ERTSerialNumber,
"ConsumptionIntervalCount", "", DATA_INT, ConsumptionIntervalCount,
"ModuleProgrammingState", "", DATA_FORMAT, "0x%02X", DATA_INT, ModuleProgrammingState,
// "ModuleProgrammingState", "", DATA_STRING, ModuleProgrammingState_str,
"TamperCounters", "", DATA_STRING, TamperCounters_str,
// "AsynchronousCounters", "", DATA_FORMAT, "0x%02X", DATA_INT, AsynchronousCounters,
"Unknown_field_1", "", DATA_STRING, Unknown_field_1_str,
"LastGenerationCount", "", DATA_INT, LastGenerationCount,
"Unknown_field_2", "", DATA_STRING, Unknown_field_2_str,
// "AsynchronousCounters", "", DATA_STRING, AsynchronousCounters_str,
// "PowerOutageFlags", "", DATA_STRING, PowerOutageFlags_str ,
"LastConsumptionCount", "", DATA_INT, LastConsumptionCount,
"DifferentialConsumptionIntervals", "", DATA_ARRAY, data_array(27, DATA_INT, DifferentialConsumptionIntervals),
"TransmitTimeOffset", "", DATA_INT, TransmitTimeOffset,
"MeterIdCRC", "", DATA_FORMAT, "0x%04X", DATA_INT, MeterIdCRC,
"PacketCRC", "", DATA_FORMAT, "0x%04X", DATA_INT, PacketCRC,
"MeterType", "", DATA_STRING, meter_type,
"mic", "Integrity", DATA_STRING, "CRC",
NULL);
/* clang-format on */
decoder_output_data(decoder, data);
return 1;
}
static char const *const output_fields[] = {
// Common fields
"model",
"PacketTypeID",
"PacketLength",
"HammingCode",
"ApplicationVersion",
"ERTType",
"ERTSerialNumber",
"ConsumptionIntervalCount",
"ModuleProgrammingState",
// NetIDM Only
"Unknown_field_1",
"LastGenerationCount",
"Unknown_field_2",
// IDM Only
"TamperCounters",
"AsynchronousCounters",
"PowerOutageFlags",
// Common fields
"LastConsumptionCount",
"DifferentialConsumptionIntervals",
"TransmitTimeOffset",
"MeterIdCRC",
"PacketCRC",
"MeterType",
"mic",
NULL,
};
// Freq 912600155
// -X n=L58,m=OOK_MC_ZEROBIT,s=30,l=30,g=20000,r=20000,match={24}0x16a31e,preamble={1}0x00
r_device const ert_idm = {
.name = "ERT Interval Data Message (IDM)",
.modulation = OOK_PULSE_MANCHESTER_ZEROBIT,
.short_width = 30,
.long_width = 0, // not used
.gap_limit = 20000,
.reset_limit = 20000,
// .gap_limit = 2500,
// .reset_limit = 4000,
.decode_fn = &ert_idm_decode,
.fields = output_fields,
};
r_device const ert_netidm = {
.name = "ERT Interval Data Message (IDM) for Net Meters",
.modulation = OOK_PULSE_MANCHESTER_ZEROBIT,
.short_width = 30,
.long_width = 0, // not used
.gap_limit = 20000,
.reset_limit = 20000,
// .gap_limit = 2500,
// .reset_limit = 4000,
.decode_fn = &ert_netidm_decode,
.fields = output_fields,
};