X-Git-Url: https://git.rvb.name/rtl-433.git/blobdiff_plain/ca13278b24eb61443559bcb61e64627fba3d8823..6d15c6f967221af825cf84e3ed12b96c763b127b:/src/devices/fineoffset_wh1080.c diff --git a/src/devices/fineoffset_wh1080.c b/src/devices/fineoffset_wh1080.c new file mode 100644 index 0000000..ba09033 --- /dev/null +++ b/src/devices/fineoffset_wh1080.c @@ -0,0 +1,499 @@ + +/* + * *** 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). + * + ***************************************** + * 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 trasmitted 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 "data.h" +#include "rtl_433.h" +#include "util.h" +#include "math.h" + +#define CRC_POLY 0x31 +#define CRC_INIT 0xff + + + + +static char* wind_dir_string[] = {"N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW",}; +static 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) { + return (br[1] << 4 & 0xf0 ) | (br[2] >> 4); +} + +static char* get_battery(const uint8_t* br) { + if ((br[9] >> 4) != 1) { + return "OK"; + } else { + return "LOW"; + } +} + +// ------------ WEATHER SENSORS DECODING ---------------------------------------------------- + +static 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) { + return br[4]; +} + +static char* get_wind_direction_str(const uint8_t* br) { + return wind_dir_string[br[9] & 0x0f]; +} + +static 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) { + return br[5]; // Raw +} + +static 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) { + return ((br[5] * 34.0f) / 100) * 2.23693629f; // Mph +} + +static 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) { + return ((br[5] * 34.0f) / 100) * 1.94384f; // Knots +} + +static float get_wind_gust_raw(const uint8_t* br) { + return br[6]; // Raw +} + +static 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) { + return ((br[6] * 34.0f) / 100) * 2.23693629f; // Mph + +} + +static 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) { + return ((br[6] * 34.0f) / 100) * 1.94384f; // Knots +} + +static 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 ---------------------------------------------------- + +static unsigned short get_uv_sensor_id(const uint8_t* br) { + return (br[1] << 4 & 0xf0 ) | (br[2] >> 4); +} + +static char* get_uvstatus(const uint8_t* br) { + if (br[3] == 85) { + return "OK"; + } else { + return "ERROR"; + } +} + +static unsigned short wh3080_uvi(const uint8_t* br) { + return (br[2] & 0x0F ); +} + + +// ------------ WH3080 LIGHT SENSOR DECODING ------------------------------------------------- + +static float get_rawlight(const uint8_t* br) { + return (((((br[4]) << 16) | ((br[5]) << 8) | br[6]))); +} + + +//----------------- TIME DECODING ---------------------------------------------------- + +static char* get_signal(const uint8_t* br) { + if ((br[2] & 0x0F) == 10) { + return "DCF77"; + } else { + return "WWVB/MSF"; + } +} + +static int get_hours(const uint8_t* br) { + return ((br[3] >> 4 & 0x03) * 10) + (br[3] & 0x0F); +} + +static int get_minutes(const uint8_t* br) { + return (((br[4] & 0xF0) >> 4) * 10) + (br[4] & 0x0F); +} + +static int get_seconds(const uint8_t* br) { + return (((br[5] & 0xF0) >> 4) * 10) + (br[5] & 0x0F); +} + +static int get_year(const uint8_t* br) { + return (((br[6] & 0xF0) >> 4) * 10) + (br[6] & 0x0F); +} + +static int get_month(const uint8_t* br) { + return ((br[7] >> 4 & 0x01) * 10) + (br[7] & 0x0F); +} + +static int get_day(const uint8_t* br) { + return (((br[8] & 0xF0) >> 4) * 10) + (br[8] & 0x0F); +} + +//------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------- + + + +static int fineoffset_wh1080_callback(bitbuffer_t *bitbuffer) { + data_t *data; + char time_str[LOCAL_TIME_BUFLEN]; + const uint8_t *br; + 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) && + (bitbuffer->bits_per_row[0] != 64) && (bitbuffer->bits_per_row[0] != 63)){ + return 0; + } + + if(bitbuffer->bits_per_row[0] == 88) { // FineOffset WH1080/3080 Weather data msg + sens_msg = 12; + br = bitbuffer->bb[0]; + } 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<((sens_msg)-1) ; i++) + fprintf(stderr, "%02x ", bbuf[i]); + fprintf(stderr, "\n"); + } + + if (br[0] != 0xff) { + // preamble missing + return 0; + } + + 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; // WH1080/3080 Weather msg + } else if (br[0] == 0xff && (br[1] >> 4) == 0x0b) { + msg_type = 1; // WH1080/3080 Datetime msg + } else if (br[0] == 0xff && (br[1] >> 4) == 0x07) { + msg_type = 2; // WH3080 UV/Light msg + } + + + + +//--------------------------------------------------------------------------------------- +//-------- GETTING WEATHER SENSORS DATA ------------------------------------------------- + + const float temperature = get_temperature(br); + const int humidity = get_humidity(br); + const char* direction_str = get_wind_direction_str(br); + const char* direction_deg = get_wind_direction_deg(br); + + + // Select which metric system for *wind avg speed* and *wind gust* : + + // Wind average speed : + + //const float speed = get_wind_avg_ms((br) // <--- Data will be shown in Meters/sec. + //const float speed = get_wind_avg_mph((br) // <--- Data will be shown in Mph + 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. + //const float gust = get_wind_gust_mph(br); // <--- Data will be shown in Mph + const float gust = get_wind_gust_kmh(br); // <--- Data will be shown in km/h + //const float gust = get_wind_gust_knot(br); // <--- Data will be shown in Knots + + const float rain = get_rainfall(br); + const int device_id = get_device_id(br); + const char* battery = get_battery(br); + + + //--------------------------------------------------------------------------------------- + //-------- GETTING UV DATA -------------------------------------------------------------- + + const int uv_sensor_id = get_uv_sensor_id(br); + const char* uv_status = get_uvstatus(br); + const int uv_index = wh3080_uvi(br); + + + //--------------------------------------------------------------------------------------- + //-------- GETTING LIGHT DATA ----------------------------------------------------------- + + const float light = get_rawlight(br); + const float lux = (get_rawlight(br)/10); + const float wm = (get_rawlight(br)/6830); + const float fc = ((get_rawlight(br)/10.76)/10.0); + + + + //--------------------------------------------------------------------------------------- + //-------- GETTING TIME DATA ------------------------------------------------------------ + + char* signal = get_signal(br); + const int hours = get_hours(br); + const int minutes = get_minutes(br); + const int seconds = get_seconds(br); + const int year = 2000 + get_year(br); + const int month = get_month(br); + const int day = get_day(br); + + + //--------- PRESENTING DATA -------------------------------------------------------------- + +if (msg_type == 0) { + + 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; + } +} + +static char *output_fields[] = { + "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 Electronics WH1080/WH3080 Weather Station", + .modulation = OOK_PULSE_PWM_RAW, + .short_limit = 800, + .long_limit = 2800, + .reset_limit = 2800, + .json_callback = &fineoffset_wh1080_callback, + .disabled = 0, + .demod_arg = 0, + .fields = output_fields, +}; + +/** + * http://www.jaycar.com.au/mini-lcd-display-weather-station/p/XC0400 + */ + +r_device fineoffset_XC0400 = { + .name = "Fine Offset Electronics, XC0400", + .modulation = OOK_PULSE_PWM_RAW, + .short_limit = 800, // Short pulse 544µs, long pulse 1524µs, fixed gap 1036µs + .long_limit = 2800, // Maximum pulse period (long pulse + fixed gap) + .reset_limit = 2800, // We just want 1 package + .json_callback = &fineoffset_wh1080_callback, + .disabled = 0, + .demod_arg = 0, + .fields = output_fields +};