1 /** @file
2     DSC security contact sensors.
3 
4     Copyright (C) 2015 Tommy Vestermark
5     Copyright (C) 2015 Robert C. Terzi
6 
7     This program is free software; you can redistribute it and/or modify
8     it under the terms of the GNU General Public License as published by
9     the Free Software Foundation; either version 2 of the License, or
10     (at your option) any later version.
11 */
12 /**
13 DSC - Digital Security Controls 433 Mhz Wireless Security Contacts
14 doors, windows, smoke, CO2, water.
15 
16 Protocol Description available in this FCC Report for FCC ID F5300NB912
17 https://apps.fcc.gov/eas/GetApplicationAttachment.html?id=100988
18 
19 General Packet Description
20 - Packets are 26.5 mS long
21 - Packets start with 2.5 mS of constant modulation for most sensors
22   Smoke/CO2/Fire sensors start with 5.6 mS of constant modulation
23 - The length of a bit is 500 uS, broken into two 250 uS segments.
24    A logic 0 is 500 uS (2 x 250 uS) of no signal.
25    A logic 1 is 250 uS of no signal followed by 250 uS of signal/keying
26 - Then there are 4 sync logic 1 bits.
27 - There is a sync/start 1 bit in between every 8 bits.
28 - A zero byte would be 8 x 500 uS of no signal (plus the 250 uS of
29   silence for the first half of the next 1 bit) for a maximum total
30   of 4,250 uS (4.25 mS) of silence.
31 - The last byte is a CRC with nothing after it, no stop/sync bit, so
32   if there was a CRC byte of 0, the packet would wind up being short
33   by 4 mS and up to 8 bits (48 bits total).
34 
35 There are 48 bits in the packet including the leading 4 sync 1 bits.
36 This makes the packet 48 x 500 uS bits long plus the 2.5 mS preamble
37 for a total packet length of 26.5 ms.  (smoke will be 3.1 ms longer)
38 
39 Packet Decoding
40 
41     Check intermessage start / sync bits, every 8 bits
42     Byte 0   Byte 1   Byte 2   Byte 3   Byte 4   Byte 5
43     vvvv         v         v         v         v
44     SSSSdddd ddddSddd dddddSdd ddddddSd dddddddS cccccccc  Sync,data,crc
45     01234567 89012345 67890123 45678901 23456789 01234567  Received Bit No.
46     84218421 84218421 84218421 84218421 84218421 84218421  Received Bit Pos.
47 
48     SSSS         S         S         S         S           Synb bit positions
49         ssss ssss ttt teeee ee eeeeee e eeeeeee  cccccccc  type
50         tttt tttt yyy y1111 22 223333 4 4445555  rrrrrrrr
51 
52 - Bits: 0,1,2,3,12,21,30,39 should == 1
53 
54 - Status (st) = 8 bits, open, closed, tamper, repeat
55 - Type (ty)   = 4 bits, Sensor type, really first nybble of ESN
56 - ESN (e1-5)  = 20 bits, Electronic Serial Number: Sensor ID.
57 - CRC (cr)    = 8 bits, CRC, type/polynom to be determined
58 
59 The ESN in practice is 24 bits, The type + remaining 5 nybbles.
60 The physical devices have all 6 digits printed in hex. Devices are enrolled
61 by entering or recording the 6 hex digits.
62 
63 The CRC is 8 bit, reflected (lsb first), Polynomial 0xf5, Initial value 0x3d
64 
65 Status bit breakout:
66 
67 The status byte contains a number of bits that indicate:
68 -  open vs closed
69 - event vs heartbeat
70 - battery ok vs low
71 - tamper
72 - recent activity (for certain devices)
73 
74 The majority of the DSC sensors use the status bits the same way.
75 There are some slight differences depending on who made the device.
76 
77 @todo - the status bits don't make sense for the one-way keyfob
78 and should be broken out two indicate which buttons are pressed.
79 The keyfob can be detected by the type nybble.
80 
81 Notes:
82 - The device type nybble isn't really useful other than for detecting
83   the keyfob. For example door/window contacts (Type 2) are used pretty
84   generically, so the same type can be used for burglar, flood, fire,
85   temperature limits, etc.  The device type is mildly informational
86   during testing and discovery. It can easily be seen as the  first digit
87   of the ESN, so it doesn't need to be broken out separately.
88 - There seem to be two bits used inconsistently to indicate whether
89   the sensor is being tampered with (case opened, removed from the wall,
90   missing EOL resistor, etc.
91 - The two-way devices wireless keypad and use an entirely different
92   modulation. They are supposed to be encrypted. A sampling rate
93   greater than 250 khz (1 mhz?) looks to be necessary.
94 
95 */
96 
97 
98 #include "decoder.h"
99 
100 #define DSC_CT_MSGLEN        5
101 
dsc_callback(r_device * decoder,bitbuffer_t * bitbuffer)102 static int dsc_callback(r_device *decoder, bitbuffer_t *bitbuffer)
103 {
104     data_t *data;
105     uint8_t *b;
106     int valid_cnt = 0;
107     uint8_t bytes[5];
108     uint8_t status, crc;
109     //int subtype;
110     uint32_t esn;
111     char status_str[3];
112     char esn_str[7];
113     int s_closed, s_event, s_tamper, s_battery_low;
114     int s_xactivity, s_xtamper1, s_xtamper2, s_exception;
115 
116     int result = 0;
117 
118     for (int row = 0; row < bitbuffer->num_rows; row++) {
119         if (decoder->verbose > 1 && bitbuffer->bits_per_row[row] > 0 ) {
120             fprintf(stderr, "%s: row %d bit count %d\n", __func__,
121                     row, bitbuffer->bits_per_row[row]);
122         }
123 
124         // Number of bits in the packet should be 48 but due to the
125         // encoding of trailing zeros is a guess based on reset_limit /
126         // long_width (bit period).  With current values up to 10 zero
127         // bits could be added, so it is normal to get a 58 bit packet.
128         //
129         // If the limits are changed for some reason, the max number of bits
130         // will need to be changed as there may be more zero bit padding
131         if (bitbuffer->bits_per_row[row] < 48 ||
132             bitbuffer->bits_per_row[row] > 70) {  // should be 48 at most
133             if (decoder->verbose > 1 && bitbuffer->bits_per_row[row] > 0) {
134                 fprintf(stderr, "%s: row %d invalid bit count %d\n", __func__,
135                         row, bitbuffer->bits_per_row[row]);
136             }
137             result = DECODE_ABORT_EARLY;
138             continue; // DECODE_ABORT_EARLY
139         }
140 
141         b = bitbuffer->bb[row];
142         // Validate Sync/Start bits == 1 and are in the right position
143         if (!((b[0] & 0xF0) &&     // First 4 bits are start/sync bits
144               (b[1] & 0x08) &&    // Another sync/start bit between
145               (b[2] & 0x04) &&    // every 8 data bits
146               (b[3] & 0x02) &&
147               (b[4] & 0x01))) {
148             if (decoder->verbose > 1) {
149                 bitrow_printf(b, 40, "%s: Invalid start/sync bits ", __func__);
150             }
151             result = DECODE_ABORT_EARLY;
152             continue; // DECODE_ABORT_EARLY
153         }
154 
155         bytes[0] = ((b[0] & 0x0F) << 4) | ((b[1] & 0xF0) >> 4);
156         bytes[1] = ((b[1] & 0x07) << 5) | ((b[2] & 0xF8) >> 3);
157         bytes[2] = ((b[2] & 0x03) << 6) | ((b[3] & 0xFC) >> 2);
158         bytes[3] = ((b[3] & 0x01) << 7) | ((b[4] & 0xFE) >> 1);
159         bytes[4] = ((b[5]));
160 
161         // prevent false positive of: ff ff ff ff 00
162         if (bytes[0] == 0xff && bytes[1] == 0xff && bytes[2] == 0xff && bytes[3] == 0xff) {
163             result = DECODE_FAIL_SANITY;
164             continue; // DECODE_FAIL_SANITY
165         }
166 
167         if (decoder->verbose) {
168             bitrow_printf(bytes, 40, "%s: Contact Raw Data: ", __func__);
169         }
170 
171         status = bytes[0];
172         //subtype = bytes[1] >> 4;  // @todo needed for detecting keyfob
173         esn = (bytes[1] << 16) | (bytes[2] << 8) | bytes[3];
174         crc = bytes[4];
175 
176         if (crc8le(bytes, DSC_CT_MSGLEN, 0xf5, 0x3d) != 0) {
177             if (decoder->verbose)
178                 fprintf(stderr, "%s: Contact bad CRC: %06X, Status: %02X, CRC: %02X\n", __func__,
179                         esn, status, crc);
180             result = DECODE_FAIL_MIC;
181             continue; // DECODE_FAIL_MIC
182         }
183 
184         // Decode status bits:
185 
186         // 0x02 = Closed/OK/Restored
187         s_closed = (status & 0x02) == 0x02;
188 
189         // 0x40 = Heartbeat (not an open/close event)
190         s_event = ((status & 0x40) != 0x40);
191 
192         // 0x08 Battery Low
193         s_battery_low = (status & 0x08) == 0x08;
194 
195         // Tamper: 0x10 set or 0x01 unset indicate tamper
196         // 0x10 Set to tamper message type (more testing needed)
197         // 0x01 Cleared tamper status (seend during hearbeats)
198         s_tamper = ((status & 0x01) != 0x01) || ((status & 0x10) == 0x10);
199 
200         // "experimental" (naming might change)
201         s_xactivity = (status & 0x20) == 0x20;
202 
203         // Break out 2 tamper bits
204         s_xtamper1 = (status & 0x01) != 0x01; // 0x01 set: case closed/no tamper
205         s_xtamper2 = (status & 0x10) == 0x10; //tamper event or EOL problem
206 
207         // exception/states not seen
208         // 0x80 is always set and 0x04 has never been set.
209         s_exception = ((status & 0x80) != 0x80) || ((status & 0x04) == 0x04);
210 
211         sprintf(status_str, "%02x", status);
212         sprintf(esn_str, "%06x", esn);
213 
214 
215         /* clang-format off */
216         data = data_make(
217                 "model",        "",             DATA_STRING, "DSC-Security",
218                 "id",           "",             DATA_INT,    esn,
219                 "closed",       "",             DATA_INT,    s_closed, // @todo make bool
220                 "event",        "",             DATA_INT,    s_event, // @todo make bool
221                 "tamper",       "",             DATA_INT,    s_tamper, // @todo make bool
222                 "battery_ok",   "Battery",      DATA_INT,    !s_battery_low,
223                 "xactivity",    "",             DATA_INT,    s_xactivity, // @todo make bool
224 
225                 // Note: the following may change or be removed
226                 "xtamper1",     "",             DATA_INT,    s_xtamper1, // @todo make bool
227                 "xtamper2",     "",             DATA_INT,    s_xtamper2, // @todo make bool
228                 "exception",    "",             DATA_INT,    s_exception, // @todo make bool
229                 "esn",          "",             DATA_STRING, esn_str, // to be removed - transitional
230                 "status",       "",             DATA_INT,    status,
231                 "status_hex",   "",             DATA_STRING, status_str, // to be removed - once bits are output
232                 "mic",          "Integrity",    DATA_STRING, "CRC",
233                 NULL);
234         /* clang-format on */
235         decoder_output_data(decoder, data);
236 
237         valid_cnt++; // Have a valid packet.
238     }
239 
240     if (valid_cnt) {
241         return 1;
242     }
243 
244     // Only returns the latest result, but better than nothing.
245     return result;
246 }
247 
248 static char *output_fields[] = {
249         "model",
250         "id",
251         "closed",
252         "event",
253         "tamper",
254         "status",
255         "battery_ok",
256         "esn",
257         "exception",
258         "status_hex",
259         "xactivity",
260         "xtamper1",
261         "xtamper2",
262         "mic",
263         NULL,
264 };
265 
266 r_device dsc_security = {
267         .name        = "DSC Security Contact",
268         .modulation  = OOK_PULSE_PCM_RZ,
269         .short_width = 250,  // Pulse length, 250 µs
270         .long_width  = 500,  // Bit period, 500 µs
271         .reset_limit = 5000, // Max gap,
272         .decode_fn   = &dsc_callback,
273         .disabled    = 0,
274         .fields      = output_fields,
275 };
276 
277 r_device dsc_security_ws4945 = {
278         .name        = "DSC Security Contact (WS4945)",
279         .modulation  = OOK_PULSE_PCM_RZ,
280         .short_width = 536,  // Pulse length, 536 µs
281         .long_width  = 1072, // Bit period, 1072 µs
282         .reset_limit = 6000, // Max gap,
283         .decode_fn   = &dsc_callback,
284         .disabled    = 0,
285         .fields      = output_fields,
286 };
287