Редизайн на основе текущей ветки мейнстрима + новые устройства.
[rtl-433.git] / src / devices / fineoffset.c
1 /* Fine Offset Electronics sensor protocol
2  *
3  * Copyright (C) 2017 Tommy Vestermark
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  */
9 #include "rtl_433.h"
10 #include "data.h"
11 #include "util.h"
12 #include "pulse_demod.h"
13
14
15 /*
16  * Fine Offset Electronics WH2 Temperature/Humidity sensor protocol
17  * aka Agimex Rosenborg 66796 (sold in Denmark)
18  * aka ClimeMET CM9088 (Sold in UK)
19  * aka TFA Dostmann/Wertheim 30.3157 (Temperature only!) (sold in Germany)
20  * aka ...
21  *
22  * The sensor sends two identical packages of 48 bits each ~48s. The bits are PWM modulated with On Off Keying
23  *
24  * The data is grouped in 6 bytes / 12 nibbles
25  * [pre] [pre] [type] [id] [id] [temp] [temp] [temp] [humi] [humi] [crc] [crc]
26  *
27  * pre is always 0xFF
28  * type is always 0x4 (may be different for different sensor type?)
29  * id is a random id that is generated when the sensor starts
30  * temp is 12 bit signed magnitude scaled by 10 celcius
31  * humi is 8 bit relative humidity percentage
32  * 
33  * Based on reverse engineering with gnu-radio and the nice article here:
34  *  http://lucsmall.com/2012/04/29/weather-station-hacking-part-2/
35  */
36 static int fineoffset_WH2_callback(bitbuffer_t *bitbuffer) {
37     bitrow_t *bb = bitbuffer->bb;
38     data_t *data;
39
40     char time_str[LOCAL_TIME_BUFLEN];
41
42     if (debug_output > 1) {
43        fprintf(stderr,"Possible fineoffset: ");
44        bitbuffer_print(bitbuffer);
45     }
46
47     uint8_t id;
48     int16_t temp;
49     float temperature;
50     uint8_t humidity;
51
52     const uint8_t polynomial = 0x31;    // x8 + x5 + x4 + 1 (x8 is implicit)
53
54     // Validate package
55     if (bitbuffer->bits_per_row[0] == 48 &&         // Match exact length to avoid false positives
56         bb[0][0] == 0xFF &&             // Preamble
57         bb[0][5] == crc8(&bb[0][1], 4, polynomial, 0)   // CRC (excluding preamble)
58     )
59     {
60         /* Get time now */
61         local_time_str(0, time_str);
62
63          // Nibble 3,4 contains id
64         id = ((bb[0][1]&0x0F) << 4) | ((bb[0][2]&0xF0) >> 4);
65
66         // Nibble 5,6,7 contains 12 bits of temperature
67         // The temperature is signed magnitude and scaled by 10
68         temp = ((bb[0][2] & 0x0F) << 8) | bb[0][3];
69         if(temp & 0x800) {
70             temp &= 0x7FF;      // remove sign bit
71             temp = -temp;       // reverse magnitude
72         }
73         temperature = (float)temp / 10;
74
75         // Nibble 8,9 contains humidity
76         humidity = bb[0][4];
77
78
79         if (debug_output > 1) {
80            fprintf(stderr, "ID          = 0x%2X\n",  id);
81            fprintf(stderr, "temperature = %.1f C\n", temperature);
82            fprintf(stderr, "humidity    = %u %%\n",  humidity);
83         }
84
85         // Thermo
86         if (bb[0][4] == 0xFF) {
87         data = data_make("time",          "",            DATA_STRING, time_str,
88                          "model",         "",            DATA_STRING, "TFA 30.3157 Temperature sensor",
89                          "id",            "ID",          DATA_INT, id,
90                          "temperature_C", "Temperature", DATA_FORMAT, "%.01f C", DATA_DOUBLE, temperature,
91                           NULL);
92         data_acquired_handler(data);
93         }
94         // Thermo/Hygro
95         else {
96         data = data_make("time",          "",            DATA_STRING, time_str,
97                          "model",         "",            DATA_STRING, "Fine Offset Electronics, WH2 Temperature/Humidity sensor",
98                          "id",            "ID",          DATA_INT, id,
99                          "temperature_C", "Temperature", DATA_FORMAT, "%.01f C", DATA_DOUBLE, temperature,
100                          "humidity",      "Humidity",    DATA_FORMAT, "%u %%", DATA_INT, humidity,
101                           NULL);
102         data_acquired_handler(data);
103         }
104         return 1;
105     }
106     return 0;
107 }
108
109
110 /* Fine Offset Electronics WH25 Temperature/Humidity/Pressure sensor protocol
111  *
112  * The sensor sends a package each ~64 s with a width of ~28 ms. The bits are PCM modulated with Frequency Shift Keying
113  *
114  * Example:
115  * [00] {500} 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 2a aa aa aa aa aa 8b 75 39 40 9c 8a 09 c8 72 6e ea aa aa 80 10 
116  * Reading: 22.6 C, 40 %, 1001.7 hPa
117  *
118  * Extracted data:
119  *          ?I IT TT HH PP PP CC BB
120  * aa 2d d4 e5 02 72 28 27 21 c9 bb aa
121  *
122  * II = Sensor ID (based on 2 different sensors). Does not change at battery change.
123  * T TT = Temperature (+40*10)
124  * HH = Humidity
125  * PP PP = Pressure (*10)
126  * CC = Checksum of previous 6 bytes (binary sum truncated to 8 bit)
127  * BB = Bitsum (XOR) of the 6 data bytes (high and low nibble exchanged)
128  *
129  */
130 static int fineoffset_WH25_callback(bitbuffer_t *bitbuffer) {
131     data_t *data;
132     char time_str[LOCAL_TIME_BUFLEN];
133
134     // Validate package
135     if (bitbuffer->bits_per_row[0] < 440 || bitbuffer->bits_per_row[0] > 510) {  // Nominal size is 488 bit periods
136         return 0;
137     }
138
139     // Get time now
140     local_time_str(0, time_str);
141
142     // Find a data package and extract data buffer
143     static const uint8_t HEADER[] = { 0xAA, 0x2D, 0xD4 };
144     uint8_t buffer[12];
145     unsigned bit_offset = bitbuffer_search(bitbuffer, 0, 320, HEADER, sizeof(HEADER)*8);    // Normal index is 367, skip some bytes to find faster
146     if (bit_offset + sizeof(buffer)*8 >= bitbuffer->bits_per_row[0]) {  // Did not find a big enough package
147         if (debug_output) {
148             fprintf(stderr, "Fineoffset_WH25: short package. Header index: %u\n", bit_offset);
149             bitbuffer_print(bitbuffer);
150         }
151         return 0;
152     }
153     bitbuffer_extract_bytes(bitbuffer, 0, bit_offset, buffer, sizeof(buffer)*8);
154
155     if (debug_output) {
156         char raw_str[128];
157         for (unsigned n=0; n<sizeof(buffer); n++) { sprintf(raw_str+n*3, "%02x ", buffer[n]); }
158         fprintf(stderr, "Fineoffset_WH25: Raw: %s @ bit_offset [%u]\n", raw_str, bit_offset);
159     }
160
161     // Verify checksum
162     uint8_t checksum = 0, bitsum = 0;
163     for (size_t n=3; n<=8; ++n) {
164         checksum += buffer[n];
165         bitsum ^= buffer[n];
166     }
167     bitsum = (bitsum << 4) | (bitsum >> 4);     // Swap nibbles
168     if (checksum != buffer[9] || bitsum != buffer[10]) {
169         if (debug_output) {
170             fprintf(stderr, "Fineoffset_WH25: Checksum error: %02x %02x\n", checksum, bitsum);
171             bitbuffer_print(bitbuffer);
172         }
173         return 0;
174     }
175
176     // Decode data
177     uint8_t id = (buffer[3] << 4) | (buffer[4] >> 4);
178     float   temperature = (float)((uint16_t)(buffer[4] & 0xF) << 8 | buffer[5]) / 10.0 - 40.0;
179     uint8_t humidity = buffer[6];
180     float   pressure = (float)((uint16_t)buffer[7] << 8 | buffer[8]) / 10.0;
181
182     // Output data
183     data = data_make("time",          "",            DATA_STRING, time_str,
184                      "model",         "",            DATA_STRING, "Fine Offset Electronics, WH25",
185                      "id",            "ID",          DATA_INT, id,
186                      "temperature_C", "Temperature", DATA_FORMAT, "%.01f C", DATA_DOUBLE, temperature,
187                      "humidity",      "Humidity",    DATA_FORMAT, "%u %%", DATA_INT, humidity,
188                      "pressure",      "Pressure",    DATA_FORMAT, "%.01f hPa", DATA_DOUBLE, pressure,
189                       NULL);
190     data_acquired_handler(data);
191
192     return 1;
193 }
194
195
196 /* Fine Offset Electronics WH0530 Temperature/Rain sensor protocol
197  * aka Agimex Rosenborg 35926 (sold in Denmark)
198  * aka ...
199  *
200  * The sensor sends two identical packages of 71 bits each ~48s. The bits are PWM modulated with On Off Keying
201  * Data consists of 9 bytes with first bit missing
202  *
203  * Extracted data:
204  * 7f 38 a2 8f 02 00 ff e7 51
205  * hh hI IT TT RR RR ?? CC CC
206  *
207  * hh h = Header (first bit is not received and must be added)
208  * II = Sensor ID (guess). Does not change at battery change.
209  * T TT = Temperature (+40*10)
210  * RR RR = Rain count (each count = 0.3mm, LSB first)
211  * ?? = Always 0xFF (maybe reserved for humidity?)
212  * CC = CRC8 with polynomium 0x31
213  * CC = Checksum of previous 7 bytes (binary sum truncated to 8 bit)
214  */
215 static int fineoffset_WH0530_callback(bitbuffer_t *bitbuffer) {
216     bitrow_t *bb = bitbuffer->bb;
217     data_t *data;
218
219     char time_str[LOCAL_TIME_BUFLEN];
220
221     // Validate package
222     if (bitbuffer->bits_per_row[0] != 71        // Match exact length to avoid false positives
223         || (bb[0][0]>>1) != 0x7F                // Check header (two upper nibbles)
224         || (bb[0][1]>>5) != 0x3                 // Check header (third nibble)
225     ) {
226         return 0;
227     }
228
229     // Get time now
230     local_time_str(0, time_str);
231
232     uint8_t buffer[8];
233     bitbuffer_extract_bytes(bitbuffer, 0, 7, buffer, sizeof(buffer)*8);     // Skip first 7 bits
234
235     if (debug_output) {
236         char raw_str[128];
237         for (unsigned n=0; n<sizeof(buffer); n++) { sprintf(raw_str+n*3, "%02x ", buffer[n]); }
238         fprintf(stderr, "Fineoffset_WH0530: Raw %s\n", raw_str);
239     }
240
241     // Verify checksum
242     const uint8_t crc = crc8(buffer, 6, 0x31, 0);
243     const uint8_t checksum = buffer[0] + buffer[1] + buffer[2] + buffer[3] + buffer[4] + buffer[5] + buffer[6];
244     if (crc != buffer[6] || checksum != buffer[7]) {
245         if (debug_output) {
246             fprintf(stderr, "Fineoffset_WH0530: Checksum error: %02x %02x\n", crc, checksum);
247         }
248         return 0;
249     }
250
251     const uint8_t id = (buffer[0]<<4) | (buffer[1]>>4);
252     const float temperature = (float)((uint16_t)(buffer[1] & 0xF)<< 8 | buffer[2]) / 10.0 - 40.0;
253     const float rain = 0.3 * (((uint16_t)buffer[4] << 8) | buffer[3]);
254
255     data = data_make("time",          "",            DATA_STRING, time_str,
256                      "model",         "",            DATA_STRING, "Fine Offset Electronics, WH0530 Temperature/Rain sensor",
257                      "id",            "ID",          DATA_INT, id,
258                      "temperature_C", "Temperature", DATA_FORMAT, "%.01f C", DATA_DOUBLE, temperature,
259                      "rain",          "Rain",        DATA_FORMAT, "%.01f mm", DATA_DOUBLE, rain,
260                      NULL);
261     data_acquired_handler(data);
262
263     return 1;
264 }
265
266
267 static char *output_fields[] = {
268     "time",
269     "model",
270     "id",
271     "temperature_C",
272     "humidity",
273     NULL
274 };
275
276
277 static char *output_fields_WH25[] = {
278     "time",
279     "model",
280     "id",
281     "temperature_C",
282     "humidity",
283     "pressure",
284 //    "raw",
285     NULL
286 };
287
288
289 static char *output_fields_WH0530[] = {
290     "time",
291     "model",
292     "id",
293     "temperature_C",
294     "rain",
295     NULL
296 };
297
298
299 r_device fineoffset_WH2 = {
300     .name           = "Fine Offset Electronics, WH2 Temperature/Humidity Sensor",
301     .modulation     = OOK_PULSE_PWM_RAW,
302     .short_limit    = 800,      // Short pulse 544µs, long pulse 1524µs, fixed gap 1036µs
303     .long_limit     = 2800,     // Maximum pulse period (long pulse + fixed gap)
304     .reset_limit    = 2800,     // We just want 1 package
305     .json_callback  = &fineoffset_WH2_callback,
306     .disabled       = 0,
307     .demod_arg      = 0,
308     .fields         = output_fields
309 };
310
311
312 r_device fineoffset_WH25 = {
313     .name           = "Fine Offset Electronics, WH25 Temperature/Humidity/Pressure Sensor",
314     .modulation     = FSK_PULSE_PCM,
315     .short_limit    = 58,       // Bit width = 58µs (measured across 580 samples / 40 bits / 250 kHz )
316     .long_limit     = 58,       // NRZ encoding (bit width = pulse width)
317     .reset_limit    = 20000,    // Package starts with a huge gap of ~18900 us
318     .json_callback  = &fineoffset_WH25_callback,
319     .disabled       = 0,
320     .demod_arg      = 0,
321     .fields         = output_fields_WH25
322 };
323
324
325 PWM_Precise_Parameters pwm_precise_parameters_fo_wh0530 = {
326     .pulse_tolerance    = 40,
327     .pulse_sync_width   = 0,    // No sync bit used
328 };
329
330 r_device fineoffset_WH0530 = {
331     .name           = "Fine Offset Electronics, WH0530 Temperature/Rain Sensor",
332     .modulation     = OOK_PULSE_PWM_PRECISE,
333     .short_limit    = 504,      // Short pulse 504µs
334     .long_limit     = 1480, // Long pulse 1480µs
335     .reset_limit    = 1200,     // Fixed gap 960µs (We just want 1 package)
336     .json_callback  = &fineoffset_WH0530_callback,
337     .disabled       = 0,
338     .demod_arg      = (uintptr_t)&pwm_precise_parameters_fo_wh0530,
339     .fields         = output_fields_WH0530
340 };