Add support for Fine Offset WH3080 weather station.

This commit is contained in:
ovrheat 2017-04-02 01:12:34 +02:00
parent e6608c4f2e
commit 1ca9779307
3 changed files with 256 additions and 129 deletions

View file

@ -19,3 +19,4 @@ Baruch Even <baruch@ev-en.org>
Andrea <andrea@andreaaizza.com>
Helge Weissig <helgew@grajagan.org>
Robert Fraczkiewicz <aromring@gmail.com>
Nicola Quiriti <nik@wifi4all.it>

View file

@ -113,7 +113,7 @@ Supported device protocols:
[29] Chuango Security Technology
[30] Generic Remote SC226x EV1527
[31] TFA-Twin-Plus-30.3049 and Ea2 BL999
[32] Fine Offset WH1080 Weather Station
[32] Fine Offset Electronics WH1080/WH3080 Weather Station
[33] WT450
[34] LaCrosse WS-2310 Weather Station
[35] Esperanza EWS

View file

@ -1,14 +1,8 @@
/*
* *** Fine Offset WH1080 Weather Station ***
* (aka Watson W-8681)
* (aka Digitech XC0348 Weather Station)
* (aka PCE-FWS 20)
* (aka Elecsa AstroTouch 6975)
* (aka Froggit WH1080)
* (aka .....)
* *** Fine Offset WH1080/WH3080 Weather Station ***
*
* This module is based on Stanisław Pitucha ('viraptor' https://github.com/viraptor ) earl code for the Digitech XC0348
* 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:
@ -16,9 +10,18 @@
*
* See also Frank 'SevenW' page ( https://www.sevenwatt.com/main/wh1080-protocol-v2-fsk/ ) for some other useful info.
*
* I only have re-elaborated and merged their works. Credits (and kudos) should go to them all (and to many others too).
* 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).
*
*********************
*****************************************
* 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
@ -26,45 +29,63 @@
* 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 seems to work fine, decoding all of the data as read into the original console (there is some minimal difference
* 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 or BMP085 sensor.
* an Arduino/Raspberry solution wired with a BMP180/280 or BMP085 sensor.
*
* Data are trasmitted in a 48 seconds cycle (data packet, then wait 48 seconds, then data packet...).
*
* This module is also capable to decode the DCF77 time signal sent by the wireless time signal decoder:
* around the minute 59 of the even hours the sensor's TX stops sending weather data, probably to receive (and sync with)
* DCF77 signal.
* 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
*
* 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 time decoding (or, better, try to implement a more complete time decoding system :) ).
*
* 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.
* I've had tested the module with a 'Froggit WH1080' on 868.3 Mhz, using '-f 868140000' as frequency parameter and
* it works fine
* (compiled in x86, RaspberryPi 1 (v2) and RaspberryPi 2, and also on a BananaPi platform. Everything is OK).
* I don't know if it works also with other versions and, generally speaking, with ALL of the rebranded versions of
* this weather station.
* 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! :)
*
*
* ***TODO***: check if negative temperature values (and sign) are OK (no real winter this year where I live, so cannot test...) .
*
*
* 2016 Nicola Quiriti ('ovrheat')
*****************************************
* 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')
*
*
*/
@ -80,17 +101,15 @@
unsigned short msg_type = 0; // 0=Weather 1=Time
char* wind_dir_string[] = {"N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW",};
char* wind_dir_degr[]= {"0", "23", "45", "68", "90", "113", "135", "158", "180", "203", "225", "248", "270", "293", "315", "338",};
static const char* wind_dir_string[] = {"N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW",};
static const char* wind_dir_degr[]= {"0", "23", "45", "68", "90", "113", "135", "158", "180", "203", "225", "248", "270", "293", "315", "338",};
static unsigned short get_device_id(const uint8_t* br) {
unsigned short get_device_id(const uint8_t* br) {
return (br[1] << 4 & 0xf0 ) | (br[2] >> 4);
}
static char* get_battery(const uint8_t* br) {
char* get_battery(const uint8_t* br) {
if ((br[9] >> 4) != 1) {
return "OK";
} else {
@ -100,92 +119,127 @@ static char* get_battery(const uint8_t* br) {
// ------------ WEATHER SENSORS DECODING ----------------------------------------------------
static float get_temperature(const uint8_t* br) {
float get_temperature(const uint8_t* br) {
const int temp_raw = (br[2] << 8) + br[3];
return ((temp_raw & 0x0fff) - 0x190) / 10.0;
}
static int get_humidity(const uint8_t* br) {
int get_humidity(const uint8_t* br) {
return br[4];
}
static const char* get_wind_direction_str(const uint8_t* br) {
char* get_wind_direction_str(const uint8_t* br) {
return wind_dir_string[br[9] & 0x0f];
}
static const char* get_wind_direction_deg(const uint8_t* br) {
char* get_wind_direction_deg(const uint8_t* br) {
return wind_dir_degr[br[9] & 0x0f];
}
static float get_wind_speed_raw(const uint8_t* br) {
float get_wind_speed_raw(const uint8_t* br) {
return br[5]; // Raw
}
static float get_wind_avg_ms(const uint8_t* br) {
float get_wind_avg_ms(const uint8_t* br) {
return (br[5] * 34.0f) / 100; // Meters/sec.
}
static float get_wind_avg_mph(const uint8_t* br) {
float get_wind_avg_mph(const uint8_t* br) {
return ((br[5] * 34.0f) / 100) * 2.23693629f; // Mph
}
static float get_wind_avg_kmh(const uint8_t* br) {
float get_wind_avg_kmh(const uint8_t* br) {
return ((br[5] * 34.0f) / 100) * 3.6f; // Km/h
}
static float get_wind_avg_knot(const uint8_t* br) {
float get_wind_avg_knot(const uint8_t* br) {
return ((br[5] * 34.0f) / 100) * 1.94384f; // Knots
}
static float get_wind_gust_raw(const uint8_t* br) {
float get_wind_gust_raw(const uint8_t* br) {
return br[6]; // Raw
}
static float get_wind_gust_ms(const uint8_t* br) {
float get_wind_gust_ms(const uint8_t* br) {
return (br[6] * 34.0f) / 100; // Meters/sec.
}
static float get_wind_gust_mph(const uint8_t* br) {
float get_wind_gust_mph(const uint8_t* br) {
return ((br[6] * 34.0f) / 100) * 2.23693629f; // Mph
}
static float get_wind_gust_kmh(const uint8_t* br) {
float get_wind_gust_kmh(const uint8_t* br) {
return ((br[6] * 34.0f) / 100) * 3.6f; // Km/h
}
static float get_wind_gust_knot(const uint8_t* br) {
float get_wind_gust_knot(const uint8_t* br) {
return ((br[6] * 34.0f) / 100) * 1.94384f; // Knots
}
static float get_rainfall(const uint8_t* br) {
return ((((unsigned short)br[7] & 0x0f) << 8) | br[8]) * 0.3f;
float get_rainfall(const uint8_t* br) {
unsigned short rain_raw = (((unsigned short)br[7] & 0x0f) << 8) | br[8];
return (float)rain_raw * 0.3f;
}
// ------------ WH3080 UV SENSOR DECODING ----------------------------------------------------
unsigned short get_uv_sensor_id(const uint8_t* br) {
return (br[1] << 4 & 0xf0 ) | (br[2] >> 4);
}
char* get_uvstatus(const uint8_t* br) {
if (br[3] == 85) {
return "OK";
} else {
return "ERROR";
}
}
unsigned short wh3080_uvi(const uint8_t* br) {
return (br[2] & 0x0F );
}
// ------------ WH3080 LIGHT SENSOR DECODING -------------------------------------------------
float get_rawlight(const uint8_t* br) {
return (((((br[4]) << 16) | ((br[5]) << 8) | br[6])));
}
//----------------- TIME DECODING ----------------------------------------------------
static int get_hours(const uint8_t* br) {
char* get_signal(const uint8_t* br) {
if ((br[2] & 0x0F) == 10) {
return "DCF77";
} else {
return "WWVB/MSF";
}
}
int get_hours(const uint8_t* br) {
return ((br[3] >> 4 & 0x03) * 10) + (br[3] & 0x0F);
}
static int get_minutes(const uint8_t* br) {
int get_minutes(const uint8_t* br) {
return (((br[4] & 0xF0) >> 4) * 10) + (br[4] & 0x0F);
}
static int get_seconds(const uint8_t* br) {
int get_seconds(const uint8_t* br) {
return (((br[5] & 0xF0) >> 4) * 10) + (br[5] & 0x0F);
}
static int get_year(const uint8_t* br) {
int get_year(const uint8_t* br) {
return (((br[6] & 0xF0) >> 4) * 10) + (br[6] & 0x0F);
}
static int get_month(const uint8_t* br) {
int get_month(const uint8_t* br) {
return ((br[7] >> 4 & 0x01) * 10) + (br[7] & 0x0F);
}
static int get_day(const uint8_t* br) {
int get_day(const uint8_t* br) {
return (((br[8] & 0xF0) >> 4) * 10) + (br[8] & 0x0F);
}
@ -198,29 +252,45 @@ static int fineoffset_wh1080_callback(bitbuffer_t *bitbuffer) {
data_t *data;
char time_str[LOCAL_TIME_BUFLEN];
const uint8_t *br;
uint8_t bbuf[12];
int msg_type; // 0=Weather 1=Datetime 2=UV/Light
int sens_msg = 12; // 12=Weather/Time sensor 8=UV/Light sensor
int i;
uint8_t bbuf[sens_msg];
local_time_str(0, time_str);
if (bitbuffer->num_rows != 1) {
return 0;
}
if ((bitbuffer->bits_per_row[0] != 88) && (bitbuffer->bits_per_row[0] != 87)) {
if ((bitbuffer->bits_per_row[0] != 88) && (bitbuffer->bits_per_row[0] != 87) &&
(bitbuffer->bits_per_row[0] != 64) && (bitbuffer->bits_per_row[0] != 63)){
return 0;
}
if(bitbuffer->bits_per_row[0] == 88) {
if(bitbuffer->bits_per_row[0] == 88) { // FineOffset WH1080/3080 Weather data msg
sens_msg = 12;
br = bitbuffer->bb[0];
} else {
} else if(bitbuffer->bits_per_row[0] == 87) { // FineOffset WH1080/3080 Weather data msg (different version (newest?))
sens_msg = 12;
/* 7 bits of preamble, bit shift the whole buffer and fix the bytestream */
bitbuffer_extract_bytes(bitbuffer, 0, 7,
(uint8_t *)&bbuf+1, 10*8);
br = bbuf;
bbuf[0] = 0xFF;
} else if(bitbuffer->bits_per_row[0] == 64) { // FineOffset WH3080 UV/Light data msg
sens_msg = 8;
br = bitbuffer->bb[0];
} else if(bitbuffer->bits_per_row[0] == 63) { // FineOffset WH3080 UV/Light data msg (different version (newest?))
sens_msg = 8;
/* 7 bits of preamble, bit shift the whole buffer and fix the bytestream */
bitbuffer_extract_bytes(bitbuffer, 0, 7,
(uint8_t *) & bbuf +1, 7*8);
br = bbuf;
bbuf[0] = 0xFF;
}
if (debug_output) {
for (i=0 ; i<11 ; i++)
for (i=0 ; i<((sens_msg)-1) ; i++)
fprintf(stderr, "%02x ", bbuf[i]);
fprintf(stderr, "\n");
}
@ -230,15 +300,25 @@ static int fineoffset_wh1080_callback(bitbuffer_t *bitbuffer) {
return 0;
}
if (br[10] != crc8(br, 10, CRC_POLY, CRC_INIT)) {
if (sens_msg == 12) {
if (br[10] != crc8(br, 10, CRC_POLY, CRC_INIT)) {
// crc mismatch
return 0;
}
} else {
if (br[7] != crc8(br, 7, CRC_POLY, CRC_INIT)) {
// crc mismatch
return 0;
}
}
if (br[0] == 0xff && (br[1] >> 4) == 0x0a) {
msg_type = 0;
msg_type = 0; // WH1080/3080 Weather msg
} else if (br[0] == 0xff && (br[1] >> 4) == 0x0b) {
msg_type = 1;
msg_type = 1; // WH1080/3080 Datetime msg
} else if (br[0] == 0xff && (br[1] >> 4) == 0x07) {
msg_type = 2; // WH3080 UV/Light msg
}
@ -262,7 +342,6 @@ static int fineoffset_wh1080_callback(bitbuffer_t *bitbuffer) {
const float speed = get_wind_avg_kmh(br); // <--- Data will be shown in Km/h
//const float speed = get_wind_avg_knot((br) // <--- Data will be shown in Knots
// Wind gust speed :
//const float gust = get_wind_gust_ms(br); // <--- Data will be shown in Meters/sec.
@ -273,83 +352,130 @@ static int fineoffset_wh1080_callback(bitbuffer_t *bitbuffer) {
const float rain = get_rainfall(br);
const int device_id = get_device_id(br);
const char* battery = get_battery(br);
//---------------------------------------------------------------------------------------
//-------- GETTING UV DATA --------------------------------------------------------------
//---------------------------------------------------------------------------------------
//-------- GETTING TIME DATA ------------------------------------------------------------
int uv_sensor_id = get_uv_sensor_id(br);
char* uv_status = get_uvstatus(br);
int uv_index = wh3080_uvi(br);
const int the_hours = get_hours(br);
const int the_minutes = get_minutes(br);
const int the_seconds = get_seconds(br);
const int the_year = 2000 + get_year(br);
const int the_month = get_month(br);
const int the_day = get_day(br);
//---------------------------------------------------------------------------------------
//-------- GETTING LIGHT DATA -----------------------------------------------------------
float light = get_rawlight(br);
float lux = (get_rawlight(br)/10);
float wm = (get_rawlight(br)/6830);
float fc = ((get_rawlight(br)/10.76)/10.0);
//---------------------------------------------------------------------------------------
//-------- GETTING TIME DATA ------------------------------------------------------------
char* signal = get_signal(br);
int hours = get_hours(br);
int minutes = get_minutes(br);
int seconds = get_seconds(br);
int year = 2000 + get_year(br);
int month = get_month(br);
int day = get_day(br);
//--------- PRESENTING DATA --------------------------------------------------------------
//--------- PRESENTING DATA --------------------------------------------------------------
if (msg_type == 0) {
data = data_make("time", "", DATA_STRING, time_str,
"model", "", DATA_STRING, "Fine Offset WH1080 weather station",
"msg_type", "Msg type", DATA_INT, msg_type,
"id", "StationID", DATA_FORMAT, "%04X", DATA_INT, device_id,
"temperature_C", "Temperature", DATA_FORMAT, "%.01f C", DATA_DOUBLE, temperature,
"humidity", "Humidity", DATA_FORMAT, "%u %%", DATA_INT, humidity,
"direction_str", "Wind string", DATA_STRING, direction_str,
"direction_deg", "Wind degrees", DATA_STRING, direction_deg,
"speed", "Wind avg speed", DATA_FORMAT, "%.02f", DATA_DOUBLE, speed,
"gust", "Wind gust", DATA_FORMAT, "%.02f", DATA_DOUBLE, gust,
"rain", "Total rainfall", DATA_FORMAT, "%.01f", DATA_DOUBLE, rain,
"battery", "Battery", DATA_STRING, battery, // Unsure about Battery byte...
NULL);
data = data_make(
"time", "", DATA_STRING, time_str,
"model", "", DATA_STRING, "Fine Offset Electronics WH1080/WH3080 Weather Station",
"msg_type", "Msg type", DATA_INT, msg_type,
"id", "Station ID", DATA_FORMAT, "%d", DATA_INT, device_id,
"temperature_C","Temperature", DATA_FORMAT, "%.01f C", DATA_DOUBLE, temperature,
"humidity", "Humidity", DATA_FORMAT, "%u %%", DATA_INT, humidity,
"direction_str","Wind string", DATA_STRING, direction_str,
"direction_deg","Wind degrees", DATA_STRING, direction_deg,
"speed", "Wind avg speed",DATA_FORMAT, "%.02f", DATA_DOUBLE, speed,
"gust", "Wind gust", DATA_FORMAT, "%.02f", DATA_DOUBLE, gust,
"rain", "Total rainfall",DATA_FORMAT, "%3.1f", DATA_DOUBLE, rain,
"battery", "Battery", DATA_STRING, battery,
NULL);
data_acquired_handler(data);
return 1;
} else if (msg_type == 1) {
data = data_make(
"time", "", DATA_STRING, time_str,
"model", "", DATA_STRING, "Fine Offset Electronics WH1080/WH3080 Weather Station",
"msg_type", "Msg type", DATA_INT, msg_type,
"id", "Station ID", DATA_FORMAT, "%d", DATA_INT, device_id,
"signal", "Signal Type", DATA_STRING, signal,
"hours", "Hours\t", DATA_FORMAT, "%02d", DATA_INT, hours,
"minutes", "Minutes", DATA_FORMAT, "%02d", DATA_INT, minutes,
"seconds", "Seconds", DATA_FORMAT, "%02d", DATA_INT, seconds,
"year", "Year\t", DATA_FORMAT, "%02d", DATA_INT, year,
"month", "Month\t", DATA_FORMAT, "%02d", DATA_INT, month,
"day", "Day\t", DATA_FORMAT, "%02d", DATA_INT, day,
NULL);
data_acquired_handler(data);
return 1;
} else {
data = data_make(
"time", "", DATA_STRING, time_str,
"model", "", DATA_STRING, "Fine Offset Electronics WH3080 Weather Station",
"msg_type", "Msg type", DATA_INT, msg_type,
"uv_sensor_id", "UV Sensor ID", DATA_FORMAT, "%d", DATA_INT, uv_sensor_id,
"uv_status", "Sensor Status",DATA_STRING, uv_status,
"uv_index", "UV Index", DATA_INT, uv_index,
"lux", "Lux\t", DATA_FORMAT, "%.1f", DATA_DOUBLE, lux,
"wm", "Watts/m\t", DATA_FORMAT, "%.2f", DATA_DOUBLE, wm,
"fc", "Foot-candles", DATA_FORMAT, "%.2f", DATA_DOUBLE, fc,
NULL);
data_acquired_handler(data);
return 1;
} else {
data = data_make("time", "", DATA_STRING, time_str,
"model", "", DATA_STRING, "Fine Offset WH1080 weather station",
"msg_type", "Msg type", DATA_INT, msg_type,
"id", "StationID", DATA_FORMAT, "%04X", DATA_INT, device_id,
"hours", "Hours", DATA_FORMAT, "%02d", DATA_INT, the_hours,
"minutes", "Minutes", DATA_FORMAT, "%02d", DATA_INT, the_minutes,
"seconds", "Seconds", DATA_FORMAT, "%02d", DATA_INT, the_seconds,
"year", "Year", DATA_FORMAT, "%02d", DATA_INT, the_year,
"month", "Month", DATA_FORMAT, "%02d", DATA_INT, the_month,
"day", "Day", DATA_FORMAT, "%02d", DATA_INT, the_day,
NULL);
data_acquired_handler(data);
return 1;
}
}
}
static char *output_fields[] = {
"time",
"model",
"id",
"temperature_C",
"humidity",
"direction_str",
"direction_deg",
"speed",
"gust",
"rain",
"msg_type",
"hours",
"minutes",
"seconds",
"year",
"month",
"day",
"battery",
NULL
"time",
"model",
"id",
"temperature_C",
"humidity",
"direction_str",
"direction_deg",
"speed",
"gust",
"rain",
"msg_type",
"signal",
"hours",
"minutes",
"seconds",
"year",
"month",
"day",
"battery",
"sensor_code",
"uv_status",
"uv_index",
"lux",
"wm",
"fc",
NULL
};
r_device fineoffset_wh1080 = {
.name = "Fine Offset WH1080 Weather Station",
.name = "Fine Offset Electronics WH1080/WH3080 Weather Station",
.modulation = OOK_PULSE_PWM_RAW,
.short_limit = 976,
.long_limit = 2400,
.reset_limit = 10520,
.short_limit = 800,
.long_limit = 2800,
.reset_limit = 2800,
.json_callback = &fineoffset_wh1080_callback,
.disabled = 0,
.demod_arg = 0,