1 /** @file
2     Wireless Smoke & Heat Detector.
3 
4     Copyright (C) 2017 Christian W. Zuckschwerdt <zany@triq.net>
5 
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10 
11 */
12 /**
13 Wireless Smoke & Heat Detector.
14 
15 Ningbo Siterwell Electronics  GS 558  Sw. V05  Ver. 1.3  on 433.885MHz
16 VisorTech RWM-460.f  Sw. V05, distributed by PEARL, seen on 433.674MHz
17 
18 A short wakeup pulse followed by a wide gap (11764 us gap),
19 followed by 24 data pulses and 2 short stop pulses (in a single bit width).
20 This is repeated 8 times with the next wakeup directly following
21 the preceding stop pulses.
22 
23 Bit width is 1731 us with
24 Short pulse: -___ 436 us pulse + 1299 us gap
25 Long pulse:  ---_ 1202 us pulse + 526 us gap
26 Stop pulse:  -_-_ 434us pulse + 434us gap + 434us pulse + 434us gap
27 = 2300 baud pulse width / 578 baud bit width
28 
29 24 bits (6 nibbles):
30 - first 5 bits are unit number with bits reversed
31 - next 15(?) bits are group id, likely also reversed
32 - last 4 bits are always 0x3 (maybe hardware/protocol version)
33 Decoding will reverse the whole packet.
34 Short pulses are 0, long pulses 1, need to invert the demod output.
35 
36 Each device has it's own group id and unit number as well as a
37 shared/learned group id and unit number.
38 In learn mode the primary will offer it's group id and the next unit number.
39 The secondary device acknowledges pairing with 16 0x555555 packets
40 and copies the offered shared group id and unit number.
41 The primary device then increases it's unit number.
42 This means the primary will always have the same unit number as the
43 last learned secondary, weird.
44 Also you always need to learn from the same primary.
45 
46 */
47 
48 #include "decoder.h"
49 
smoke_gs558_callback(r_device * decoder,bitbuffer_t * bitbuffer)50 static int smoke_gs558_callback(r_device *decoder, bitbuffer_t *bitbuffer)
51 {
52     data_t *data;
53     uint8_t *b;
54     int r;
55     int learn = 0;
56     int unit; // max 30
57     int id;
58     char code_str[7];
59 
60     if (bitbuffer->num_rows < 3)
61         return DECODE_ABORT_EARLY; // truncated transmission
62 
63     bitbuffer_invert(bitbuffer);
64 
65     for (r = 0; r < bitbuffer->num_rows; ++r) {
66         b = bitbuffer->bb[r];
67 
68         // count learn pattern and strip
69         if (bitbuffer->bits_per_row[r] >= 24
70                 && b[0] == 0x55 && b[1] == 0x55 && b[2] == 0x55) {
71             ++learn;
72             bitbuffer->bits_per_row[r] = 0;
73         }
74 
75         // strip end-of-packet pulse
76         if ((bitbuffer->bits_per_row[r] == 26 || bitbuffer->bits_per_row[r] == 27)
77                 && b[3] == 0)
78             bitbuffer->bits_per_row[r] = 24;
79     }
80 
81     r = bitbuffer_find_repeated_row(bitbuffer, 3, 24);
82 
83     if (r < 0)
84         return DECODE_ABORT_EARLY;
85 
86     if (bitbuffer->bits_per_row[r] > 4*8)
87         return DECODE_ABORT_LENGTH;
88 
89     b = bitbuffer->bb[r];
90 
91     // if ((b[2] & 0x0f) != 0x03)
92     //     return DECODE_ABORT_EARLY; // last nibble is always 0x3?
93 
94     b[0] = reverse8(b[0]);
95     b[1] = reverse8(b[1]);
96     b[2] = reverse8(b[2]);
97 
98     unit = b[0] & 0x1f; // 5 bits
99     id = ((b[2] & 0x0f) << 11) | (b[1] << 3) | (b[0] >> 5); // 15 bits
100 
101     if (id == 0 || id == 0x7fff)
102          return DECODE_FAIL_SANITY; // reject min/max to reduce false positives
103 
104     sprintf(code_str, "%02x%02x%02x", b[2], b[1], b[0]);
105 
106     /* clang-format off */
107     data = data_make(
108             "model",        "",             DATA_STRING, "Smoke-GS558",
109             "id"   ,        "",             DATA_INT, id,
110             "unit",         "",             DATA_INT, unit,
111             "learn",        "",             DATA_INT, learn > 1,
112             "code",         "Raw Code",     DATA_STRING, code_str,
113             NULL);
114     /* clang-format on */
115     decoder_output_data(decoder, data);
116 
117     return 1;
118 }
119 
120 static char *output_fields[] = {
121     "model",
122     "id",
123     "unit",
124     "learn",
125     "code",
126     NULL,
127 };
128 
129 r_device smoke_gs558 = {
130     .name           = "Wireless Smoke and Heat Detector GS 558",
131     .modulation     = OOK_PULSE_PWM,
132     .short_width    = 436, // Threshold between short and long pulse [us]
133     .long_width     = 1202, // Maximum gap size before new row of bits [us]
134     .gap_limit      = 1299 * 1.5f, // Maximum gap size before new row of bits [us]
135     .reset_limit    = 11764 * 1.2f, // Maximum gap size before End Of Message [us]
136     .decode_fn      = &smoke_gs558_callback,
137     .disabled       = 0,
138     .fields         = output_fields,
139 };
140