From 176d63a805e0ec410ab9bdb5d0edb69c63693b7f Mon Sep 17 00:00:00 2001 From: "Christian W. Zuckschwerdt" Date: Fri, 27 Dec 2024 19:57:00 +0100 Subject: [PATCH] Add support for Revolt ZX-7717 power meter --- README.md | 1 + conf/rtl_433.example.conf | 1 + include/rtl_433_devices.h | 1 + src/CMakeLists.txt | 1 + src/devices/revolt_zx7717.c | 164 ++++++++++++++++++++++++++++++++++++ 5 files changed, 168 insertions(+) create mode 100644 src/devices/revolt_zx7717.c diff --git a/README.md b/README.md index 23f5101e7..84e88db31 100644 --- a/README.md +++ b/README.md @@ -356,6 +356,7 @@ See [CONTRIBUTING.md](./docs/CONTRIBUTING.md). [268] Bresser Thermo-/Hygro-Sensor Explore Scientific ST1005H [269] DeltaDore X3D devices [270]* Quinetic + [271] Revolt ZX-7717 power meter * Disabled by default, use -R n or a conf file to enable diff --git a/conf/rtl_433.example.conf b/conf/rtl_433.example.conf index 4579e174f..d24aab2b9 100644 --- a/conf/rtl_433.example.conf +++ b/conf/rtl_433.example.conf @@ -497,6 +497,7 @@ convert si protocol 268 # Bresser Thermo-/Hygro-Sensor Explore Scientific ST1005H protocol 269 # DeltaDore X3D devices # protocol 268 # Quinetic + protocol 271 # Revolt ZX-7717 power meter ## Flex devices (command line option "-X") diff --git a/include/rtl_433_devices.h b/include/rtl_433_devices.h index f872c2521..55a25b5b4 100644 --- a/include/rtl_433_devices.h +++ b/include/rtl_433_devices.h @@ -278,6 +278,7 @@ DECL(bresser_st1005h) \ DECL(deltadore_x3d) \ DECL(quinetic) \ + DECL(revolt_zx7717) \ /* Add new decoders here. */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ae152f343..577c2c112 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -205,6 +205,7 @@ add_library(r_433 STATIC devices/rainpoint.c devices/regency_fan.c devices/revolt_nc5462.c + devices/revolt_zx7717.c devices/rftech.c devices/risco_agility.c devices/rojaflex.c diff --git a/src/devices/revolt_zx7717.c b/src/devices/revolt_zx7717.c new file mode 100644 index 000000000..85e2e71e2 --- /dev/null +++ b/src/devices/revolt_zx7717.c @@ -0,0 +1,164 @@ +/** @file + Revolt ZX-7717-675 433 MHz power meter. + + Copyright (C) 2024 Christian W. Zuckschwerdt + Copyright (C) 2024 Boing + + 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" + +/** +Revolt ZX-7717-675 433 MHz power meter. + +- Used with Revolt ZX-7716 Monitor. +- Other names: HPM-27717, ZX-7717-919 +- Up to 6 channels +- First seen: 12-2024 +- https://www.revolt-power.de/TOP-KAT161-Zusaetzliche-Steckdose-ZX-7717-919.shtml + +Outputs are: (in this order) +- Current (A) max 15.999 A , Minimum is >= 0.001 A +- Voltage (V) max 250.0 V +- Power (VA) max 3679.9 VA +- PF (Powerfactor not in message, but calculated) +- 8 bit checksum +- some unknown bytes/flags + +Modulation: ASK/OOK with Manchester coding. +Send interval: 5 secs and/or when current changes. + +HF Output is 10 mW, but appears much higher (due to antenna maybe), +With RSSI -0.1 dB, SNR 33.0 dB at 31 m distance! + +The packet is 14 manchester encoded bytes with a Preamble of 0x2A and +an 8-bit checksum (last byte). + +Raw data: + + 2ab0abe05a15603a14005710840011 + 2ab0abe05a15603a13005710df0040 + 2ab0abe05a15603ae2c0e710ca20bb + 2ab0abe05a15603a1ac0e710c12078 + 2ab0abe05a15603a7d007b104f00c7 + +Example messages: + + 0d d507 5aa8 06 5c 2800 ea08 2100 88 + 0d d507 5aa8 06 5c c800 ea08 fb00 02 + 0d d507 5aa8 06 5c 4703 e708 5304 dd + 0d d507 5aa8 06 5c 5803 e708 8304 1e + 0d d507 5aa8 06 5c be00 de08 f200 e3 + +Data layout: + + LL IIII UUUU CC FF AAAA VVVV WWWW XX + +- L: (8 bit) 0d : payload_length (13) +- I: (16 bit) d507 : id +- U: (16 bit) 5aa8 : unknown1 +- C: (8 bit) 06 : channel (6) // TODO +- F: (8 bit) 5c ; unknown2 +- A: (16 bit) be00 : current (0.190) +- V: (16 bit) de08 : voltage (227.0) +- W: (16 bit) f200 : power (24.2) +- X: (8 bit) e3 : checksum + +*/ +static int revolt_zx7717_decode(r_device *decoder, bitbuffer_t *bitbuffer) +{ + uint8_t const preamble[] = {0x2a}; // sync is 0x2a + + if (bitbuffer->num_rows != 1) { + return DECODE_ABORT_EARLY; + } + unsigned row_len = bitbuffer->bits_per_row[0]; + if (row_len < 120 || row_len > 120) { + return DECODE_ABORT_EARLY; // Unrecognized data + } + + unsigned pos = bitbuffer_search(bitbuffer, 0, 0, preamble, 8); + pos += 8; // skip preamble + + if (pos > 16) { // FIXME: needs better checks + return DECODE_ABORT_LENGTH; // short buffer or preamble not found + } + unsigned len = bitbuffer->bits_per_row[0] - pos; + + // There are different length messages + // This handles the 14 bytes (112 bits) message only + if (len < 112) { + return DECODE_ABORT_LENGTH; // short buffer + } + len = 112; // FIXME: cuts the last pulse + + uint8_t b[42]; + bitbuffer_extract_bytes(bitbuffer, 0, pos, b, len); + reflect_bytes(b, (len + 7) / 8); + + int sum = add_bytes(b, 12); + if (b[13] != (sum & 0xff)) { + return DECODE_FAIL_MIC; // bad checksum + } + + decoder_log_bitrow(decoder, 1, __func__, b, len, "message"); + + int id = (b[1] << 8) | (b[2]); // Big Endian? + int unknown1 = (b[3] << 8) | b[4]; // Big Endian? + int unknown2 = (b[5] << 8) | b[6]; // Big Endian? + int channel = (b[5]); // not sure yet + int current = (b[8] << 8) | b[7]; // Little Endian + int voltage = (b[10] << 8) | b[9]; // Little Endian + int power = (b[12] << 8) | b[11]; // Little Endian + // calculation for PF (Powerfactor) is invalid if current is < 0.02 A + // e.g. a standby device will show bad readings + double va = current * voltage * 0.001; + double powerf = va > 1.0 ? power / va : 1.0; + + /* clang-format off */ + data_t *data = data_make( + "model", "", DATA_STRING, "Revolt-ZX7717", + "id", "Device ID", DATA_FORMAT, "%04x", DATA_INT, id, + "channel", "Channel", DATA_INT, channel, + "unknown", "UnknownB3", DATA_FORMAT, "%04x", DATA_INT, unknown1, + "unknown", "UnknownB5", DATA_FORMAT, "%04x", DATA_INT, unknown2, + "current_A", "Current", DATA_FORMAT, "%.3f A", DATA_DOUBLE, current * 0.001, + "voltage_V", "Voltage", DATA_FORMAT, "%.1f V", DATA_DOUBLE, voltage * 0.1, + "power_W", "Power", DATA_FORMAT, "%.1f W", DATA_DOUBLE, power * 0.1, + "pf", "PF(calc)", DATA_DOUBLE, powerf, + "mic", "Integrity", DATA_STRING, "CHECKSUM", + NULL); + /* clang-format on */ + + decoder_output_data(decoder, data); + return 1; +} + +static char const *const output_fields[] = { + "model", + "id", + "channel", + "unknown1", + "unknown2", + "current", + "voltage", + "power", + "pf", + "code", + "mic", + NULL, +}; + +r_device const revolt_zx7717 = { + .name = "Revolt ZX-7717 power meter", + .modulation = OOK_PULSE_MANCHESTER_ZEROBIT, + .short_width = 310, // Nominal width of clok half period [us] + .long_width = 310, + .reset_limit = 900, // Maximum gap size before End Of Message [us] + .decode_fn = &revolt_zx7717_decode, + .fields = output_fields, +};