From 993c6e74c78c4f9748a8f6b29089281bcfa2a613 Mon Sep 17 00:00:00 2001 From: ProfBoc75 Date: Wed, 9 Oct 2024 18:53:48 +0200 Subject: [PATCH 1/3] Add support for Risco 2 way protocol, Risco PIR/PET Sensor RWX95PA --- include/rtl_433_devices.h | 1 + src/CMakeLists.txt | 1 + src/devices/risco_agility.c | 207 ++++++++++++++++++++++++++++++++++++ 3 files changed, 209 insertions(+) create mode 100644 src/devices/risco_agility.c diff --git a/include/rtl_433_devices.h b/include/rtl_433_devices.h index e6f2fb133..fc5722d81 100644 --- a/include/rtl_433_devices.h +++ b/include/rtl_433_devices.h @@ -271,6 +271,7 @@ DECL(geevon) \ DECL(fineoffset_wh46) \ DECL(vevor_7in1) \ + DECL(risco_agility) \ /* Add new decoders here. */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 94a8609db..1d85686b9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -202,6 +202,7 @@ add_library(r_433 STATIC devices/regency_fan.c devices/revolt_nc5462.c devices/rftech.c + devices/risco_agility.c devices/rojaflex.c devices/rubicson.c devices/rubicson_48659.c diff --git a/src/devices/risco_agility.c b/src/devices/risco_agility.c new file mode 100644 index 000000000..e241a7f89 --- /dev/null +++ b/src/devices/risco_agility.c @@ -0,0 +1,207 @@ +/** @file + Risco 2 way Agility protocol. + + Copyright (C) 2024 Bruno OCTAU (ProfBoc75) + + 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" + +/** +Risco 2 way Agility protocol. + +Manufacturer : +- Risco Ltd. + +Reference: +- Risco PIR RWX95PA Agility sensor, + +FCC extract: +- The module is a transceiver which consist of a small PCB with an integral helical antenna, +which operates in the frequency of 433.92MHz Modulation is On-Off Keying using Manchester code with max bit rate of 2400Bps. +This module is installed only in RISCO 2-way wireless units, and it's behavior is determined by the host unit, as tested by ITL. +- Being bi-directional enables the detectors to receive an acknowledgment from the panel for every transmission. + +This module, p/n RWRT433R000A, is a 433.92Mhz 2-way wireless module manufactured by RISCO Ltd. +The model consists of a small PCB, a header for connection to the host unit, and a helical integral antenna. +This model is not sold separately, and is not installed in any units other then RISCO 2-way wireless units, and currently it is used +in the following hosts: +- Agility Security panel P/N: RW132x4t0zzA +- 2-Way I/O Expander P/N: RW132I04000H +- 2-Way Wireless PIR Detector P/N: RWX95043300A +- 2-Way Wireless PET Detector P/N: RWX95P43300A + +S.a. issue #3062 + +Data Layout: +- 2 types of message have been identified. +- 16 bytes +- or 33 bytes + +Preamble/Syncword .... : 0x555a + +Short 16 bytes message: + 0 8 16 24 34 40 48 56 64 72 80 88 96104112120 + Byte Position 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + Sample ff 60 01 e1 9c b6 01 74 fe 28 0c 60 60 00 50 be + AA AA BB BC DD DD EE EE EE FF FF GG HI JJ ZZ ZZ + +- AA:{16} flag 1, fixed 0xFF60 +- BB:{12} flag 2, fixed 0x01E +- C: {4} 0 or 1 flag 3 +- D: {16} Counter, 8 bits reversed and reflected binary coded, one bit change between message, each byte increases to maximum then decreases. +- EE:{24} Possible ID, not yet decoded from Wxxxxxxxxxxx number on the QR sticker. +- FF:{16} Fixed 0x280c value +- GG:{8} flag 4, 0x60 from PIR sensor, 0xA0 from other type frame +- H: {4} Alarm state, 0x6 (0x4 Gray decoded) = Tampered, 0xA (0x6) = Tampered_motion, 0xC (0x2) = Motion, 0x0 = Clear, not detection. +- I: {4} 0x0 = Normal, 0x3 (0x8) = Low Bat ? +- J: {4} 0 or 1 +- ZZ:{16} CRC-16, poly 0x8005, init 0x8181 + +Bitbench: + + ? 16h ? 12h ZERO_OR_ONE 4h COUNTER ? 4h CMD ? 4h ID ? 32h FIX 16h ? 8h ? 8h ZERO_OR_ONE 8h CRC 16h 8h + +Long 33 bytes message: (draft, to be reviewed) + + Byte Position 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 + Sample fe f8 01 d1 ba 18 01 ac 89 28 0c a0 03 01 e0 a3 19 01 06 00 00 c0 c0 00 df 3e 2f a5 f4 1e 00 82 1b + AA AA BB BC DE FF FF FF FF GG GG HH II JJ J? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ZZ ZZ + +- AA:{16} flag 1, fixed 0xFEF8 +- BB:{12} flag 2, fixed 0x01D +- C: {4} 0 or 1 flag 3 +- D: {4} Counter ? +- E: {4} Command ? (send / acknowledge ?) +- FF:{32} Possible ID, not yet decoded from Wxxxxxxxxxxx number on the QR sticker. +- GG:{16} Fixed 0x280c value +- HH:{8} flag 4, 0x60 from PIR sensor, 0xA0 from other type frame +- II:{8} flag 5, 0x60 = Tampered, 0xA0 = Tampered_motion, 0xC0 = Motion, 0x03 from other type frame. +- ??: Unknown +- JJ:{12} flag 6, fixed 0x01E +- ZZ:{16} CRC-16, poly 0x8005, init 0x8181 + +Bitbench: + + ? 16h ? 12h ZERO_OR_ONE 4h ? COUNTER ? 4h CMD ? 4h ID ? 32h FIX 16h ? 8h 8h 12h 12h 32h 8h 72h ? 8h CRC 16h + +*/ + +static int risco_agility_decode(r_device *decoder, bitbuffer_t *bitbuffer) +{ + bitbuffer_t decoded = { 0 }; + uint8_t *b; + uint8_t const preamble_pattern[] = {0x55, 0x5a}; + uint8_t len_msg = 16; // default for sensor message, could be 33 bytes for other Agility message not yet decoded + // 4 bits reversed then gray decoded + int const gray_map[] = { + 0, // 0, 0 + 15, // 1, F + 7, // 2, 7 + 8, // 3, 8 + 3, // 4, 3 + 12, // 5, C + 4, // 6, 4 + 11, // 7, B + 1, // 8, 1 + 14, // 9, E + 6, // A, 6 + 9, // B, 9 + 2, // C, 2 + 13, // D, D + 5, // E, 5 + 10, // F, A + }; + + if (bitbuffer->num_rows != 1) { + return DECODE_ABORT_EARLY; + } + + int pos = bitbuffer_search(bitbuffer, 0, 0, preamble_pattern, sizeof(preamble_pattern) * 8); + if (pos >= bitbuffer->bits_per_row[0]) { + decoder_logf(decoder, 1, __func__, "Preamble not found"); + return DECODE_ABORT_EARLY; + } + + decoder_log_bitrow(decoder, 1, __func__, bitbuffer->bb[0], bitbuffer->bits_per_row[0], "MSG"); + + bitbuffer_differential_manchester_decode(bitbuffer, 0, pos + sizeof(preamble_pattern) * 8, &decoded, len_msg * 8); + + decoder_log_bitrow(decoder, 1, __func__, decoded.bb[0], decoded.bits_per_row[0], "DMC"); + + // check msg length + + if (decoded.bits_per_row[0] < len_msg * 8) { + decoder_logf(decoder, 1, __func__, "Too short"); + return DECODE_ABORT_LENGTH; + } + + b = decoded.bb[0]; + + // verify checksum + if (crc16(b, len_msg, 0x8005, 0x8181)) { + decoder_logf(decoder, 1, __func__, "crc error"); + return DECODE_FAIL_MIC; // crc mismatch + } + + // expected 0xFF60 short message, 0xFEF8 message not yet decoded properly + int message_type = (b[0] << 8)| b[1]; + if ( message_type != 0xFF60) { + decoder_logf(decoder, 1, __func__, "Wrong message type %02x", message_type); + return DECODE_ABORT_LENGTH; + } + + // ID is not Gray decoded as not yet identified from QR W ID number. + int id = (b[6] << 16) | (b [7] << 8) | b[8]; + + // Alarm state, 0x6 = Tampered, 0xA = Tampered_motion, 0xC = Motion, 0x0 = Clear, not detection. + int state = gray_map[(b[12] & 0xF0) >> 4]; + int tamper = (state & 0x4) >> 2; + int motion = (state & 0x2) >> 1; + int low_batt = ((gray_map[b[12] & 0xF]) & 0x8)>> 3; + int counter1 = gray_map[(b[4] & 0xF0) >> 4]; + int counter2 = gray_map[(b[4] & 0xF)]; + int counter3 = gray_map[(b[5] & 0xF0) >> 4]; + int counter4 = gray_map[(b[5] & 0xF)]; + int counter = (counter4 << 12) | (counter3 << 8) | (counter2 << 4) | counter1; + + /* clang-format off */ + data_t *data = data_make( + "model", "", DATA_STRING, "Risco-PIR", + "id", "", DATA_FORMAT, "%06x", DATA_INT, id, + "counter", "Counter", DATA_FORMAT, "%04x", DATA_INT, counter, + "tamper", "Tamper", DATA_COND, tamper, DATA_INT, 1, + "motion", "Motion", DATA_COND, motion, DATA_INT, 1, + "battery_ok", "Battery_OK", DATA_INT, !low_batt, + "mic", "Integrity", DATA_STRING, "CRC", + NULL); + /* clang-format on */ + + decoder_output_data(decoder, data); + return 1; +} + +static char const *const output_fields[] = { + "model", + "id", + "counter", + "tamper", + "motion", + "battery_ok", + "mic", + NULL, +}; + +r_device const risco_agility = { + .name = "Risco 2 Way Agility protocol, Risco PIR/PET Sensor RWX95PA", + .modulation = OOK_PULSE_PCM, + .short_width = 175, + .long_width = 175, + .reset_limit = 1000, + .decode_fn = &risco_agility_decode, + .fields = output_fields, +}; From 81af3ee43185b8815e5f13f3103995d7119b2cde Mon Sep 17 00:00:00 2001 From: ProfBoc75 Date: Thu, 10 Oct 2024 19:00:20 +0200 Subject: [PATCH 2/3] Fix Gray decoder and update sensor model --- src/devices/risco_agility.c | 58 ++++++++++++++----------------------- 1 file changed, 22 insertions(+), 36 deletions(-) diff --git a/src/devices/risco_agility.c b/src/devices/risco_agility.c index e241a7f89..7a3e2cce9 100644 --- a/src/devices/risco_agility.c +++ b/src/devices/risco_agility.c @@ -11,7 +11,7 @@ #include "decoder.h" -/** +/** @fn int risco_agility_decode(r_device *decoder, bitbuffer_t *bitbuffer) Risco 2 way Agility protocol. Manufacturer : @@ -91,31 +91,18 @@ Long 33 bytes message: (draft, to be reviewed) */ +static int gray_decode(int n) { + int p = n; + while (n >>= 1) p ^= n; + return p; +} + static int risco_agility_decode(r_device *decoder, bitbuffer_t *bitbuffer) { bitbuffer_t decoded = { 0 }; uint8_t *b; uint8_t const preamble_pattern[] = {0x55, 0x5a}; uint8_t len_msg = 16; // default for sensor message, could be 33 bytes for other Agility message not yet decoded - // 4 bits reversed then gray decoded - int const gray_map[] = { - 0, // 0, 0 - 15, // 1, F - 7, // 2, 7 - 8, // 3, 8 - 3, // 4, 3 - 12, // 5, C - 4, // 6, 4 - 11, // 7, B - 1, // 8, 1 - 14, // 9, E - 6, // A, 6 - 9, // B, 9 - 2, // C, 2 - 13, // D, D - 5, // E, 5 - 10, // F, A - }; if (bitbuffer->num_rows != 1) { return DECODE_ABORT_EARLY; @@ -151,29 +138,28 @@ static int risco_agility_decode(r_device *decoder, bitbuffer_t *bitbuffer) // expected 0xFF60 short message, 0xFEF8 message not yet decoded properly int message_type = (b[0] << 8)| b[1]; if ( message_type != 0xFF60) { - decoder_logf(decoder, 1, __func__, "Wrong message type %02x", message_type); + decoder_logf(decoder, 1, __func__, "Wrong message type %04x", message_type); return DECODE_ABORT_LENGTH; } - // ID is not Gray decoded as not yet identified from QR W ID number. - int id = (b[6] << 16) | (b [7] << 8) | b[8]; + // ID is probably not well decoded as bit not reverse and not Gray decoded + int id = (b[6] << 16) | (b [7] << 8) | b[8]; + + reflect_bytes(b,16); // Alarm state, 0x6 = Tampered, 0xA = Tampered_motion, 0xC = Motion, 0x0 = Clear, not detection. - int state = gray_map[(b[12] & 0xF0) >> 4]; - int tamper = (state & 0x4) >> 2; - int motion = (state & 0x2) >> 1; - int low_batt = ((gray_map[b[12] & 0xF]) & 0x8)>> 3; - int counter1 = gray_map[(b[4] & 0xF0) >> 4]; - int counter2 = gray_map[(b[4] & 0xF)]; - int counter3 = gray_map[(b[5] & 0xF0) >> 4]; - int counter4 = gray_map[(b[5] & 0xF)]; - int counter = (counter4 << 12) | (counter3 << 8) | (counter2 << 4) | counter1; + int state = gray_decode(b[12] & 0xF); + int tamper = (state & 0x4) >> 2; + int motion = (state & 0x2) >> 1; + int low_batt = (gray_decode((b[12] & 0xF0) >> 4) & 0x8)>> 3; + int counter_raw = (b[5] << 8) | b[4]; + int counter = gray_decode(counter_raw); /* clang-format off */ data_t *data = data_make( - "model", "", DATA_STRING, "Risco-PIR", - "id", "", DATA_FORMAT, "%06x", DATA_INT, id, - "counter", "Counter", DATA_FORMAT, "%04x", DATA_INT, counter, + "model", "", DATA_STRING, "Risco-RWX95P", + "id", "", DATA_INT, id, + "counter", "Counter", DATA_INT, counter, "tamper", "Tamper", DATA_COND, tamper, DATA_INT, 1, "motion", "Motion", DATA_COND, motion, DATA_INT, 1, "battery_ok", "Battery_OK", DATA_INT, !low_batt, @@ -197,7 +183,7 @@ static char const *const output_fields[] = { }; r_device const risco_agility = { - .name = "Risco 2 Way Agility protocol, Risco PIR/PET Sensor RWX95PA", + .name = "Risco 2 Way Agility protocol, Risco PIR/PET Sensor RWX95P", .modulation = OOK_PULSE_PCM, .short_width = 175, .long_width = 175, From 28c6c041d602acb9236c078cee58aa89edb6fe81 Mon Sep 17 00:00:00 2001 From: ProfBoc75 Date: Tue, 22 Oct 2024 08:43:31 +0200 Subject: [PATCH 3/3] Minor doc corrections --- README.md | 1 + conf/rtl_433.example.conf | 1 + src/devices/risco_agility.c | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8f3dac495..711a71c1c 100644 --- a/README.md +++ b/README.md @@ -350,6 +350,7 @@ See [CONTRIBUTING.md](./docs/CONTRIBUTING.md). [262] Fine Offset Electronics WH46 air quality sensor [263] Vevor Wireless Weather Station 7-in-1 [264] Arexx Multilogger IP-HA90, IP-TH78EXT, TSN-70E + [265] Risco 2 Way Agility protocol, Risco PIR/PET Sensor RWX95PA * 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 c392a8bf6..96ebb737d 100644 --- a/conf/rtl_433.example.conf +++ b/conf/rtl_433.example.conf @@ -491,6 +491,7 @@ convert si protocol 262 # Fine Offset Electronics WH46 air quality sensor protocol 263 # Vevor Wireless Weather Station 7-in-1 protocol 264 # Arexx Multilogger IP-HA90, IP-TH78EXT, TSN-70E + protocol 265 # Risco 2 Way Agility protocol, Risco PIR/PET Sensor RWX95PA ## Flex devices (command line option "-X") diff --git a/src/devices/risco_agility.c b/src/devices/risco_agility.c index 7a3e2cce9..8db508475 100644 --- a/src/devices/risco_agility.c +++ b/src/devices/risco_agility.c @@ -147,7 +147,7 @@ static int risco_agility_decode(r_device *decoder, bitbuffer_t *bitbuffer) reflect_bytes(b,16); - // Alarm state, 0x6 = Tampered, 0xA = Tampered_motion, 0xC = Motion, 0x0 = Clear, not detection. + // Alarm state, 0x4 = Tampered, 0x6 = Tampered_motion, 0x2 = Motion, 0x0 = Clear, not detection. int state = gray_decode(b[12] & 0xF); int tamper = (state & 0x4) >> 2; int motion = (state & 0x2) >> 1; @@ -190,4 +190,4 @@ r_device const risco_agility = { .reset_limit = 1000, .decode_fn = &risco_agility_decode, .fields = output_fields, -}; +}; \ No newline at end of file