-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
Copy pathfineoffset_wh1080.c
370 lines (319 loc) · 15.1 KB
/
fineoffset_wh1080.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
/** @file
Fine Offset WH1080/WH3080 Weather Station.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
*/
/** @fn int fineoffset_wh1080_callback(r_device *decoder, bitbuffer_t *bitbuffer)
Fine Offset WH1080/WH3080 Weather Station.
This module is based on Stanisław Pitucha ('viraptor' https://github.com/viraptor) code stub for the Digitech XC0348
Weather Station, which seems to be a rebranded Fine Offset WH1080 Weather Station.
Some info and code derived from Kevin Sangelee's page:
http://www.susa.net/wordpress/2012/08/raspberry-pi-reading-wh1081-weather-sensors-using-an-rfm01-and-rfm12b/ .
See also Frank 'SevenW' page https://www.sevenwatt.com/main/wh1080-protocol-v2-fsk/ for some other useful info.
For the WH1080 part I mostly have re-elaborated and merged their works. Credits (and kudos) should go to them all
(and to many others too).
Two packets are sent 31 ms apart. Reports 1 row, 88 pulses.
Data layout:
ff FI IT TT HH SS GG ?R RR BD CC
- F: 4 bit fixed message format
- I: 8 bit device id
- T: 12 bit temperature, offset 40 scale 10, i.e. 0.1C steps -40C (top 2 bits are sign, discard)
- H: 8 bit humidity percent
- S: 8 bit wind speed, 0.34m/s steps
- G: 8 bit gust speed, 0.34m/s steps
- R: 12 bit? rain, 0.3mm steps
- B: 4 bit flags, 0x1 is battery_low
- D: 8 bit wind direction: 00 is N, 02 is NE, 04 is E, etc. up to 0F is seems
- C: 8 bit checksum
## WH1080
(aka Watson W-8681)
(aka Digitech XC0348 Weather Station)
(aka PCE-FWS 20)
(aka Elecsa AstroTouch 6975)
(aka Froggit WH1080)
(aka .....)
This weather station is based on an indoor touchscreen receiver, and on a 5+1 outdoor wireless sensors group
(rain, wind speed, wind direction, temperature, humidity, plus a DCF77 time signal decoder, maybe capable to decode
some other time signal standard).
See the product page here: http://www.foshk.com/weather_professional/wh1080.htm .
It's a very popular weather station, you can easily find it on eBay or Amazon (just do a search for 'WH1080').
The module works fine, decoding all of the data as read into the original console (there is some minimal difference
sometime on the decimals due to the different architecture of the console processor, which is a little less precise).
Please note that the pressure sensor (barometer) is enclosed in the indoor console unit, NOT in the outdoor
wireless sensors group.
That's why it's NOT possible to get pressure data by wireless communication. If you need pressure data you should try
an Arduino/Raspberry solution wired with a BMP180/280 or BMP085 sensor.
Data are transmitted in a 48 seconds cycle (data packet, then wait 48 seconds, then data packet...).
This module is also capable to decode the DCF77/WWVB time signal sent by the time signal decoder
(which is enclosed on the sensor tx): around the minute 59 of the even hours the sensor's TX stops sending weather data,
probably to receive (and sync with) DCF77/WWVB signals.
After around 3-4 minutes of silence it starts to send just time data for some minute, then it starts again with
weather data as usual.
By living in Europe I can only test DCF77 time decoding, so if you live outside Europe and you find garbage instead
of correct time, you should disable/ignore time decoding
(or, better, try to implement a more complete time decoding system :) ).
To recognize message type (weather or time) you can use the 'msg_type' field on json output:
- msg_type 0 = weather data
- msg_type 1 = time data
The 'Total rainfall' field is a cumulative counter, increased by 0.3 millimeters of rain at once.
The station comes in three TX operating frequency versions: 433, 868.3 and 915 Mhz.
The module is tested with a 'Froggit WH1080' on 868.3 Mhz, using '-f 868140000' as frequency parameter and
it works fine (compiled in x86, RaspberryPi 1 (v2), Raspberry Pi2 and Pi3, and also on a BananaPi platform. Everything is OK).
I don't know if it works also with ALL of the rebranded versions/models of this weather station.
I guess it *should* do... Just give it a try! :)
## WH3080
The WH3080 Weather Station seems to be basically a WH1080 with the addition of UV/Light sensors onboard.
The weather/datetime radio protocol used for both is identical, the only difference is for the addition in the WH3080
of the UV/Light part.
UV/Light radio messages are disjointed from (and shorter than) weather/datetime radio messages and are transmitted
in a 'once-every-60-seconds' cycle.
The module is able to decode all kind of data coming from the WH3080: weather, datetime, UV and light plus some
error/status code.
To recognize message type (weather, datetime or UV/light) you can refer to the 'msg_type' field on json output:
- msg_type 0 = weather data
- msg_type 1 = datetime data
- msg_type 2 = UV/light data
While the LCD console seems to truncate/round values in order to best fit to its display, this module keeps entire values
as received from externals sensors (exception made for some rounding while converting values from lux to watts/m and fc),
so you can see -sometimes- some little difference between module's output and LCD console's values.
2016-2017 Nicola Quiriti ('ovrheat' - 'seven')
*/
#include "decoder.h"
static int const wind_dir_degr[]= {0, 23, 45, 68, 90, 113, 135, 158, 180, 203, 225, 248, 270, 293, 315, 338};
// The transmission differences are 8 preamble bits (EPB) and 7 preamble bits (SPB)
#define EPB 8
#define SPB 7
#define TYPE_OOK 1
#define TYPE_FSK 2
static int fineoffset_wh1080_callback(r_device *decoder, bitbuffer_t *bitbuffer, int type)
{
data_t *data;
uint8_t *br;
int msg_type; // 0=Weather 1=Datetime 2=UV/Light
int sens_msg = 10; // 10=Weather/Time sensor 7=UV/Light sensor
uint8_t bbuf[11]; // max 8 / 11 bytes needed
int preamble; // 7 or 8 preamble bits
int temp_raw;
float temperature;
uint8_t const fsk_preamble[] = {0xAA, 0x2D, 0xD4};
if (bitbuffer->num_rows != 1) {
return DECODE_ABORT_EARLY;
}
if (type == TYPE_FSK) {
int bit_offset = bitbuffer_search(bitbuffer, 0, 0, fsk_preamble, sizeof(fsk_preamble) * 8) + sizeof(fsk_preamble) * 8;
if (bit_offset + sizeof(bbuf) * 8 > bitbuffer->bits_per_row[0]) { // Did not find a big enough package
decoder_logf_bitbuffer(decoder, 1, __func__, bitbuffer, "short package. Header index: %u", bit_offset);
return DECODE_ABORT_LENGTH;
}
bitbuffer_extract_bytes(bitbuffer, 0, bit_offset-8, bbuf, sizeof(bbuf) * 8);
br = bbuf;
br[0] = 0xFF; // Emulate OOK payload
preamble = EPB;
}
else if (bitbuffer->bits_per_row[0] >= 88 && bitbuffer->bits_per_row[0] < 100) { // FineOffset WH1080/3080 Weather data msg
preamble = EPB;
sens_msg = 10;
br = bitbuffer->bb[0];
}
else if (bitbuffer->bits_per_row[0] == 87) { // FineOffset WH1080/3080 Weather data msg (different version (newest?))
preamble = SPB;
sens_msg = 10;
/* 7 bits of preamble, bit shift the whole buffer and fix the bytestream */
bitbuffer_extract_bytes(bitbuffer, 0, 7, bbuf + 1, 10 * 8);
bbuf[0] = (bitbuffer->bb[0][0] >> 1) | 0x80;
br = bbuf;
}
else if (bitbuffer->bits_per_row[0] == 64) { // FineOffset WH3080 UV/Light data msg
preamble = EPB;
sens_msg = 7;
br = bitbuffer->bb[0];
}
else if (bitbuffer->bits_per_row[0] == 63) { // FineOffset WH3080 UV/Light data msg (different version (newest?))
preamble = SPB;
sens_msg = 7;
/* 7 bits of preamble, bit shift the whole buffer and fix the bytestream */
bitbuffer_extract_bytes(bitbuffer, 0, 7, bbuf + 1, 7 * 8);
bbuf[0] = (bitbuffer->bb[0][0] >> 1) | 0x80;
br = bbuf;
}
else {
return DECODE_ABORT_LENGTH;
}
decoder_log_bitrow(decoder, 1, __func__, br, sens_msg * 8, "Fine Offset WH1080 data ");
if (br[0] != 0xff) {
return DECODE_FAIL_SANITY; // preamble missing
}
if (sens_msg == 10) {
if (crc8(br, 11, 0x31, 0xff)) { // init is 0 if we skip the preamble
return DECODE_FAIL_MIC; // crc mismatch
}
}
else {
if (crc8(br, 8, 0x31, 0xff)) { // init is 0 if we skip the preamble
return DECODE_FAIL_MIC; // crc mismatch
}
}
if ((br[1] >> 4) == 0x0a) {
msg_type = 0; // WH1080/3080 Weather msg
}
else if ((br[1] >> 4) == 0x0b) {
msg_type = 1; // WH1080/3080 Datetime msg
}
else if ((br[1] >> 4) == 0x07) {
msg_type = 2; // WH3080 UV/Light msg
}
else {
// 0x03 is WH0530, Alecto WS-1200
// 0x05 is Alecto WS-1200 DCF77
return DECODE_FAIL_SANITY;
}
// GETTING WEATHER SENSORS DATA
if (type == TYPE_OOK) {
temp_raw = ((br[2] & 0x03) << 8) | br[3]; // only 10 bits, discard top bits
temperature = (temp_raw - 400) * 0.1f;
}
else {
temp_raw = ((br[2] & 0x0F) << 8) | br[3];
if (temp_raw & 0x800) {
temp_raw &= 0x7FF; // remove sign bit
temp_raw = -temp_raw; // reverse magnitude
}
temperature = (temp_raw) * 0.1f;
}
int humidity = br[4];
int direction_deg = wind_dir_degr[br[9] & 0x0f];
float speed = (br[5] * 0.34f) * 3.6f; // m/s -> km/h
float gust = (br[6] * 0.34f) * 3.6f; // m/s -> km/h
int rain_raw = ((br[7] & 0x0f) << 8) | br[8];
float rain = rain_raw * 0.3f;
int device_id = (br[1] << 4 & 0xf0) | (br[2] >> 4);
int battery_low = (br[9] >> 4) == 1;
// GETTING UV DATA
int uv_sensor_id = (br[1] << 4 & 0xf0) | (br[2] >> 4);
int uv_status_ok = br[3] == 85;
int uv_index = br[2] & 0x0F;
// GETTING LIGHT DATA
int light = (br[4] << 16) | (br[5] << 8) | br[6];
double lux = light * 0.1;
float wm;
if (preamble == SPB)
wm = (light / 1265.8f);
else //EPB
wm = (light / 6830.0f);
// GETTING TIME DATA
int signal_type = ((br[2] & 0x0F) == 10);
char const *signal_type_str = signal_type ? "DCF77" : "WWVB/MSF";
int hours = ((br[3] & 0x30) >> 4) * 10 + (br[3] & 0x0F);
int minutes = ((br[4] & 0xF0) >> 4) * 10 + (br[4] & 0x0F);
int seconds = ((br[5] & 0xF0) >> 4) * 10 + (br[5] & 0x0F);
int year = ((br[6] & 0xF0) >> 4) * 10 + (br[6] & 0x0F) + 2000;
int month = ((br[7] & 0x10) >> 4) * 10 + (br[7] & 0x0F);
int day = ((br[8] & 0xF0) >> 4) * 10 + (br[8] & 0x0F);
// PRESENTING DATA
if (msg_type == 0) {
/* clang-format off */
data = data_make(
"model", "", DATA_STRING, "Fineoffset-WHx080",
"subtype", "Msg type", DATA_INT, msg_type,
"id", "Station ID", DATA_INT, device_id,
"battery_ok", "Battery", DATA_INT, !battery_low,
"temperature_C", "Temperature", DATA_FORMAT, "%.1f C", DATA_DOUBLE, temperature,
"humidity", "Humidity", DATA_FORMAT, "%u %%", DATA_INT, humidity,
"wind_dir_deg", "Wind Direction", DATA_INT, direction_deg,
"wind_avg_km_h", "Wind avg speed", DATA_FORMAT, "%.2f", DATA_DOUBLE, speed,
"wind_max_km_h", "Wind gust", DATA_FORMAT, "%.2f", DATA_DOUBLE, gust,
"rain_mm", "Total rainfall", DATA_FORMAT, "%.1f", DATA_DOUBLE, rain,
"mic", "Integrity", DATA_STRING, "CRC",
NULL);
/* clang-format on */
}
else if (msg_type == 1) {
char clock_str[23];
snprintf(clock_str, sizeof(clock_str), "%04d-%02d-%02dT%02d:%02d:%02d",
year, month, day, hours, minutes, seconds);
/* clang-format off */
data = data_make(
"model", "", DATA_STRING, "Fineoffset-WHx080",
"subtype", "Msg type", DATA_INT, msg_type,
"id", "Station ID", DATA_INT, device_id,
"signal", "Signal Type", DATA_STRING, signal_type_str,
"radio_clock", "Radio Clock", DATA_STRING, clock_str,
"mic", "Integrity", DATA_STRING, "CRC",
NULL);
/* clang-format on */
}
else {
/* clang-format off */
data = data_make(
"model", "", DATA_STRING, "Fineoffset-WHx080",
"subtype", "Msg type", DATA_INT, msg_type,
"uv_sensor_id", "UV Sensor ID", DATA_INT, uv_sensor_id,
"uv_status", "Sensor Status", DATA_STRING, uv_status_ok ? "OK" : "ERROR",
"uv_index", "UV Index", DATA_INT, uv_index,
"lux", "Lux", DATA_FORMAT, "%.1f", DATA_DOUBLE, lux,
"wm", "Watts/m", DATA_FORMAT, "%.2f", DATA_DOUBLE, wm,
"mic", "Integrity", DATA_STRING, "CRC",
NULL);
/* clang-format on */
}
decoder_output_data(decoder, data);
return 1;
}
/**
Fine Offset WH1080/WH3080 Weather Station.
@sa fineoffset_wh1080_callback()
*/
static int fineoffset_wh1080_callback_ook(r_device *decoder, bitbuffer_t *bitbuffer)
{
return fineoffset_wh1080_callback(decoder, bitbuffer, TYPE_OOK);
}
/**
Fine Offset WH1080/WH3080 Weather Station.
@sa fineoffset_wh1080_callback()
*/
static int fineoffset_wh1080_callback_fsk(r_device *decoder, bitbuffer_t *bitbuffer)
{
return fineoffset_wh1080_callback(decoder, bitbuffer, TYPE_FSK);
}
static char const *const output_fields[] = {
"model",
"subtype",
"id",
"battery_ok",
"temperature_C",
"humidity",
"wind_dir_deg",
"wind_avg_km_h",
"wind_max_km_h",
"rain_mm",
"signal",
"radio_clock",
"sensor_code",
"uv_sensor_id",
"uv_status",
"uv_index",
"lux",
"wm",
"mic",
NULL,
};
r_device const fineoffset_wh1080 = {
.name = "Fine Offset Electronics WH1080/WH3080 Weather Station",
.modulation = OOK_PULSE_PWM,
.short_width = 544, // Short pulse 544µs, long pulse 1524µs, fixed gap 1036µs
.long_width = 1524, // Maximum pulse period (long pulse + fixed gap)
.reset_limit = 2800, // We just want 1 package
.decode_fn = &fineoffset_wh1080_callback_ook,
.fields = output_fields,
};
r_device const fineoffset_wh1080_fsk = {
.name = "Fine Offset Electronics WH1080/WH3080 Weather Station (FSK)",
.modulation = FSK_PULSE_PCM,
.short_width = 58,
.long_width = 58,
.reset_limit = 5800,
.decode_fn = &fineoffset_wh1080_callback_fsk,
.fields = output_fields,
};