Skip to content
Permalink
master
Switch branches/tags
Go to file
5 contributors

Users who have contributed to this file

@zuckschwerdt @Mindavi @obones @etihwnad @bitfliq
/** @file
Ambient Weather WH31E, EcoWitt WH40 protocol.
Copyright (C) 2018 Christian W. Zuckschwerdt <zany@triq.net>
based on protocol analysis by James Cuff and Michele Clamp,
EcoWitt WH40 analysis by Helmut Bachmann,
Ecowitt WS68 analysis by Tolip Wen.
EcoWitt WH31B analysis by Michael Turk.
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.
*/
/**
Ambient Weather WH31E protocol.
915 MHz FSK PCM Thermo-Hygrometer Sensor (bundled as Ambient Weather WS-3000-X5).
Note that Ambient Weather and EcoWitt are likely rebranded Fine Offset products.
56 us bit length with a warm-up of 1336 us mark(pulse), 1996 us space(gap),
a preamble of 48 bit flips (0xaaaaaaaaaaaa) and a 0x2dd4 sync-word.
Data layout:
YY II CT TT HH XX AA ?? ?? ?? ??
- Y is a fixed Type Code of 0x30
- I is a device ID
- C is the Channel number (only the lower 3 bits)
- T is 12bits Temperature in C, scaled by 10, offset 400
- H is Humidity
- X is CRC-8, poly 0x31, init 0x00
- A is SUM-8
Example packets:
{177} aa aa aa aa aa aa 2d d4 30 c3 8 20a 5e df bc 07 56 a7 ae 00 00 00 00
{178} aa aa aa aa aa aa 2d d4 30 44 9 21a 39 5a b3 07 45 04 5f 00 00 00 00
Some payloads:
30 c3 81 d5 5c 2a cf 08 35 44 2c
30 35 c2 2f 3c 0f a1 07 52 29 9f
30 35 c2 2e 3c fb 8c 07 52 29 9f
30 c9 a2 1e 40 0c 05 07 34 c6 b1
30 2b b2 14 3d 94 f2 08 53 78 e6
30 c9 a2 1f 40 f8 f2 07 34 c6 b1
30 44 92 13 3e 0e 65 07 45 04 5f
30 44 92 15 3d 07 5f 07 45 04 5f
30 c3 81 d6 5b 90 35 08 35 44 2c
Ambient Weather WH31E Radio Controlled Clock (RCC) packet WWVB
These packets are sent with this schedule, according to the manual:
After the remote sensor is powered up, the sensor will transmit weather
data for 30 seconds, and then the sensor will begin radio controlled clock
(RCC) reception. During the RCC time reception period (maximum 5 minutes),
no weather data will be transmitted to avoid interference.
If the signal reception is not successful within 3 minute, the signal
search will be cancelled and will automatically resume every two hours
until the signal is successfully captured. The regular RF link will resume
once RCC reception routine is finished.
/ time message type 0x52
| / station id
| | / unknown
| | | / 20xx year in BCD
| | | | / month in BCD
| | | | | / day in BCD
| | | | | | / hour in BCD
| | | | | | | / minute in BCD
| | | | | | | | / second in BCD
| | | | | | | | | / CRC-8, poly 0x31, init 0x00
| | | | | | | | | | / SUM-8
YY II UU YY MM DD HH mm SS CC XX
0 1 2 3 4 5 6 7 8 9 10 - byte index
UU has kept the value 0x4a. Data it may represent that is broadcast from WWVB:
- Daylight savings upcoming/active (it WAS active during the captures) (2 bits)
- Leap year (1 bit)
- Leap second at the end of this month (1 bit)
- DUT1, difference between UTC and UT1 (4-7 bits depending on re-encoding)
The upper bits of the upper nibbles M, D, H, m, S may possibly be used to
encode this information, given their maximum valid digits of 1, 3, 2, 6, 6,
respectively.
Packets observed
Reception time Payload
2020-10-20T02:06:55.809Z 52 27 4a 20 10 20 02 06 55 05 75
2020-10-20T02:08:02.793Z 52 27 4a 20 10 20 02 08 02 81 a0
2020-10-20T07:35:04.290Z 52 75 4a 20 10 20 07 35 03 8a 2a
2020-10-20T07:35:52.394Z 52 58 4a 20 10 20 07 35 51 48 19
2020-10-20T07:36:06.287Z 52 75 4a 20 10 20 07 36 05 01 a4
2020-10-20T07:36:55.305Z 52 58 4a 20 10 20 07 36 54 90 65
2020-10-20T07:37:08.284Z 52 75 4a 20 10 20 07 37 07 97 3d
2020-10-20T07:37:58.355Z 52 58 4a 20 10 20 07 37 57 37 10
2020-10-20T07:38:10.280Z 52 75 4a 20 10 20 07 38 09 11 ba
2020-10-20T07:39:01.398Z 52 58 4a 20 10 20 07 39 00 b3 37
2020-10-20T08:05:50.830Z 52 a0 4a 20 10 20 08 05 50 0f f8
2020-10-20T08:06:58.862Z 52 a0 4a 20 10 20 08 06 58 9b 8d
2020-10-20T08:08:06.883Z 52 a0 4a 20 10 20 08 08 06 97 39
2020-10-20T08:09:14.785Z 52 a0 4a 20 10 20 08 09 14 42 f3
EcoWitt WH40 protocol.
Seems to be the same as Fine Offset WH5360 or Ecowitt WH5360B.
Data layout:
YY 00 IIII FF RRRR XX AA 00 02 ?? 00 00
- Y is a fixed Type Code of 0x40
- I is a device ID
- F is perhaps flags, but only seen fixed 0x10 so far
- R is the rain bucket tip count, 0.1mm increments
- X is CRC-8, poly 0x31, init 0x00
- A is SUM-8
Some payloads:
4000 cd6f 10 0000 64 f0 ; 00 027b 0000
4000 cd6f 10 0001 55 e2 ; 00 02f6 0000
4000 cd6f 10 0002 06 94 ; 00 02ed 0000
4000 cd6f 10 0003 37 c6 ; 00 02db 0000
4000 cd6f 10 0004 a0 30 ; 00 02b7 0000
4000 cd6f 10 0005 91 22 ; 00 02de 0000
4000 cd6f 10 0006 c2 54 ; 00 02bd 0000
4000 cd6f 10 0007 f3 86 ; 00 027b 0000
4000 cd6f 10 0008 dd 71 ; 00 02f6 0000
4000 cd6f 10 0009 ec 81 ; 00 02ed 0000
4000 cd6f 10 000a bf 55 ; 00 02db 0000
Samples with 1.2V battery (last 2 samples contain 1 manual bucket tip)
4000 cd6f 10 0000 64 f0 ; 00 01de 00b0
4000 cd6f 10 0000 64 f0 ; 00 02de 00b0
4000 cd6f 10 0000 64 f0 ; 00 02bd 0000
4000 cd6f 10 0001 55 e2 ; 00 027b 0000
4000 cd6f 10 0001 55 e2 ; 00 027b 0000
Samples with 0.9V battery (last 3 samples contain 1 manual bucket tip)
4000 cd6f 10 0000 64 f0 ; 00 16de 0000
4000 cd6f 10 0000 64 f0 ; 00 02de 0000
4000 cd6f 10 0001 55 e2 ; 00 02bd 0000
4000 cd6f 10 0001 55 e2 ; 00 027b 0000
4000 cd6f 10 0001 55 e2 ; 00 027b 0000
Ecowitt WS68 Anemometer protocol.
Data layout:
TYPE:8h ?8h ID:16h LUX:16h BATT:8h WDIR_H:4h 4h8h8h WSPEED:8h WDIR_LO:8h WGUST:8h ?8h CRC:8h SUM:8h ?8h4h
Some payloads:
68 0000 c5 0000 4b 0f ffff 00 5a 00 00 d0af 104
68 0000 c5 0000 4b 0f ffff 00 b4 00 00 79b2 102
68 0000 c5 0000 4b 0f ffff 7e e0 94 00 75ec 102
68 0000 c5 0000 4b 2f ffff 00 0e 00 00 8033 208
68 0000 c5 000f 4b 0f ffff 00 2e 00 00 d395 108
68 0000 c5 0107 4b 0f ffff 00 2e 00 02 a663 100
*/
#include "decoder.h"
static int ambientweather_whx_decode(r_device *decoder, bitbuffer_t *bitbuffer)
{
data_t *data;
int events = 0;
uint8_t b[18]; // actually only 6/9/17.5 bytes, no indication what the last 5 might be
int row;
int msg_type;
int id, channel, battery_ok, temp_raw;
int humidity, rain_raw;
float temp_c;
char extra[11];
uint8_t const wh31e_type_code = 0x30; // 48
uint8_t const wh31b_type_code = 0x37; // 55
uint8_t const preamble[] = {0xaa, 0x2d, 0xd4}; // (partial) preamble and sync word
for (row = 0; row < bitbuffer->num_rows; ++row) {
// Validate message and reject it as fast as possible : check for preamble
unsigned start_pos = bitbuffer_search(bitbuffer, row, 0, preamble, 24);
// no preamble detected, move to the next row
if (start_pos == bitbuffer->bits_per_row[row])
continue; // DECODE_ABORT_EARLY
decoder_logf(decoder, 1, __func__, "WH31E/WH31B/WH40 detected, buffer is %u bits length", bitbuffer->bits_per_row[row]);
// remove preamble, keep whole payload
bitbuffer_extract_bytes(bitbuffer, row, start_pos + 24, b, 18 * 8);
msg_type = b[0];
if (msg_type == wh31e_type_code || msg_type == wh31b_type_code) {
uint8_t c_crc = crc8(b, 6, 0x31, 0x00);
if (c_crc) {
decoder_logf(decoder, 1, __func__, "WH31E/WH31B (%d) bad CRC", msg_type);
continue; // DECODE_FAIL_MIC
}
uint8_t c_sum = add_bytes(b, 6) - b[6];
if (c_sum) {
decoder_logf(decoder, 1, __func__, "WH31E/WH31B (%d) bad SUM", msg_type);
continue; // DECODE_FAIL_MIC
}
id = b[1];
battery_ok = (b[2] >> 7);
channel = ((b[2] & 0x70) >> 4) + 1;
temp_raw = ((b[2] & 0x0f) << 8) | (b[3]);
temp_c = (temp_raw - 400) * 0.1f;
humidity = b[4];
sprintf(extra, "%02x%02x%02x%02x%02x", b[6], b[7], b[8], b[9], b[10]);
/* clang-format off */
data = data_make(
"model", "", DATA_COND, msg_type == 0x30, DATA_STRING, "AmbientWeather-WH31E",
"model", "", DATA_COND, msg_type == 0x37, DATA_STRING, "AmbientWeather-WH31B",
"id" , "", DATA_INT, id,
"channel", "Channel", DATA_INT, channel,
"battery_ok", "Battery", DATA_INT, battery_ok,
"temperature_C", "Temperature", DATA_FORMAT, "%.1f C", DATA_DOUBLE, temp_c,
"humidity", "Humidity", DATA_FORMAT, "%u %%", DATA_INT, humidity,
"data", "Extra Data", DATA_STRING, extra,
"mic", "Integrity", DATA_STRING, "CRC",
NULL);
/* clang-format on */
decoder_output_data(decoder, data);
events++;
}
else if (msg_type == 0x52) {
// WH31E (others?) RCC
uint8_t c_crc = crc8(b, 10, 0x31, 0x00);
if (c_crc) {
decoder_log(decoder, 1, __func__, "WH31E RCC bad CRC");
continue; // DECODE_FAIL_MIC
}
uint8_t c_sum = add_bytes(b, 10) - b[10];
if (c_sum) {
decoder_log(decoder, 1, __func__, "WH31E RCC bad SUM");
continue; // DECODE_FAIL_MIC
}
id = b[1];
int unknown = b[2];
int year = ((b[3] & 0xF0) >> 4) * 10 + (b[3] & 0x0F) + 2000;
int month = ((b[4] & 0x10) >> 4) * 10 + (b[4] & 0x0F);
int day = ((b[5] & 0x30) >> 4) * 10 + (b[5] & 0x0F);
int hours = ((b[6] & 0x30) >> 4) * 10 + (b[6] & 0x0F);
int minutes = ((b[7] & 0x70) >> 4) * 10 + (b[7] & 0x0F);
int seconds = ((b[8] & 0x70) >> 4) * 10 + (b[8] & 0x0F);
char clock_str[23];
sprintf(clock_str, "%04d-%02d-%02dT%02d:%02d:%02dZ",
year, month, day, hours, minutes, seconds);
/* clang-format off */
data = data_make(
"model", "", DATA_STRING, "AmbientWeather-WH31E",
"id" , "Station ID", DATA_INT, id,
"data", "Unknown", DATA_INT, unknown,
"radio_clock", "Radio Clock", DATA_STRING, clock_str,
"mic", "Integrity", DATA_STRING, "CRC",
NULL);
/* clang-format on */
decoder_output_data(decoder, data);
events++;
}
else if (msg_type == 0x40) {
// WH40
uint8_t c_crc = crc8(b, 8, 0x31, 0x00);
if (c_crc) {
decoder_log(decoder, 1, __func__, "WH40 bad CRC");
continue; // DECODE_FAIL_MIC
}
uint8_t c_sum = add_bytes(b, 8) - b[8];
if (c_sum) {
decoder_log(decoder, 1, __func__, "WH40 bad SUM");
continue; // DECODE_FAIL_MIC
}
id = (b[2] << 8) | b[3];
battery_ok = (b[4] >> 7);
channel = ((b[4] & 0x70) >> 4) + 1;
rain_raw = (b[5] << 8) | b[6];
sprintf(extra, "%02x%02x%02x%02x%02x", b[9], b[10], b[11], b[12], b[13]);
/* clang-format off */
data = data_make(
"model", "", DATA_STRING, "EcoWitt-WH40",
"id" , "", DATA_INT, id,
//"channel", "Channel", DATA_INT, channel,
//"battery_ok", "Battery", DATA_INT, battery_ok,
"rain_mm", "Total Rain", DATA_FORMAT, "%.1f mm", DATA_DOUBLE, rain_raw * 0.1,
"data", "Extra Data", DATA_STRING, extra,
"mic", "Integrity", DATA_STRING, "CRC",
NULL);
/* clang-format on */
decoder_output_data(decoder, data);
events++;
}
else if (msg_type == 0x68) {
// WS68
uint8_t c_crc = crc8(b, 15, 0x31, 0x00);
if (c_crc) {
decoder_log(decoder, 1, __func__, "WH68 bad CRC");
continue; // DECODE_FAIL_MIC
}
uint8_t c_sum = add_bytes(b, 15) - b[15];
if (c_sum) {
decoder_log(decoder, 1, __func__, "WH68 bad SUM");
continue; // DECODE_FAIL_MIC
}
id = (b[2] << 8) | b[3];
int lux = (b[4] << 8) | b[5];
int batt = b[6];
battery_ok = batt > 0x30; // wild guess
int wspeed = b[10];
int wgust = b[12];
int wdir = ((b[7] & 0x20) >> 5) | b[11];
sprintf(extra, "%02x %02x%01x", b[13], b[16], b[17] >> 4);
/* clang-format off */
data = data_make(
"model", "", DATA_STRING, "EcoWitt-WS68",
"id" , "", DATA_INT, id,
"battery_raw", "Battery Raw", DATA_INT, batt,
"battery_ok", "Battery", DATA_INT, battery_ok,
"lux_raw", "lux", DATA_INT, lux,
"wind_avg_raw", "Wind Speed", DATA_INT, wspeed,
"wind_max_raw", "Wind Gust", DATA_INT, wgust,
"wind_dir_deg", "Wind dir", DATA_INT, wdir,
"data", "Extra Data", DATA_STRING, extra,
"mic", "Integrity", DATA_STRING, "CRC",
NULL);
/* clang-format on */
decoder_output_data(decoder, data);
events++;
}
else {
decoder_logf(decoder, 1, __func__, "unknown message type %02x (expected 0x30/0x40/0x68)", msg_type);
}
}
return events;
}
static char *output_fields[] = {
"model",
"id",
"channel",
"battery_ok",
"temperature_C",
"humidity",
"rain_mm",
"lux",
"wind_avg_km_h",
"wind_max_km_h",
"wind_dir_deg",
"data",
"radio_clock",
"mic",
NULL,
};
r_device ambientweather_wh31e = {
.name = "Ambient Weather WH31E Thermo-Hygrometer Sensor, EcoWitt WH40 rain gauge",
.modulation = FSK_PULSE_PCM,
.short_width = 56,
.long_width = 56,
.reset_limit = 1500,
.gap_limit = 1800,
.decode_fn = &ambientweather_whx_decode,
.fields = output_fields,
};