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