* idm and netidm decoders
This commit is contained in:
Peter Shipley 2020-07-20 10:37:03 -07:00 committed by GitHub
parent a9b8a46666
commit 3777e90eea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 730 additions and 0 deletions

View file

@ -233,6 +233,9 @@ See [CONTRIBUTING.md](./docs/CONTRIBUTING.md).
[157] Missil ML0757 weather station
[158] Sharp SPC775 weather station
[159] Insteon
[160] IDM
[161] NetIDM
* Disabled by default, use -R n or -G

View file

@ -167,6 +167,8 @@
DECL(missil_ml0757) \
DECL(sharp_spc775) \
DECL(insteon) \
DECL(idm) \
DECL(netidm) \
/* Add new decoders here. */

View file

@ -598,6 +598,12 @@ Sharp SPC775 weather station
.TP
[ \fB159\fI\fP ]
Insteon
.TP
[ \fB160\fI\fP ]
IDM
.TP
[ \fB161\fI\fP ]
NetIDM
* Disabled by default, use \-R n or \-G
.SS "Input device selection"

View file

@ -89,6 +89,7 @@ add_library(r_433 STATIC
devices/honeywell_wdb.c
devices/ht680.c
devices/ibis_beacon.c
devices/idm.c
devices/ikea_sparsnas.c
devices/infactory.c
devices/inovalley-kw9015b.c

View file

@ -88,6 +88,7 @@ rtl_433_SOURCES = abuf.c \
devices/honeywell_wdb.c \
devices/ht680.c \
devices/ibis_beacon.c \
devices/idm.c \
devices/ikea_sparsnas.c \
devices/infactory.c \
devices/inovalley-kw9015b.c \

713
src/devices/idm.c Normal file
View file

@ -0,0 +1,713 @@
/** @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 idm_callback() and netidm_callback() 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 *get_meter_type_name(uint8_t ERTType) {
switch (ERTType & 0x0f) {
case 4:
case 5:
case 7:
case 8:
return("Electric");
break;
case 2:
case 9:
case 12:
return("Gas");
break;
case 11:
case 13:
return("Water");
break;
}
return("unknown");
}
/*
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 idm_callback(r_device *decoder, bitbuffer_t *bitbuffer)
{
uint8_t b[92];
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 (decoder->verbose && bitbuffer->bits_per_row[0] > 600) {
fprintf(stderr, "\n\n%s: rows=%hu, row0 len=%hu\n", __func__, bitbuffer->num_rows, bitbuffer->bits_per_row[0]);
}
if (bitbuffer->bits_per_row[0] < IDM_PACKET_BITLEN) {
// to be removed later
if (decoder->verbose && bitbuffer->bits_per_row[0] > 600) {
fprintf(stderr, "%s: %s, row len=%hu < %u\n", __func__, "DECODE_ABORT_LENGTH",
bitbuffer->bits_per_row[0], IDM_PACKET_BITLEN);
fprintf(stderr, "%s: DECODE_ABORT_LENGTH 1 %hu < %d\n", __func__, bitbuffer->bits_per_row[0], IDM_PACKET_BITLEN);
bitbuffer_print(bitbuffer);
}
return (DECODE_ABORT_LENGTH);
}
sync_index = bitbuffer_search(bitbuffer, 0, 0, idm_frame_sync, 24);
if (decoder->verbose) {
fprintf(stderr, "%s: sync_index=%d\n", __func__, sync_index);
}
if (sync_index >= bitbuffer->bits_per_row[0]) {
// to be removed later
if (decoder->verbose) {
fprintf(stderr, "%s: DECODE_ABORT_EARLY s > l\n", __func__);
bitbuffer_print(bitbuffer);
}
return DECODE_ABORT_EARLY;
}
if ((bitbuffer->bits_per_row[0] - sync_index) < IDM_PACKET_BITLEN) {
// to be removed later
if (decoder->verbose) {
fprintf(stderr, "%s: DECODE_ABORT_LENGTH 2 %d < %d\n", __func__, (bitbuffer->bits_per_row[0] - sync_index), IDM_PACKET_BITLEN);
// bitrow_printf(b, bitbuffer->bits_per_row[0], "%s bitrow_printf", __func__);
bitbuffer_print(bitbuffer);
}
return DECODE_ABORT_LENGTH;
}
// bitbuffer_debug(bitbuffer);
bitbuffer_extract_bytes(bitbuffer, 0, sync_index, b, IDM_PACKET_BITLEN);
if (decoder->verbose) {
bitrow_printf(b, IDM_PACKET_BITLEN, "%s bitrow_printf", __func__);
}
// 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 buttonpress demand reset
SCM2 Counter4 Meter has a low-battery/endofcalendar 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) - 1);
p += 2;
for (int j = 0; j < 6; j++) {
p += sprintf(p, "%02X", b[13 + j]);
}
if (decoder->verbose > 1)
bitrow_printf(&b[13], 6 * 8, "%s TamperCounters_str %s\t", __func__, 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) - 1);
p += 2;
for (int j = 0; j < 6; j++) {
p += sprintf(p, "%02X", b[21 + j]);
}
if (decoder->verbose > 1)
bitrow_printf(&b[21], 6 * 8, "%s PowerOutageFlags_str %s\t", __func__, PowerOutageFlags_str);
LastConsumptionCount = ((uint32_t)b[27] << 24) | (b[28] << 16) | (b[29] << 8) | (b[30]);
if (decoder->verbose)
bitrow_printf(&b[27], 32, "%s LastConsumptionCount %d\t", __func__, LastConsumptionCount);
// DifferentialConsumptionIntervals : 47 intervals of 9-bit unsigned integers
if (decoder->verbose > 1)
bitrow_printf(&b[31], 423, "%s DifferentialConsumptionIntervals", __func__);
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 > 1) {
fprintf(stderr, "%s DifferentialConsumptionIntervals:\n\t", __func__);
for (int j = 0; j < 47; j++) {
fprintf(stderr, "%d ", DifferentialConsumptionIntervals[j]);
}
fprintf(stderr, "\n\n");
}
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 * meter_type = get_meter_type_name(ERTType);
// fprintf(stderr, "meter_type = %s\n", 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;
}
/*
NetIDM layout:
length Offset
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 netidm_callback(r_device *decoder, bitbuffer_t *bitbuffer)
{
uint8_t b[92];
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 (decoder->verbose && bitbuffer->bits_per_row[0] > 600) {
fprintf(stderr, "\n\n%s: rows=%d, row0 len=%hu\n", __func__, bitbuffer->num_rows, bitbuffer->bits_per_row[0]);
}
if (bitbuffer->bits_per_row[0] < IDM_PACKET_BITLEN) {
// to be removed later
if (decoder->verbose && bitbuffer->bits_per_row[0] > 600) {
fprintf(stderr, "%s: %s, row len=%hu < %u\n", __func__, "DECODE_ABORT_LENGTH",
bitbuffer->bits_per_row[0], IDM_PACKET_BITLEN);
fprintf(stderr, "%s: DECODE_ABORT_LENGTH 1 %d < %d\n", __func__, bitbuffer->bits_per_row[0], IDM_PACKET_BITLEN);
}
// bitbuffer_print(bitbuffer);
return (DECODE_ABORT_LENGTH);
}
sync_index = bitbuffer_search(bitbuffer, 0, 0, idm_frame_sync, 24);
if (decoder->verbose) {
fprintf(stderr, "%s: sync_index=%d\n", __func__, sync_index);
}
if (sync_index >= bitbuffer->bits_per_row[0]) {
// to be removed later
if (decoder->verbose) {
fprintf(stderr, "%s: DECODE_ABORT_EARLY s > l\n", __func__);
bitbuffer_print(bitbuffer);
}
return DECODE_ABORT_EARLY;
}
if ((bitbuffer->bits_per_row[0] - sync_index) < IDM_PACKET_BITLEN) {
if (decoder->verbose) {
fprintf(stderr, "%s: DECODE_ABORT_LENGTH 2 %d < %d\n", __func__, (bitbuffer->bits_per_row[0] - sync_index), IDM_PACKET_BITLEN);
// bitrow_printf(b, bitbuffer->bits_per_row[0], "%s bitrow_printf", __func__);
bitbuffer_print(bitbuffer);
}
return DECODE_ABORT_LENGTH;
}
bitbuffer_extract_bytes(bitbuffer, 0, sync_index, b, IDM_PACKET_BITLEN);
if (decoder->verbose)
bitrow_printf(b, IDM_PACKET_BITLEN, "%s bitrow_printf", __func__);
// 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 buttonpress demand reset
SCM2 Counter4 Meter has a low-battery/endofcalendar 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) - 1);
p += 2;
for (int j = 0; j < 6; j++) {
p += sprintf(p, "%02X", b[13 + j]);
}
if (decoder->verbose > 1)
bitrow_printf(&b[13], 6 * 8, "%s TamperCounters_str %s\t", __func__, TamperCounters_str);
// should this be included ?
p = Unknown_field_1_str;
strncpy(p, "0x", sizeof(Unknown_field_1_str) - 1);
p += 2;
for (int j = 0; j < 7; j++) {
p += sprintf(p, "%02X", b[19 + j]);
}
if (decoder->verbose) {
bitrow_printf(&b[19], 7 * 8, "%s Unknown_field_1 %s\t", __func__, Unknown_field_1_str);
bitrow_debug(&b[19], 7 * 8);
}
// 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) - 1);
p += 2;
for (int j = 0; j < 3; j++) {
p += sprintf(p, "%02X", b[29 + j]);
}
if (decoder->verbose)
bitrow_printf(&b[29], 3 * 8, "%s Unknown_field_1 %s\t", __func__, Unknown_field_2_str);
LastConsumptionCount = ((uint32_t)b[32] << 24) | (b[33] << 16) | (b[34] << 8) | (b[35]);
if (decoder->verbose)
bitrow_printf(&b[32], 32, "%s LastConsumptionCount %d\t", __func__, LastConsumptionCount);
// DifferentialConsumptionIntervals[] = 27 intervals of 14-bit unsigned integers.
unsigned pos = sync_index + (36 * 8);
if (decoder->verbose)
bitrow_printf(&b[36], 48 * 8, "%s DifferentialConsumptionIntervals", __func__);
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);
// bitrow_printf(buffy, 14, "%d\t%d\t", j, DifferentialConsumptionIntervals[j]);
pos += 14;
}
if (decoder->verbose) {
fprintf(stderr, "%s DifferentialConsumptionIntervals:\n\t", __func__);
for (int j = 0; j < 27; j++) {
fprintf(stderr, "%d ", DifferentialConsumptionIntervals[j]);
}
fprintf(stderr, "\n\n");
// bitrow_debug(&b[36], 48*8);
}
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 *meter_type = get_meter_type_name(ERTType);
// fprintf(stderr, "meter_type = %s\n", 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 *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 idm = {
.name = "Interval Data Message (IDM)",
.modulation = OOK_PULSE_MANCHESTER_ZEROBIT,
.short_width = 30,
.long_width = 30,
.gap_limit = 20000,
.reset_limit = 20000,
// .gap_limit = 2500,
// .reset_limit = 4000,
.decode_fn = &idm_callback,
.disabled = 0,
.fields = output_fields,
};
r_device netidm = {
.name = "Interval Data Message (IDM) for Net Meters",
.modulation = OOK_PULSE_MANCHESTER_ZEROBIT,
.short_width = 30,
.long_width = 30,
.gap_limit = 20000,
.reset_limit = 20000,
// .gap_limit = 2500,
// .reset_limit = 4000,
.decode_fn = &netidm_callback,
.disabled = 0,
.fields = output_fields,
};

View file

@ -212,6 +212,7 @@ COPY ..\..\libusb\MS64\dll\libusb*.dll $(TargetDir)</Command>
<ClCompile Include="..\src\devices\honeywell_wdb.c" />
<ClCompile Include="..\src\devices\ht680.c" />
<ClCompile Include="..\src\devices\ibis_beacon.c" />
<ClCompile Include="..\src\devices\idm.c" />
<ClCompile Include="..\src\devices\ikea_sparsnas.c" />
<ClCompile Include="..\src\devices\infactory.c" />
<ClCompile Include="..\src\devices\inovalley-kw9015b.c" />

View file

@ -373,6 +373,9 @@
<ClCompile Include="..\src\devices\ibis_beacon.c">
<Filter>Source Files\devices</Filter>
</ClCompile>
<ClCompile Include="..\src\devices\idm.c">
<Filter>Source Files\devices</Filter>
</ClCompile>
<ClCompile Include="..\src\devices\ikea_sparsnas.c">
<Filter>Source Files\devices</Filter>
</ClCompile>