1 /* Fine Offset Electronics sensor protocol
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.
12 #include "pulse_demod.h"
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)
22 * The sensor sends two identical packages of 48 bits each ~48s. The bits are PWM modulated with On Off Keying
24 * The data is grouped in 6 bytes / 12 nibbles
25 * [pre] [pre] [type] [id] [id] [temp] [temp] [temp] [humi] [humi] [crc] [crc]
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
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/
36 static int fineoffset_WH2_callback(bitbuffer_t *bitbuffer) {
37 bitrow_t *bb = bitbuffer->bb;
40 char time_str[LOCAL_TIME_BUFLEN];
42 if (debug_output > 1) {
43 fprintf(stderr,"Possible fineoffset: ");
44 bitbuffer_print(bitbuffer);
52 const uint8_t polynomial = 0x31; // x8 + x5 + x4 + 1 (x8 is implicit)
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)
61 local_time_str(0, time_str);
63 // Nibble 3,4 contains id
64 id = ((bb[0][1]&0x0F) << 4) | ((bb[0][2]&0xF0) >> 4);
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];
70 temp &= 0x7FF; // remove sign bit
71 temp = -temp; // reverse magnitude
73 temperature = (float)temp / 10;
75 // Nibble 8,9 contains humidity
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);
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,
92 data_acquired_handler(data);
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,
102 data_acquired_handler(data);
110 /* Fine Offset Electronics WH25 Temperature/Humidity/Pressure sensor protocol
112 * The sensor sends a package each ~64 s with a width of ~28 ms. The bits are PCM modulated with Frequency Shift Keying
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
119 * ?I IT TT HH PP PP CC BB
120 * aa 2d d4 e5 02 72 28 27 21 c9 bb aa
122 * II = Sensor ID (based on 2 different sensors). Does not change at battery change.
123 * T TT = Temperature (+40*10)
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)
130 static int fineoffset_WH25_callback(bitbuffer_t *bitbuffer) {
132 char time_str[LOCAL_TIME_BUFLEN];
135 if (bitbuffer->bits_per_row[0] < 440 || bitbuffer->bits_per_row[0] > 510) { // Nominal size is 488 bit periods
140 local_time_str(0, time_str);
142 // Find a data package and extract data buffer
143 static const uint8_t HEADER[] = { 0xAA, 0x2D, 0xD4 };
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
148 fprintf(stderr, "Fineoffset_WH25: short package. Header index: %u\n", bit_offset);
149 bitbuffer_print(bitbuffer);
153 bitbuffer_extract_bytes(bitbuffer, 0, bit_offset, buffer, sizeof(buffer)*8);
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);
162 uint8_t checksum = 0, bitsum = 0;
163 for (size_t n=3; n<=8; ++n) {
164 checksum += buffer[n];
167 bitsum = (bitsum << 4) | (bitsum >> 4); // Swap nibbles
168 if (checksum != buffer[9] || bitsum != buffer[10]) {
170 fprintf(stderr, "Fineoffset_WH25: Checksum error: %02x %02x\n", checksum, bitsum);
171 bitbuffer_print(bitbuffer);
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;
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,
190 data_acquired_handler(data);
196 /* Fine Offset Electronics WH0530 Temperature/Rain sensor protocol
197 * aka Agimex Rosenborg 35926 (sold in Denmark)
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
204 * 7f 38 a2 8f 02 00 ff e7 51
205 * hh hI IT TT RR RR ?? CC CC
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)
215 static int fineoffset_WH0530_callback(bitbuffer_t *bitbuffer) {
216 bitrow_t *bb = bitbuffer->bb;
219 char time_str[LOCAL_TIME_BUFLEN];
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)
230 local_time_str(0, time_str);
233 bitbuffer_extract_bytes(bitbuffer, 0, 7, buffer, sizeof(buffer)*8); // Skip first 7 bits
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);
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]) {
246 fprintf(stderr, "Fineoffset_WH0530: Checksum error: %02x %02x\n", crc, checksum);
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]);
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,
261 data_acquired_handler(data);
267 static char *output_fields[] = {
277 static char *output_fields_WH25[] = {
289 static char *output_fields_WH0530[] = {
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,
308 .fields = output_fields
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,
321 .fields = output_fields_WH25
325 PWM_Precise_Parameters pwm_precise_parameters_fo_wh0530 = {
326 .pulse_tolerance = 40,
327 .pulse_sync_width = 0, // No sync bit used
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,
338 .demod_arg = (uintptr_t)&pwm_precise_parameters_fo_wh0530,
339 .fields = output_fields_WH0530