diff --git a/README.md b/README.md index c7c1547c..67ed8d78 100644 --- a/README.md +++ b/README.md @@ -240,6 +240,7 @@ See [CONTRIBUTING.md](./docs/CONTRIBUTING.md). [164] Security+ 2.0 (Keyfob) [165] TFA Dostmann 30.3221.02 T/H Outdoor Sensor [166] LaCrosse Technology View LTV-WSDTH01 Breeze Pro Wind Sensor + [167] Somfy RTS * Disabled by default, use -R n or -G diff --git a/include/rtl_433_devices.h b/include/rtl_433_devices.h index ac591461..6e3c836a 100644 --- a/include/rtl_433_devices.h +++ b/include/rtl_433_devices.h @@ -174,6 +174,7 @@ DECL(secplus_v2) \ DECL(tfa_30_3221) \ DECL(lacrosse_breezepro) \ + DECL(somfy_rts) \ /* Add new decoders here. */ #define DECL(name) extern r_device name; diff --git a/man/man1/rtl_433.1 b/man/man1/rtl_433.1 index ee51301a..7ce9e47f 100644 --- a/man/man1/rtl_433.1 +++ b/man/man1/rtl_433.1 @@ -619,6 +619,9 @@ TFA Dostmann 30.3221.02 T/H Outdoor Sensor .TP [ \fB166\fI\fP ] LaCrosse Technology View LTV\-WSDTH01 Breeze Pro Wind Sensor +.TP +[ \fB167\fI\fP ] +Somfy RTS * Disabled by default, use \-R n or \-G .SS "Input device selection" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ba2944ff..4a6b1aef 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -141,6 +141,7 @@ add_library(r_433 STATIC devices/simplisafe.c devices/smoke_gs558.c devices/solight_te44.c + devices/somfy.c devices/springfield.c devices/steelmate.c devices/tfa_30_3196.c diff --git a/src/Makefile.am b/src/Makefile.am index 121e4f19..ea5b2095 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -140,6 +140,7 @@ rtl_433_SOURCES = abuf.c \ devices/simplisafe.c \ devices/smoke_gs558.c \ devices/solight_te44.c \ + devices/somfy.c \ devices/springfield.c \ devices/steelmate.c \ devices/tfa_30_3196.c \ diff --git a/src/devices/somfy.c b/src/devices/somfy.c new file mode 100644 index 00000000..e8524313 --- /dev/null +++ b/src/devices/somfy.c @@ -0,0 +1,195 @@ +/** @file + Somfy RTS. + + Copyright (C) 2020 Matthias Schulz <mschulz@seemoo.tu-darmstadt.de> + + 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. +*/ + +/** +Somfy RTS. + +Protocol description: +The protocol is very well defined under the following two links: +[1] https://pushstack.wordpress.com/somfy-rts-protocol/ +[2] https://patentimages.storage.googleapis.com/bd/ae/4f/bf24e41e0161ca/US8189620.pdf + +Each frame consists of a preamble with hardware and software sync pulses followed by the manchester encoded data pulses. +A rising edge describes a data bit 1 and a falling edge a data bit 0. The preamble is different for the first frame and +for retransmissions. In the end, the signal is first decoded using an OOK PCM decoder and within the callback, only the +data bits will be manchester decoded. + +In the following, each character representing a low level "_" and a high level "^" is roughly 604 us long. + +First frames' preamble: + + ^^^^^^^^^^^^^^^^___________^^^^____^^^^____^^^^^^^^_ + +The first long pulse is often wrongly detected, so I just make sure that it ends up in another row during decoding and +then only consider the rows containing the second part of the first frame preamble. + +Retransmission frames' preamble: + + ^^^^____^^^^____^^^^____^^^^____^^^^____^^^^____^^^^____^^^^^^^^_ + +The data is manchester encoded _^ represents a 1 and ^_ represents a 0. The data section consists of 56 bits that equals +7 bytes of scrambled data. The data is scrambled by XORing each following byte with the last scrambled byte. After +descrambling, the 7 bytes have the following meaning conting byte from left to right as in big endian byte order: + +- byte 0: called "random" in [1] and "key" in [2], in the end it is just the seed for the scrambler +- byte 1: The higher nibble represents the control command, the lower nibble is the frame's checksum calculated by XORing + all nibbles +- byte 2-3: Replay counter value in big endian byte order +- byte 4-6: Remote control channel's address +*/ + +#include "decoder.h" + +static const char *control_str[] = { + "? (0)", + "My (1)", + "Up (2)", + "My + Up (3)", + "Down (4)", + "My + Down (5)", + "Up + Down (6)", + "? (7)", + "Prog (8)", + "Sun + Flag (9)", + "Flag (10)", + "? (11)", + "? (12)", + "? (13)", + "? (14)", + "? (15)", +}; + +static int somfy_rts_callback(r_device *decoder, bitbuffer_t *bitbuffer) +{ + data_t *data; + + uint8_t is_retransmission = 0; + uint8_t decode_row = 0; + uint8_t data_start = 0; + const uint8_t *preamble_pattern; + unsigned preamble_pattern_bit_length = 0; + + bitbuffer_t bitbuffer_decoded = { 0 }; + + uint8_t message_bytes[7] = { 0 }; + uint8_t chksum_calc = 0; + uint8_t chksum = 0; + uint16_t counter = 0; + char address_hex[7]; + uint32_t address_int_le = 0; + uint8_t control = 0; + uint8_t seed = 0; + + for (int i = 0; i < bitbuffer->num_rows; i++) { + if (bitbuffer->bits_per_row[i] > 170) { + is_retransmission = 1; + decode_row = i; + data_start = 65; + preamble_pattern = (const uint8_t *) "\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xff"; + preamble_pattern_bit_length = 64; + break; + } else if (bitbuffer->bits_per_row[i] > 130) { + is_retransmission = 0; + decode_row = i; + data_start = 25; + preamble_pattern = (const uint8_t *) "\xf0\xf0\xff"; + preamble_pattern_bit_length = 24; + break; + } + } + + if (data_start == 0) + return DECODE_FAIL_SANITY; + + if (bitbuffer_search(bitbuffer, decode_row, 0, preamble_pattern, preamble_pattern_bit_length) != 0) + return DECODE_FAIL_SANITY; + + if (bitbuffer_manchester_decode(bitbuffer, decode_row, data_start, &bitbuffer_decoded, 56) - data_start < 56) + return DECODE_FAIL_MIC; + + bitbuffer_extract_bytes(&bitbuffer_decoded, 0, 0, message_bytes, 56); + + // descramble + for (int i = 6; i > 0; i--) + message_bytes[i] = message_bytes[i] ^ message_bytes[i-1]; + + // calculate checksum + chksum_calc = xor_bytes(message_bytes, 7); + chksum_calc = (chksum_calc & 0xf) ^ (chksum_calc >> 4); // fold to nibble + + // Fail if checksum is incorrect + if (chksum_calc != 0) + return DECODE_FAIL_MIC; + + // extract seed + seed = message_bytes[0]; + + // extract control + control = (message_bytes[1] & 0xf0) >> 4; + + // extract checksum + chksum = message_bytes[1] & 0xf; + + // extract counter independent of system's byte order + counter = message_bytes[3] | (message_bytes[2] << 8); + + // extract address bytes into hexstring + snprintf(address_hex, sizeof(address_hex), "%02X%02X%02X", message_bytes[4], message_bytes[5], message_bytes[6]); + + // extract address as little endian integer. It should be little endian as multiple addresses used by one remote control increase the address value in little endian byte order. + address_int_le = (message_bytes[6] << 16) | (message_bytes[5] << 8) | message_bytes[4]; + + if (decoder->verbose > 1) { + fprintf(stderr, "seed=0x%02x, chksum=0x%x\n", seed, chksum); + } + + /* clang-format off */ + data = data_make( + "model", "", DATA_STRING, "Somfy-RTS", + "id", "Id", DATA_INT, address_int_le, + "control", "Control", DATA_STRING, control_str[control], + "counter", "Counter", DATA_INT, counter, + "address", "Address", DATA_STRING, address_hex, + "retransmission", "Retransmission", DATA_STRING, is_retransmission ? "TRUE" : "FALSE", + "mic", "Integrity", DATA_STRING, "CHECKSUM", + NULL); + /* clang-format on */ + + decoder_output_data(decoder, data); + + return 1; +} + +static char *output_fields[] = { + "model", + "control", + "counter", + "address", + "retransmission", + NULL, +}; + +// rtl_433 -r g001_433.414M_250k.cu8 -X "n=somfy-test,m=OOK_PCM,s=604,l=604,t=40,r=10000,g=3000,y=2416" +// Nominal bit width is ~604 us, RZ, short=long + +r_device somfy_rts = { + .name = "Somfy RTS", + .modulation = OOK_PULSE_PCM_RZ, + .short_width = 604, // short pulse is ~604 us (1 x nominal bit width) + .long_width = 604, // long pulse is ~604 us (1 x nominal bit width) +// .sync_width = 2416, // hardware sync pulse is ~2416 us (4 x nominal bit width), software sync pulse is ~4550 us. Commented, as sync_width has no effect on the PCM decoder. + .gap_limit = 3000, // largest off between two pulses is ~2416 us during sync. Gap between start pulse (9664 us) and first frame is 6644 us (11 x nominal bit width), 3000 us will split first message into two rows one with start pulse and one with first frame + .reset_limit = 10000, // larger than gap between start pulse and first frame (6644 us = 11 x nominal bit width) to put start pulse and first frame in two rows, but smaller than inter-frame space of 30415 us + .tolerance = 20, + .decode_fn = &somfy_rts_callback, + .disabled = 0, + .fields = output_fields, +}; diff --git a/vs15/rtl_433.vcxproj b/vs15/rtl_433.vcxproj index a1287969..b688946b 100644 --- a/vs15/rtl_433.vcxproj +++ b/vs15/rtl_433.vcxproj @@ -265,6 +265,7 @@ COPY ..\..\libusb\MS64\dll\libusb*.dll $(TargetDir)</Command> <ClCompile Include="..\src\devices\simplisafe.c" /> <ClCompile Include="..\src\devices\smoke_gs558.c" /> <ClCompile Include="..\src\devices\solight_te44.c" /> + <ClCompile Include="..\src\devices\somfy.c" /> <ClCompile Include="..\src\devices\springfield.c" /> <ClCompile Include="..\src\devices\steelmate.c" /> <ClCompile Include="..\src\devices\tfa_30_3196.c" /> diff --git a/vs15/rtl_433.vcxproj.filters b/vs15/rtl_433.vcxproj.filters index 839ceb78..dcb792e0 100644 --- a/vs15/rtl_433.vcxproj.filters +++ b/vs15/rtl_433.vcxproj.filters @@ -529,6 +529,9 @@ <ClCompile Include="..\src\devices\solight_te44.c"> <Filter>Source Files\devices</Filter> </ClCompile> + <ClCompile Include="..\src\devices\somfy.c"> + <Filter>Source Files\devices</Filter> + </ClCompile> <ClCompile Include="..\src\devices\springfield.c"> <Filter>Source Files\devices</Filter> </ClCompile>