From 3777e90eeac18f588dab0abbcdcffb9a2ff3a467 Mon Sep 17 00:00:00 2001 From: Peter Shipley <peter.shipley@gmail.com> Date: Mon, 20 Jul 2020 10:37:03 -0700 Subject: [PATCH] Idm (#1421) * idm and netidm decoders --- README.md | 3 + include/rtl_433_devices.h | 2 + man/man1/rtl_433.1 | 6 + src/CMakeLists.txt | 1 + src/Makefile.am | 1 + src/devices/idm.c | 713 +++++++++++++++++++++++++++++++++++ vs15/rtl_433.vcxproj | 1 + vs15/rtl_433.vcxproj.filters | 3 + 8 files changed, 730 insertions(+) create mode 100644 src/devices/idm.c diff --git a/README.md b/README.md index 9b0bd432..ce08735f 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/include/rtl_433_devices.h b/include/rtl_433_devices.h index 3f932cf1..0ea47882 100644 --- a/include/rtl_433_devices.h +++ b/include/rtl_433_devices.h @@ -167,6 +167,8 @@ DECL(missil_ml0757) \ DECL(sharp_spc775) \ DECL(insteon) \ + DECL(idm) \ + DECL(netidm) \ /* Add new decoders here. */ diff --git a/man/man1/rtl_433.1 b/man/man1/rtl_433.1 index e0000120..53d3b710 100644 --- a/man/man1/rtl_433.1 +++ b/man/man1/rtl_433.1 @@ -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" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 81a2abfe..a4630e92 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 diff --git a/src/Makefile.am b/src/Makefile.am index f0588248..6721e3ff 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 \ diff --git a/src/devices/idm.c b/src/devices/idm.c new file mode 100644 index 00000000..c74ad5d3 --- /dev/null +++ b/src/devices/idm.c @@ -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 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) - 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 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) - 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, +}; diff --git a/vs15/rtl_433.vcxproj b/vs15/rtl_433.vcxproj index 087f30d8..2236db30 100644 --- a/vs15/rtl_433.vcxproj +++ b/vs15/rtl_433.vcxproj @@ -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" /> diff --git a/vs15/rtl_433.vcxproj.filters b/vs15/rtl_433.vcxproj.filters index a741b2d7..63088453 100644 --- a/vs15/rtl_433.vcxproj.filters +++ b/vs15/rtl_433.vcxproj.filters @@ -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>