Add support for Security plus v2 keyfob (#1480)
This commit is contained in:
parent
4871ca0d62
commit
37cb397959
8 changed files with 457 additions and 2 deletions
|
@ -115,7 +115,7 @@ See [CONTRIBUTING.md](./docs/CONTRIBUTING.md).
|
|||
[37]* Inovalley kw9015b, TFA Dostmann 30.3161 (Rain and temperature sensor)
|
||||
[38] Generic temperature sensor 1
|
||||
[39] WG-PB12V1 Temperature Sensor
|
||||
[40] Acurite 592TXR Temp/Humidity, 5n1 Weather Station, 6045 Lightning
|
||||
[40] Acurite 592TXR Temp/Humidity, 5n1 Weather Station, 6045 Lightning, 3N1, Atlas
|
||||
[41] Acurite 986 Refrigerator / Freezer Thermometer
|
||||
[42] HIDEKI TS04 Temperature, Humidity, Wind and Rain Sensor
|
||||
[43] Watchman Sonic / Apollo Ultrasonic / Beckett Rocket oil tank monitor
|
||||
|
@ -237,6 +237,7 @@ See [CONTRIBUTING.md](./docs/CONTRIBUTING.md).
|
|||
[161] Interval Data Message (IDM) for Net Meters
|
||||
[162]* ThermoPro-TX2 temperature sensor
|
||||
[163] Acurite 590TX Temperature with optional Humidity
|
||||
[164] Security+ 2.0 (Keyfob)
|
||||
|
||||
* Disabled by default, use -R n or -G
|
||||
|
||||
|
|
|
@ -171,6 +171,7 @@
|
|||
DECL(netidm) \
|
||||
DECL(thermopro_tx2) \
|
||||
DECL(acurite_590tx) \
|
||||
DECL(secplus_v2) \
|
||||
/* Add new decoders here. */
|
||||
|
||||
#define DECL(name) extern r_device name;
|
||||
|
|
|
@ -246,7 +246,7 @@ Generic temperature sensor 1
|
|||
WG\-PB12V1 Temperature Sensor
|
||||
.TP
|
||||
[ \fB40\fI\fP ]
|
||||
Acurite 592TXR Temp/Humidity, 5n1 Weather Station, 6045 Lightning
|
||||
Acurite 592TXR Temp/Humidity, 5n1 Weather Station, 6045 Lightning, 3N1, Atlas
|
||||
.TP
|
||||
[ \fB41\fI\fP ]
|
||||
Acurite 986 Refrigerator / Freezer Thermometer
|
||||
|
@ -610,6 +610,9 @@ Interval Data Message (IDM) for Net Meters
|
|||
.TP
|
||||
[ \fB163\fI\fP ]
|
||||
Acurite 590TX Temperature with optional Humidity
|
||||
.TP
|
||||
[ \fB164\fI\fP ]
|
||||
Security+ 2.0 (Keyfob)
|
||||
|
||||
* Disabled by default, use \-R n or \-G
|
||||
.SS "Input device selection"
|
||||
|
|
|
@ -133,6 +133,7 @@ add_library(r_433 STATIC
|
|||
devices/s3318p.c
|
||||
devices/schraeder.c
|
||||
devices/scmplus.c
|
||||
devices/secplus_v2.c
|
||||
devices/sharp_spc775.c
|
||||
devices/silvercrest.c
|
||||
devices/simplisafe.c
|
||||
|
|
|
@ -132,6 +132,7 @@ rtl_433_SOURCES = abuf.c \
|
|||
devices/s3318p.c \
|
||||
devices/schraeder.c \
|
||||
devices/scmplus.c \
|
||||
devices/secplus_v2.c \
|
||||
devices/sharp_spc775.c \
|
||||
devices/silvercrest.c \
|
||||
devices/simplisafe.c \
|
||||
|
|
444
src/devices/secplus_v2.c
Normal file
444
src/devices/secplus_v2.c
Normal file
|
@ -0,0 +1,444 @@
|
|||
/** @file
|
||||
Security+ 2.0 rolling code
|
||||
|
||||
Copyright (C) 2020 Peter Shipley <peter.shipley@gmail.com>
|
||||
Based on code by Clayton Smith https://github.com/argilo/secplus
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
/**
|
||||
Freq 310, 315 and 390 MHz.
|
||||
|
||||
Security+ 2.0 is described in [US patent application US20110317835A1](https://patents.google.com/patent/US20110317835A1/)
|
||||
|
||||
|
||||
*/
|
||||
|
||||
#include "decoder.h"
|
||||
|
||||
/**
|
||||
|
||||
|
||||
data comes in two bursts/packets
|
||||
|
||||
Layout:
|
||||
|
||||
bits = `AA BB IIII OOOO X*30`
|
||||
|
||||
AA = payload type ( 2 bits 00 or 01 )
|
||||
BB = FrameID ( 2 bits always 00)
|
||||
IIII = inversion indicator ( 4 bits )
|
||||
OOOO = Order indicator ( 4 bits ).
|
||||
XXXX.... = data ( 30 bits )
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
data is broken up into 3 parts ( p0 p1 p2 )
|
||||
eg:
|
||||
|
||||
data = `ABCABCABCABCABCABCABCABCABCABC`
|
||||
becomes:
|
||||
|
||||
`p0 = AAAAAAAAAA`
|
||||
`p1 = BBBBBBBBBB`
|
||||
`p2 = CCCCCCCCCC`
|
||||
|
||||
these three parts are then inverted and reordered based on the 4bit Order and Inversion indicators
|
||||
|
||||
fixed generated from concatenate p0 + p1
|
||||
|
||||
roll_array is generated from the 8 bit used for Order and Inversion indicators + p3
|
||||
by reading the buffer in binary bit pairs forming trinary values
|
||||
|
||||
EG:
|
||||
`1 0 0 1 1 0 1 0 0 1 1 0=> [1 0] [0 1] [1 0] [1 0] [0 1] [1 0] => 2 1 2 2 1 2`
|
||||
|
||||
Returns data in :
|
||||
* roll_array as an array of trinary values ( 0, 1, 2) the value 3 is invalid
|
||||
* fixed_p as an bitbuffer_t with 20 bits of data
|
||||
|
||||
|
||||
Once the above has been run twice the two are merged
|
||||
|
||||
---
|
||||
|
||||
|
||||
*/
|
||||
|
||||
|
||||
int _decode_v2_half(bitbuffer_t *bits, uint8_t roll_array[], bitbuffer_t *fixed_p, int verbose)
|
||||
{
|
||||
uint8_t invert = 0;
|
||||
uint8_t order = 0;
|
||||
uint32_t x = 0;
|
||||
unsigned int start_pos = 2; //
|
||||
uint8_t buffy[10];
|
||||
|
||||
|
||||
uint8_t part_id = (bits->bb[0][0] >> 6);
|
||||
|
||||
// fprintf(stdout, "%s: part %d\n", __func__, part_id);
|
||||
|
||||
if (verbose) {
|
||||
fprintf(stdout, "%s: bits_per_row = %d\n", __func__, bits->bits_per_row[0]);
|
||||
|
||||
bitrow_debugf(bits->bb[0], bits->bits_per_row[0], "%s : ", __func__);
|
||||
}
|
||||
|
||||
bitbuffer_extract_bytes(bits, 0, start_pos, buffy, 2);
|
||||
start_pos += 2;
|
||||
|
||||
bitbuffer_extract_bytes(bits, 0, start_pos, buffy, 8);
|
||||
start_pos += 8;
|
||||
order = buffy[0] >> 4;
|
||||
|
||||
invert = buffy[0] & 0x0f;
|
||||
// bitrow_debug(&invert, 8);
|
||||
|
||||
bitbuffer_extract_bytes(bits, 0, start_pos, buffy, 30);
|
||||
start_pos += 30;
|
||||
|
||||
// copy 30 bits of data into 32bit int then shift >> 2
|
||||
// memcpy(&dat, buffy, 4);
|
||||
x = ((uint32_t)buffy[0] << 24) | (buffy[1] << 16) | (buffy[2] << 8) | (buffy[3]);
|
||||
|
||||
x >>= 2;
|
||||
|
||||
// using short to store 10bit values
|
||||
uint16_t p0 = 0, p1 = 0, p2 = 0;
|
||||
|
||||
// sort 30 bits of interleaved data into three 10 bit buffers
|
||||
for (int i = 0; i < 10; i++) {
|
||||
p2 ^= (x & 0x00000001) << i; // 9-
|
||||
x >>= 1;
|
||||
p1 ^= (x & 0x00000001) << i;
|
||||
x >>= 1;
|
||||
p0 ^= (x & 0x00000001) << i;
|
||||
x >>= 1;
|
||||
}
|
||||
|
||||
// fprintf(stderr, "f1 (%d) %d %d %d\n", part_id, p0, p1, p2);
|
||||
|
||||
// selectively invert buffers
|
||||
switch (invert) {
|
||||
case 0x00: // 0b0000 (True, True, False),
|
||||
p0 = ~p0 & 0x03FF;
|
||||
p1 = ~p1 & 0x03FF;
|
||||
break;
|
||||
case 0x01: // 0b0001 (False, True, False),
|
||||
p1 = ~p1 & 0x03FF;
|
||||
break;
|
||||
case 0x02: // 0b0010 (False, False, True),
|
||||
p2 = ~p2 & 0x03FF;
|
||||
break;
|
||||
case 0x04: // 0b0100 (True, True, True),
|
||||
p0 = ~p0 & 0x03FF;
|
||||
p1 = ~p1 & 0x03FF;
|
||||
p2 = ~p2 & 0x03FF;
|
||||
break;
|
||||
case 0x05: // 0b0101 (True, False, True),
|
||||
case 0x0a: // 0b1010 (True, False, True),
|
||||
p0 = ~p0 & 0x03FF;
|
||||
p2 = ~p2 & 0x03FF;
|
||||
break;
|
||||
case 0x06: // 0b0110 (False, True, True),
|
||||
p1 = ~p1 & 0x03FF;
|
||||
p2 = ~p2 & 0x03FF;
|
||||
break;
|
||||
case 0x08: // 0b1000 (True, False, False),
|
||||
p0 = ~p0 & 0x03FF;
|
||||
break;
|
||||
case 0x09: // 0b1001 (False, False, False),
|
||||
break;
|
||||
default:
|
||||
if (verbose)
|
||||
fprintf(stderr, "Invert FAIL\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint16_t a = p0, b = p1, c = p2;
|
||||
|
||||
// selectively reorder buffers
|
||||
switch (order) {
|
||||
case 0x06: // 0b0110 2, 1, 0],
|
||||
case 0x09: // 0b1001 2, 1, 0],
|
||||
p2 = a;
|
||||
p1 = b;
|
||||
p0 = c;
|
||||
break;
|
||||
|
||||
case 0x08: // 0b1000 1, 2, 0],
|
||||
case 0x04: // 0b0100 1, 2, 0],
|
||||
p1 = a;
|
||||
p2 = b;
|
||||
p0 = c;
|
||||
break;
|
||||
|
||||
case 0x01: // 0b0001 2, 0, 1],
|
||||
p2 = a;
|
||||
p0 = b;
|
||||
p1 = c;
|
||||
break;
|
||||
|
||||
case 0x00: // 0b0000 0, 2, 1],
|
||||
p0 = a;
|
||||
p2 = b;
|
||||
p1 = c;
|
||||
break;
|
||||
|
||||
case 0x05: // 0b0101 1, 0, 2],
|
||||
p1 = a;
|
||||
p0 = b;
|
||||
p2 = c;
|
||||
break;
|
||||
|
||||
case 0x02: // 0b0010 0, 1, 2],
|
||||
case 0x0A: // 0b1010 0, 1, 2],
|
||||
p0 = a;
|
||||
p1 = b;
|
||||
p2 = c;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (verbose)
|
||||
fprintf(stderr, "Order FAIL");
|
||||
return 2;
|
||||
}
|
||||
|
||||
bitbuffer_extract_bytes(bits, 0, 4, buffy, 8);
|
||||
x = buffy[0];
|
||||
int k = 0;
|
||||
for (int i = 6; i >= 0; i -= 2) {
|
||||
roll_array[k++] = (x >> i) & 0x03;
|
||||
}
|
||||
|
||||
// bitrow_printf(buffy, 8, "%s ; buffy bits ", __func__);
|
||||
|
||||
// assemble binary bits into trinary
|
||||
x = p2;
|
||||
for (int i = 8; i >= 0; i -= 2) {
|
||||
roll_array[k++] = (x >> i) & 0x03;
|
||||
}
|
||||
|
||||
if (verbose) {
|
||||
fprintf(stderr, "%s : roll_array : (%d) ", __func__, part_id);
|
||||
for (int i = 0; i < 9; i++) {
|
||||
fprintf(stderr, "%d ", roll_array[i]);
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
// SANITY check trinary valuse, 00/01/10 are valid, 11 is not
|
||||
for (int i = 0; i < 9; i++) {
|
||||
if (roll_array[i] == 3) {
|
||||
fprintf(stderr, "roll_array val FAIL\n");
|
||||
return 1; // DECODE_FAIL_SANITY;
|
||||
}
|
||||
}
|
||||
|
||||
// fixed_p = p0 + p1
|
||||
for (int i = 9; i >= 0; i--) {
|
||||
bitbuffer_add_bit(fixed_p, (p0 >> i) & 0x01);
|
||||
}
|
||||
for (int i = 9; i >= 0; i--) {
|
||||
bitbuffer_add_bit(fixed_p, (p1 >> i) & 0x01);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const uint8_t _preamble[] = {0xaa, 0xaa, 0x95, 0x60};
|
||||
unsigned _preamble_len = 28;
|
||||
|
||||
static int secplus_v2_callback(r_device *decoder, bitbuffer_t *bitbuffer)
|
||||
{
|
||||
unsigned search_index = 0;
|
||||
unsigned next_pos = 0;
|
||||
uint8_t buffy[20]; // 80 bits (overkill)
|
||||
bitbuffer_t bits = {0};
|
||||
// int i = 0;
|
||||
|
||||
bitbuffer_t bits_1 = {0};
|
||||
bitbuffer_t fixed_1 = {0};
|
||||
uint8_t rolling_1[16] = {0};
|
||||
|
||||
bitbuffer_t bits_2 = {0};
|
||||
bitbuffer_t fixed_2 = {0};
|
||||
uint8_t rolling_2[16] = {0};
|
||||
|
||||
|
||||
// 280 is a conservative guess
|
||||
if (bitbuffer->bits_per_row[0] <= 280) {
|
||||
return DECODE_ABORT_LENGTH;
|
||||
}
|
||||
|
||||
// loop though segments until we collect both parts, or run out of data
|
||||
while (search_index < bitbuffer->bits_per_row[0]) {
|
||||
|
||||
search_index = bitbuffer_search(bitbuffer, 0, search_index, _preamble, _preamble_len);
|
||||
|
||||
if (search_index >= bitbuffer->bits_per_row[0]) {
|
||||
break;
|
||||
}
|
||||
|
||||
bitbuffer_clear(&bits);
|
||||
next_pos = bitbuffer_manchester_decode(bitbuffer, 0, search_index + 26, &bits, 80);
|
||||
search_index += 20;
|
||||
if (bits.bits_per_row[0] < 42) {
|
||||
continue; // DECODE_ABORT_LENGTH;
|
||||
}
|
||||
|
||||
if (decoder->verbose) {
|
||||
(void)fprintf(stderr, "%s: manchester_decode %d len = %u\n", __func__,
|
||||
0, bits.bits_per_row[0]);
|
||||
bitrow_debugf(bits.bb[0], bits.bits_per_row[0], "%s: manchester_decoded %d", __func__, 0);
|
||||
}
|
||||
|
||||
// valid = 0X00XXXX
|
||||
// 1st 3rs and 4th bits should alway be 0
|
||||
if (bits.bb[0][0] & 0xB0) {
|
||||
if (decoder->verbose)
|
||||
fprintf(stderr, "%s: DECODE_FAIL_SANITY\n", __func__);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 2nd bit indicates with half of the data
|
||||
if (bits.bb[0][0] & 0xC0) {
|
||||
if (decoder->verbose)
|
||||
(void)fprintf(stderr, "%s: Set 2\n", __func__);
|
||||
_decode_v2_half(&bits, rolling_2, &fixed_2, decoder->verbose);
|
||||
}
|
||||
else {
|
||||
if (decoder->verbose)
|
||||
(void)fprintf(stderr, "%s: Set 1\n", __func__);
|
||||
_decode_v2_half(&bits, rolling_1, &fixed_1, decoder->verbose);
|
||||
}
|
||||
|
||||
// break if we've recived both halfs
|
||||
if (fixed_1.bits_per_row[0] > 1 && fixed_2.bits_per_row[0] > 1) {
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Do was have what we need ??
|
||||
if (fixed_1.bits_per_row[0] == 0 || fixed_2.bits_per_row[0] == 0) {
|
||||
// No? Awww F'ck it then
|
||||
if (decoder->verbose)
|
||||
fprintf(stderr, "%s: DECODE_FAIL_SANITY\n", __func__);
|
||||
return DECODE_FAIL_SANITY;
|
||||
}
|
||||
|
||||
// Assemble rolling_1[] and rolling_2[] into rolling_digits[]
|
||||
uint8_t rolling_digits[24] = {0};
|
||||
uint8_t *r;
|
||||
|
||||
r = rolling_digits;
|
||||
*r++ = rolling_2[8];
|
||||
*r++ = rolling_1[8];
|
||||
for (int i = 4; i < 8; i++) {
|
||||
*r++ = rolling_2[i];
|
||||
}
|
||||
for (int i = 4; i < 8; i++) {
|
||||
*r++ = rolling_1[i];
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
*r++ = rolling_2[i];
|
||||
}
|
||||
for (int i = 0; i < 4; i++) {
|
||||
*r++ = rolling_1[i];
|
||||
}
|
||||
|
||||
// compute rolling_total from rolling_digits[]
|
||||
uint32_t rolling_total = 0;
|
||||
uint32_t rolling_temp = 0;
|
||||
|
||||
for (int i = 0; i < 18; i++) {
|
||||
rolling_temp = (rolling_temp * 3) + rolling_digits[i];
|
||||
// fprintf(stderr, ">> %12d\t%d\n", rolling_temp, rolling_digits[i]);
|
||||
}
|
||||
|
||||
// Max value = 2^28 (268435456)
|
||||
if ( rolling_temp >= 0x10000000 ) {
|
||||
return DECODE_FAIL_SANITY;
|
||||
}
|
||||
|
||||
// value is 28 bits thus need to shift over 4 bit
|
||||
rolling_total = reverse32(rolling_temp);
|
||||
rolling_total = rolling_total >> 4;
|
||||
|
||||
|
||||
// Assemble "fixed" data part
|
||||
uint64_t fixed_total = 0;
|
||||
uint8_t *bb;
|
||||
bb = fixed_1.bb[0];
|
||||
fixed_total ^= ((uint64_t)bb[0]) << 32;
|
||||
fixed_total ^= ((uint64_t)bb[1]) << 24;
|
||||
fixed_total ^= ((uint64_t)bb[2]) << 16;
|
||||
|
||||
bb = fixed_2.bb[0];
|
||||
fixed_total ^= ((uint64_t)bb[0]) << 12;
|
||||
fixed_total ^= ((uint64_t)bb[1]) << 4;
|
||||
fixed_total ^= (bb[2] >> 4) & 0x0f;
|
||||
|
||||
// fprintf(stderr, "fixed_total = %lu\n", fixed_total);
|
||||
|
||||
// int button = fixed_total >> 32;
|
||||
// int remote_id = fixed_total & 0xffffffff;
|
||||
char fixed_str[16];
|
||||
char rolling_str[16];
|
||||
|
||||
// rolling_total is a 28 bit unsigned number
|
||||
// fixed_totals is 40 bit in a uint64_t
|
||||
snprintf(fixed_str, sizeof(fixed_str), "%lu", fixed_total);
|
||||
snprintf(rolling_str, sizeof(rolling_str), "%u", rolling_total);
|
||||
|
||||
/* clang-format off */
|
||||
data_t *data;
|
||||
data = data_make(
|
||||
"model", "Model", DATA_STRING, "Secplus-v2",
|
||||
"button_id", "Button-ID", DATA_INT, (fixed_total >> 32),
|
||||
"remote_id", "Remote-ID", DATA_INT, (fixed_total & 0xffffffff),
|
||||
// "fixed", "", DATA_INT, fixed_total,
|
||||
"fixed", "Fixed_Code", DATA_STRING, fixed_str,
|
||||
"rolling", "Rolling_Code", DATA_STRING, rolling_str,
|
||||
NULL);
|
||||
/* clang-format on */
|
||||
|
||||
decoder_output_data(decoder, data);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static char *output_fields[] = {
|
||||
|
||||
// Common fields
|
||||
"model",
|
||||
"rolling"
|
||||
"fixed",
|
||||
"button_id",
|
||||
"remote_id",
|
||||
NULL,
|
||||
};
|
||||
|
||||
// Freq 310.01M
|
||||
// -X "n=vI3,m=OOK_PCM,s=230,l=230,t=40,r=10000,g=7400,match={24}0xaaaa9560"
|
||||
|
||||
r_device secplus_v2 = {
|
||||
.name = "Security+ 2.0 (Keyfob) ",
|
||||
.modulation = OOK_PULSE_PCM_RZ,
|
||||
.short_width = 250,
|
||||
.long_width = 250,
|
||||
.tolerance = 50,
|
||||
.gap_limit = 1500,
|
||||
.reset_limit = 9000,
|
||||
.decode_fn = &secplus_v2_callback,
|
||||
.disabled = 0,
|
||||
.fields = output_fields,
|
||||
};
|
|
@ -256,6 +256,7 @@ COPY ..\..\libusb\MS64\dll\libusb*.dll $(TargetDir)</Command>
|
|||
<ClCompile Include="..\src\devices\s3318p.c" />
|
||||
<ClCompile Include="..\src\devices\schraeder.c" />
|
||||
<ClCompile Include="..\src\devices\scmplus.c" />
|
||||
<ClCompile Include="..\src\devices\secplus_v2.c" />
|
||||
<ClCompile Include="..\src\devices\sharp_spc775.c" />
|
||||
<ClCompile Include="..\src\devices\silvercrest.c" />
|
||||
<ClCompile Include="..\src\devices\simplisafe.c" />
|
||||
|
|
|
@ -505,6 +505,9 @@
|
|||
<ClCompile Include="..\src\devices\scmplus.c">
|
||||
<Filter>Source Files\devices</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\devices\secplus_v2.c">
|
||||
<Filter>Source Files\devices</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\devices\sharp_spc775.c">
|
||||
<Filter>Source Files\devices</Filter>
|
||||
</ClCompile>
|
||||
|
|
Loading…
Add table
Reference in a new issue