diff --git a/include/rtl_433_devices.h b/include/rtl_433_devices.h index 14ec4b43..5f290ebd 100644 --- a/include/rtl_433_devices.h +++ b/include/rtl_433_devices.h @@ -222,7 +222,7 @@ DECL(tpms_renault_0435r) \ DECL(fineoffset_ws80) \ DECL(emos_e6016) \ - DECL(altronics_7064) \ + DECL(emax) \ DECL(ant_antplus) \ DECL(emos_e6016_rain) \ DECL(hcs200_fsk) \ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8c7a86e8..d455c7b0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -49,7 +49,6 @@ add_library(r_433 STATIC devices/acurite_01185m.c devices/akhan_100F14.c devices/alecto.c - devices/altronics_x7064.c devices/ambient_weather.c devices/ambientweather_tx8300.c devices/ambientweather_wh31e.c @@ -91,6 +90,7 @@ add_library(r_433 STATIC devices/efth800.c devices/elro_db286a.c devices/elv.c + devices/emax.c devices/emontx.c devices/emos_e6016.c devices/emos_e6016_rain.c diff --git a/src/devices/altronics_x7064.c b/src/devices/altronics_x7064.c deleted file mode 100644 index b58c7aed..00000000 --- a/src/devices/altronics_x7064.c +++ /dev/null @@ -1,134 +0,0 @@ -/** @file - Altronics X7064 temperature and humidity sensor. - - Copyright (C) 2022 Christian W. Zuckschwerdt - based on protocol decoding by Thomas White - - 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. -*/ - -#include "decoder.h" - -/** -Altronics X7064 temperature and humidity sensor. - -S.a. issue #2000 - -- Likely a rebranded device, sold by Altronics -- Data length is 32 bytes with a preamble of 10 bytes - -Data Layout: - - // That fits nicely: aaa16e95 a3 8a ae 2d is channel 1, id 6e95, temp 38e (=910, 1 F, -17.2 C), hum 2d (=45). - - AA AC II IB AT TA AT HH AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA SS - -- C: (4 bit) channel -- I: (12 bit) ID -- B: (4 bit) BP01: battery low, pairing button, 0, 1 -- T: (12 bit) temperature in F, offset 900, scale 10 -- H: (8 bit) humidity -- A: (4 bit) fixed values of 0xA -- S: (8 bit) checksum - -Raw data: - - FF FF AA AA AA AA AA CA CA 54 - AA A1 6E 95 A6 BA A5 3B AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA D4 - AA 00 0 - -Format string: - - 12h CH:4h ID:12h FLAGS:4b TEMP:4x4h4h4x4x4h HUM:8d 184h CHKSUM:8h 8x - -Decoded example: - - aaa CH:1 ID:6e9 FLAGS:0101 TEMP:6b5 HUM:059 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa CHKSUM:d4 000 - -*/ - -static int altronics_7064_decode(r_device *decoder, bitbuffer_t *bitbuffer) -{ - // full preamble is ffffaaaaaaaaaacaca54 - uint8_t const preamble_pattern[] = {0xaa, 0xaa, 0xca, 0xca, 0x54}; - - int ret = 0; - for (unsigned row = 0; row < bitbuffer->num_rows; ++row) { - unsigned pos = bitbuffer_search(bitbuffer, row, 0, preamble_pattern, sizeof(preamble_pattern) * 8); - - if (pos >= bitbuffer->bits_per_row[row]) { - decoder_log(decoder, 2, __func__, "Preamble not found"); - ret = DECODE_ABORT_EARLY; - continue; - } - decoder_logf(decoder, 2, __func__, "Found row: %d", row); - - pos += sizeof(preamble_pattern) * 8; - // we expect 32 bytes - if (pos + 32 * 8 > bitbuffer->bits_per_row[row]) { - decoder_log(decoder, 2, __func__, "Length check fail"); - ret = DECODE_ABORT_LENGTH; - continue; - } - uint8_t b[32] = {0}; - bitbuffer_extract_bytes(bitbuffer, row, pos, b, sizeof(b) * 8); - - // verify checksum - if ((add_bytes(b, 31) & 0xff) != b[31]) { - decoder_log(decoder, 2, __func__, "Checksum fail"); - ret = DECODE_FAIL_MIC; - continue; - } - - int channel = (b[1] & 0xf); - int id = (b[2] << 4) | (b[3] >> 4); - int battery_low = (b[3] & 0x08); - int pairing = (b[3] & 0x04); - int temp_raw = ((b[4] & 0x0f) << 8) | (b[5] & 0xf0) | (b[6] & 0x0f); // weird format - float temp_f = (temp_raw - 900) * 0.1f; - int humidity = b[7]; - - /* clang-format off */ - data_t *data = data_make( - "model", "", DATA_STRING, "Altronics-X7064", - "id", "", DATA_FORMAT, "%03x", DATA_INT, id, - "channel", "Channel", DATA_INT, channel, - "battery_ok", "Battery_OK", DATA_INT, !battery_low, - "temperature_F", "Temperature_F", DATA_FORMAT, "%.1f", DATA_DOUBLE, temp_f, - "humidity", "Humidity", DATA_FORMAT, "%u", DATA_INT, humidity, - "pairing", "Pairing?", DATA_COND, pairing, DATA_INT, !!pairing, - "mic", "Integrity", DATA_STRING, "CHECKSUM", - NULL); - /* clang-format on */ - - decoder_output_data(decoder, data); - return 1; - } - return ret; -} - -static char *output_fields[] = { - "model", - "id", - "channel", - "battery_ok", - "temperature_F", - "humidity", - "pairing", - "mic", - NULL, -}; - -r_device altronics_7064 = { - .name = "Altronics X7064 temperature and humidity sensor", - .modulation = FSK_PULSE_PCM, - .short_width = 90, - .long_width = 90, - .gap_limit = 900, - .reset_limit = 9000, - .decode_fn = &altronics_7064_decode, - .fields = output_fields, -}; diff --git a/src/devices/emax.c b/src/devices/emax.c new file mode 100644 index 00000000..b13760d6 --- /dev/null +++ b/src/devices/emax.c @@ -0,0 +1,239 @@ +/** @file + First version was for Altronics X7064 temperature and humidity sensor. + Then updated by Profboc75 with Optex 990040 (Emax full Weather station rain gauge/wind speed/wind direction ... ref EM3390W6 ) + + Copyright (C) 2022 Christian W. Zuckschwerdt + based on protocol decoding by Thomas White + + 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. +*/ + +#include "decoder.h" + +/** +Fuzhou Emax Electronic W6 Professional Weather Station. +Rebrand and devices decoded : +- Emax W6 / WEC-W6 / 3390TX W6 / EM3390W6 +- Altronics x7063/4 +- Optex 990040 / 990050 / 990051 / SM-040 +- Infactory FWS-1200 +- Newentor Q9 +- Otio Weather Station Pro La Surprenante 810025 +- Orium Pro Atlanta 13093, Helios 13123 +- Protmex PT3390A +- Jula Marquant 014331 weather station /014332 temp hum sensor + +S.a. issue #2000 #2299 #2326 PR #2300 + +- Likely a rebranded device, sold by Altronics +- Data length is 32 bytes with a preamble of 10 bytes (33 bytes for Rain/Wind Station) + +Data Layout: + + // That fits nicely: aaa16e95 a3 8a ae 2d is channel 1, id 6e95, temp 38e (=910, 1 F, -17.2 C), hum 2d (=45). + +Temp/Hum Sensor : + AA AC II IB AT TA AT HH AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA SS + +default empty = 0xAA + +- K: (4 bit) Kind of device, = A if Temp/Hum Sensor or = 0 if Weather Rain/Wind station +- C: (4 bit) channel ( = 4 for Weather Rain/wind station) +- I: (12 bit) ID +- B: (4 bit) BP01: battery low, pairing button, 0, 1 +- T: (12 bit) temperature in F, offset 900, scale 10 +- H: (8 bit) humidity % +- A: (4 bit) fixed values of 0xA +- S: (8 bit) checksum + +Raw data: + + FF FF AA AA AA AA AA CA CA 54 + AA A1 6E 95 A6 BA A5 3B AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA D4 + AA 00 0 + +Format string: + + 12h CH:4h ID:12h FLAGS:4b TEMP:4x4h4h4x4x4h HUM:8d 184h CHKSUM:8h 8x + +Decoded example: + + aaa CH:1 ID:6e9 FLAGS:0101 TEMP:6b5 HUM:059 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa CHKSUM:d4 000 + + +Emax EM3390W6 Rain / Wind speed / Wind Direction / Temp / Hum / UV / Lux + +Weather Rain/Wind station : hummidity not at same byte position. + AA 04 II IB 0T TT HH 0W WW 0D DD RR RR 0U LL LL 04 05 06 07 08 09 10 11 12 13 14 15 16 17 xx SS yy + +default empty/null = 0x01 => value = 0 + +- K: (4 bit) Kind of device, = A if Temp/Hum Sensor or = 0 if Weather Rain/Wind station +- C: (4 bit) channel ( = 4 for Weather Rain/wind station) +- I: (12 bit) ID +- B: (4 bit) BP01: battery low, pairing button, 0, 1 +- T: (12 bit) temperature in F, offset 900, scale 10 +- H: (8 bit) humidity % +- R: (16) Rain +- W: (12) Wind speed +- D: (9 bit) Wind Direction +- U: (5 bit) UV index +- L: (1 + 15 bit) Lux value, if first bit = 1 , then x 10 the rest. +- A: (4 bit) fixed values of 0xA +- 0: (4 bit) fixed values of 0x0 +- xx: incremental value each tx +- yy: incremental value each tx yy = xx + 1 +- S: (8 bit) checksum + +Raw Data: + + ff ff 80 00 aa aa aa aa aa ca ca 54 + aa 04 59 41 06 1f 42 01 01 01 81 01 16 01 01 01 04 05 06 07 08 09 10 11 12 13 14 15 16 17 9d ad 9e + 0000 + +Format string: + + 8h K:4h CH:4h ID:12h Flags:4b 4h Temp:12h Hum:8h 4h Wind:12h 4h Direction: 12h Rain: 16h 4h UV:4h Lux:16h 112h xx:8d CHKSUM:8h + +Decoded example: + + aa KD:0 CH:4 ID:594 FLAGS:0001 0 TEMP:61f (66.7F) HUM:42 (66%) Wind: 101 ( = 000 * 0.2 = 0 kmh) 0 Direction: 181 ( = 080 = 128°) Rain: 0116 ( 0015 * 0.2 = 4.2 mm) 0 UV: 1 (0 UV) Lux: 0101 (0 Lux) 04 05 ...16 17 xx:9d CHKSUM:ad yy:9e + +*/ + +static int emax_decode(r_device *decoder, bitbuffer_t *bitbuffer) +{ + // full preamble is ffffaaaaaaaaaacaca54 + uint8_t const preamble_pattern[] = {0xaa, 0xaa, 0xca, 0xca, 0x54}; + + int ret = 0; + for (unsigned row = 0; row < bitbuffer->num_rows; ++row) { + unsigned pos = bitbuffer_search(bitbuffer, row, 0, preamble_pattern, sizeof(preamble_pattern) * 8); + + if (pos >= bitbuffer->bits_per_row[row]) { + decoder_log(decoder, 2, __func__, "Preamble not found"); + ret = DECODE_ABORT_EARLY; + continue; + } + decoder_logf(decoder, 2, __func__, "Found row: %d", row); + + pos += sizeof(preamble_pattern) * 8; + // we expect 32 bytes + if (pos + 32 * 8 > bitbuffer->bits_per_row[row]) { + decoder_log(decoder, 2, __func__, "Length check fail"); + ret = DECODE_ABORT_LENGTH; + continue; + } + uint8_t b[32] = {0}; + bitbuffer_extract_bytes(bitbuffer, row, pos, b, sizeof(b) * 8); + + // verify checksum + if ((add_bytes(b, 31) & 0xff) != b[31]) { + decoder_log(decoder, 2, __func__, "Checksum fail"); + ret = DECODE_FAIL_MIC; + continue; + } + + int channel = (b[1] & 0x0f); + int kind = ((b[1] & 0xf0) >> 4); + int id = (b[2] << 4) | (b[3] >> 4); + int battery_low = (b[3] & 0x08); + int pairing = (b[3] & 0x04); + + // depend if external temp/hum sensor or Weather rain/wind station the values are not decode the same + + if (kind != 0) { // if not Rain/Wind ... sensor + + int temp_raw = ((b[4] & 0x0f) << 8) | (b[5] & 0xf0) | (b[6] & 0x0f); // weird format + float temp_f = (temp_raw - 900) * 0.1f; + int humidity = b[7]; + + /* clang-format off */ + data_t *data = data_make( + "model", "", DATA_STRING, "Altronics-X7064", + "id", "", DATA_FORMAT, "%03x", DATA_INT, id, + "channel", "Channel", DATA_INT, channel, + "battery_ok", "Battery_OK", DATA_INT, !battery_low, + "temperature_F", "Temperature_F", DATA_FORMAT, "%.1f", DATA_DOUBLE, temp_f, + "humidity", "Humidity", DATA_FORMAT, "%u", DATA_INT, humidity, + "pairing", "Pairing?", DATA_COND, pairing, DATA_INT, !!pairing, + "mic", "Integrity", DATA_STRING, "CHECKSUM", + NULL); + /* clang-format on */ + + decoder_output_data(decoder, data); + return 1; + } + else { // if Rain/Wind sensor + + int temp_raw = ((b[4] & 0x0f) << 8) | (b[5]); // weird format + float temp_f = (temp_raw - 900) * 0.1f; + int humidity = b[6]; + int wind_raw = ((b[7] - 1) << 8 ) | (b[8] -1); // all default is 01 so need to remove 1 from all bytes. + float speed_kmh = wind_raw * 0.2f; + int direction_deg = (((b[9] & 0x0f) - 1) << 8) | (b[10] - 1); + int rain_raw = ((b[11] - 1) << 8 ) | (b[12] -1); + float rain_mm = rain_raw * 0.2f; + int uv_index = (b[13] & 0x1f) - 1; + int lux_multi = (((b[14] - 1) & 0x80) >> 7); + int light_lux = ((b[14] & 0x7f) - 1) << 8 | (b[15] - 1); + if (lux_multi == 1) { + light_lux = light_lux * 10; + } + + /* clang-format off */ + data_t *data = data_make( + "model", "", DATA_STRING, "Emax-W6", + "id", "", DATA_FORMAT, "%03x", DATA_INT, id, + "channel", "Channel", DATA_INT, channel, + "battery_ok", "Battery_OK", DATA_INT, !battery_low, + "temperature_F", "Temperature_F", DATA_FORMAT, "%.1f", DATA_DOUBLE, temp_f, + "humidity", "Humidity", DATA_FORMAT, "%u", DATA_INT, humidity, + "wind_avg_km_h", "Wind avg speed", DATA_FORMAT, "%.1f km/h", DATA_DOUBLE, speed_kmh, + "wind_dir_deg", "Wind Direction", DATA_INT, direction_deg, + "rain_mm", "Total rainfall", DATA_FORMAT, "%.1f mm", DATA_DOUBLE, rain_mm, + "uv", "UV Index", DATA_FORMAT, "%u", DATA_INT, uv_index, + "light_lux", "Lux", DATA_FORMAT, "%u", DATA_INT, light_lux, + "pairing", "Pairing?", DATA_COND, pairing, DATA_INT, !!pairing, + "mic", "Integrity", DATA_STRING, "CHECKSUM", + NULL); + /* clang-format on */ + + decoder_output_data(decoder, data); + return 1; + + } + } + return ret; +} + +static char *output_fields[] = { + "model", + "id", + "channel", + "battery_ok", + "temperature_F", + "humidity", + "wind_avg_km_h", + "rain_mm", + "wind_dir_deg", + "uv", + "light_lux", + "pairing", + "mic", + NULL, +}; + +r_device emax = { + .name = "Emax W6, rebrand Altronics x7063/4, Optex 990040/50/51, Orium 13093/13123, Infactory FWS-1200, Newentor Q9, Otio 810025, Protmex PT3390A, Jula Marquant 014331/32, Weather Station or temperature/humidity sensor", + .modulation = FSK_PULSE_PCM, + .short_width = 90, + .long_width = 90, + .gap_limit = 1200, + .reset_limit = 9000, + .decode_fn = &emax_decode, + .fields = output_fields, +};