Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
rtl_433/src/devices/acurite.c
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
2063 lines (1712 sloc)
73.1 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** @file | |
| Acurite weather stations and temperature / humidity sensors. | |
| Copyright (c) 2015, Jens Jenson, Helge Weissig, David Ray Thompson, Robert Terzi | |
| 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. | |
| Acurite weather stations and temperature / humidity sensors. | |
| Devices decoded: | |
| - Acurite Iris (5-n-1) weather station, Model; VN1TXC, 06004RM | |
| - Acurite 5-n-1 pro weather sensor, Model: 06014RM | |
| - Acurite Atlas (7-n-1) weather station | |
| - Acurite Notos (3-n-1) weather station | |
| - Acurite 896 Rain gauge, Model: 00896 | |
| - Acurite 592TXR / 06002RM / 6044m Tower sensor (temperature and humidity) | |
| (Note: Some newer sensors share the 592TXR coding for compatibility. | |
| - Acurite 609TXC "TH" temperature and humidity sensor (609A1TX) | |
| - Acurite 986 Refrigerator / Freezer Thermometer | |
| - Acurite 515 Refrigerator / Freezer Thermometer | |
| - Acurite 606TX temperature sensor | |
| - Acurite 6045M Lightning Detector | |
| - Acurite 00275rm and 00276rm temp. and humidity with optional probe. | |
| - Acurite 1190/1192 leak/water detector | |
| */ | |
| #include "decoder.h" | |
| #define ACURITE_515_BITLEN 50 | |
| #define ACURITE_TXR_BITLEN 56 | |
| #define ACURITE_5N1_BITLEN 64 | |
| #define ACURITE_6045_BITLEN 72 | |
| #define ACURITE_ATLAS_BITLEN 80 | |
| #define ACURITE_515_BYTELEN 6 | |
| #define ACURITE_TXR_BYTELEN 7 | |
| #define ACURITE_1190_BYTELEN 7 | |
| #define ACURITE_3N1_BYTELEN 8 | |
| #define ACURITE_5N1_BYTELEN 8 | |
| #define ACURITE_899_BYTELEN 8 | |
| #define ACURITE_ATLAS_BYTELEN 8 | |
| #define ACURITE_6045_BYTELEN 9 | |
| #define ACURITE_ATLAS_LTNG_BYTELEN 10 | |
| // ** Acurite known message types | |
| #define ACURITE_MSGTYPE_1190_DETECTOR 0x01 | |
| #define ACURITE_MSGTYPE_TOWER_SENSOR 0x04 | |
| #define ACURITE_MSGTYPE_ATLAS_WNDSPD_TEMP_HUM 0x05 | |
| #define ACURITE_MSGTYPE_ATLAS_WNDSPD_RAIN 0x06 | |
| #define ACURITE_MSGTYPE_ATLAS_WNDSPD_UV_LUX 0x07 | |
| #define ACURITE_MSGTYPE_515_REFRIGERATOR 0x08 | |
| #define ACURITE_MSGTYPE_515_FREEZER 0x09 | |
| #define ACURITE_MSGTYPE_3N1_WINDSPEED_TEMP_HUMIDITY 0x20 | |
| #define ACURITE_MSGTYPE_ATLAS_WNDSPD_TEMP_HUM_LTNG 0x25 | |
| #define ACURITE_MSGTYPE_ATLAS_WNDSPD_RAIN_LTNG 0x26 | |
| #define ACURITE_MSGTYPE_ATLAS_WNDSPD_UV_LUX_LTNG 0x27 | |
| #define ACURITE_MSGTYPE_6045M 0x2f | |
| #define ACURITE_MSGTYPE_899_RAINFALL 0x30 | |
| #define ACURITE_MSGTYPE_5N1_WINDSPEED_WINDDIR_RAINFALL 0x31 | |
| #define ACURITE_MSGTYPE_5N1_WINDSPEED_TEMP_HUMIDITY 0x38 | |
| // Acurite 5n1 Wind direction values. | |
| // There are seem to be conflicting decodings. | |
| // It is possible there there are different versions | |
| // of the 5n1 station that report differently. | |
| // | |
| // The original implementation used by the 5n1 device type | |
| // here seems to have a straight linear/circular mapping. | |
| // | |
| // The newer 5n1 mapping seems to just jump around with no clear | |
| // meaning, but does map to the values sent by Acurite's | |
| // only Acu-Link Internet Bridge and physical console 1512. | |
| // This is may be a modified/non-standard Gray Code. | |
| // Mapping 5n1 raw RF wind direction values to aculink's values | |
| // RF, AcuLink | |
| // 0, 6, NW, 315.0 | |
| // 1, 8, WSW, 247.5 | |
| // 2, 2, WNW, 292.5 | |
| // 3, 0, W, 270.0 | |
| // 4, 4, NNW, 337.5 | |
| // 5, A, SW, 225.0 | |
| // 6, 5, N, 0.0 | |
| // 7, E, SSW, 202.5 | |
| // 8, 1, ENE, 67.5 | |
| // 9, F, SE, 135.0 | |
| // A, 9, E, 90.0 | |
| // B, B, ESE, 112.5 | |
| // C, 3, NE, 45.0 | |
| // D, D, SSE, 157.0 | |
| // E, 7, NNE, 22.5 | |
| // F, C, S, 180.0 | |
| // From draythomp/Desert-home-rtl_433 | |
| // matches acu-link internet bridge values | |
| // The mapping isn't circular, it jumps around. | |
| // units are 22.5 deg | |
| int const acurite_5n1_winddirections[] = { | |
| 14, // 0 - NW | |
| 11, // 1 - WSW | |
| 13, // 2 - WNW | |
| 12, // 3 - W | |
| 15, // 4 - NNW | |
| 10, // 5 - SW | |
| 0, // 6 - N | |
| 9, // 7 - SSW | |
| 3, // 8 - ENE | |
| 6, // 9 - SE | |
| 4, // a - E | |
| 5, // b - ESE | |
| 2, // c - NE | |
| 7, // d - SSE | |
| 1, // e - NNE | |
| 8, // f - S | |
| }; | |
| // The high 2 bits of byte zero are the channel (bits 7,6) | |
| // 00 = C | |
| // 10 = B | |
| // 11 = A | |
| static char const *acurite_getChannel(uint8_t byte) | |
| { | |
| static char const *channel_strs[] = {"C", "E", "B", "A"}; // 'E' stands for error | |
| int channel = (byte & 0xC0) >> 6; | |
| return channel_strs[channel]; | |
| } | |
| // Add exception and raw message bytes to message to enable | |
| // later analysis of unexpected/possbily undecoded data | |
| static void data_append_exception(data_t* data, int exception, uint8_t* bb, int browlen) | |
| { | |
| char raw_str[31], *rawp; | |
| rawp = (char *)raw_str; | |
| for (int i=0; i < browlen; i++) { | |
| sprintf(rawp,"%02x",bb[i]); | |
| rawp += 2; | |
| } | |
| *rawp = '\0'; | |
| /* clang-format off */ | |
| data = data_append(data, | |
| "exception", "data_exception", DATA_INT, exception, | |
| "raw_msg", "raw_message", DATA_STRING, raw_str, | |
| NULL); | |
| /* clang-format on */ | |
| } | |
| /** | |
| Acurite 896 rain gauge | |
| */ | |
| static int acurite_rain_896_decode(r_device *decoder, bitbuffer_t *bitbuffer) | |
| { | |
| uint8_t *b = bitbuffer->bb[0]; | |
| int id; | |
| float total_rain; | |
| data_t *data; | |
| // This needs more validation to positively identify correct sensor type, but it basically works if message is really from acurite raingauge and it doesn't have any errors | |
| if (bitbuffer->bits_per_row[0] < 24) | |
| return DECODE_ABORT_LENGTH; | |
| if ((b[0] == 0) || (b[1] == 0) || (b[2] == 0) || (b[3] != 0) || (b[4] != 0)) | |
| return DECODE_ABORT_EARLY; | |
| id = b[0]; | |
| total_rain = ((b[1] & 0xf) << 8) | b[2]; | |
| total_rain *= 0.5; // Sensor reports number of bucket tips. Each bucket tip is .5mm | |
| decoder_logf(decoder, 2, __func__, "Total Rain is %2.1fmm", total_rain); | |
| decoder_log_bitrow(decoder, 2, __func__, b, bitbuffer->bits_per_row[0], "Raw Message "); | |
| /* clang-format off */ | |
| data = data_make( | |
| "model", "", DATA_STRING, "Acurite-Rain", | |
| "id", "", DATA_INT, id, | |
| "rain_mm", "Total Rain", DATA_FORMAT, "%.1f mm", DATA_DOUBLE, total_rain, | |
| NULL); | |
| /* clang-format on */ | |
| decoder_output_data(decoder, data); | |
| return 1; | |
| } | |
| /** | |
| Acurite 609 Temperature and Humidity Sensor. | |
| 5 byte messages: | |
| II ST TT HH CC | |
| II - ID byte, changes at each power up | |
| S - Status bitmask, normally 0x2, | |
| 0xa - battery low (bit 0x80) | |
| TTT - Temp in Celsius * 10, 12 bit with complement. | |
| HH - Humidity | |
| CC - Checksum | |
| @todo - see if the 3rd nybble is battery/status | |
| */ | |
| static int acurite_th_decode(r_device *decoder, bitbuffer_t *bitbuffer) | |
| { | |
| uint8_t *bb = NULL; | |
| int cksum, battery_low, valid = 0; | |
| float tempc; | |
| uint8_t humidity, id, status; | |
| data_t *data; | |
| int result = 0; | |
| for (uint16_t brow = 0; brow < bitbuffer->num_rows; ++brow) { | |
| if (bitbuffer->bits_per_row[brow] != 40) { | |
| result = DECODE_ABORT_LENGTH; | |
| continue; // DECODE_ABORT_LENGTH | |
| } | |
| bb = bitbuffer->bb[brow]; | |
| cksum = (bb[0] + bb[1] + bb[2] + bb[3]); | |
| if (cksum == 0 || ((cksum & 0xff) != bb[4])) { | |
| result = DECODE_FAIL_MIC; | |
| continue; // DECODE_FAIL_MIC | |
| } | |
| // Temperature in Celsius is encoded as a 12 bit integer value | |
| // multiplied by 10 using the 4th - 6th nybbles (bytes 1 & 2) | |
| // negative values are recovered by sign extend from int16_t. | |
| int temp_raw = (int16_t)(((bb[1] & 0x0f) << 12) | (bb[2] << 4)); | |
| tempc = (temp_raw >> 4) * 0.1f; | |
| id = bb[0]; | |
| status = (bb[1] & 0xf0) >> 4; | |
| battery_low = status & 0x8; | |
| humidity = bb[3]; | |
| if (humidity > 100) { | |
| decoder_logf(decoder, 1, __func__, "609txc 0x%04X: invalid humidity: %d %%rH", | |
| id, humidity); | |
| return DECODE_FAIL_SANITY; | |
| } | |
| /* clang-format off */ | |
| data = data_make( | |
| "model", "", DATA_STRING, "Acurite-609TXC", | |
| "id", "", DATA_INT, id, | |
| "battery_ok", "Battery", DATA_INT, !battery_low, | |
| "temperature_C", "Temperature", DATA_FORMAT, "%.1f C", DATA_DOUBLE, tempc, | |
| "humidity", "Humidity", DATA_FORMAT, "%u %%", DATA_INT, humidity, | |
| "status", "", DATA_INT, status, | |
| "mic", "Integrity", DATA_STRING, "CHECKSUM", | |
| NULL); | |
| /* clang-format on */ | |
| decoder_output_data(decoder, data); | |
| valid++; | |
| } | |
| if (valid) | |
| return 1; | |
| // Only returns the latest result, but better than nothing. | |
| return result; | |
| } | |
| /** | |
| Acurite 06045m Lightning Sensor decoding. | |
| Specs: | |
| - lightning strike count | |
| - estimated distance to front of storm, 1 to 25 miles / 1.6 to 40 km | |
| - Temperature -40 to 158 F / -40 to 70 C | |
| - Humidity 1 - 99% RH | |
| Status Information sent per 06047M/01021 display | |
| - (RF) interference (preventing lightning detection) | |
| - low battery | |
| Message format: | |
| Somewhat similar to 592TXR and 5-n-1 weather stations. | |
| Same pulse characteristics. checksum, and parity checking on data bytes. | |
| Byte 0 Byte 1 Byte 2 Byte 3 Byte 4 Byte 5 Byte 6 Byte 7 Byte 8 | |
| CCIIIIII IIIIIIII pB101111 pHHHHHHH pA?TTTTT pTTTTTTT pLLLLLLL pLRDDDDD KKKKKKKK | |
| - C = Channel (2 bits) | |
| - I = Sensor ID (14 bit Static ID) | |
| - p = parity check bit | |
| - B = Battery OK (cleared for low) | |
| - H = Humidity (7 bits) | |
| - A = Active mode lightning detection (cleared for standby mode) | |
| - T = Temperature (12 bits) | |
| - L = Lightning strike count (8 bits) | |
| - D = Lightning distance (5 bits) | |
| - K = Checksum (8 bits) | |
| Byte 0 - channel/ID | |
| - bitmask CCII IIII | |
| - 0xC0: channel (A: 0xC, B: 0x8, C: 00) | |
| - 0x3F: most significant 6 bits of Sensor ID | |
| (14 bits, same as Acurite Tower sensor family) | |
| Byte 1 - ID all 8 bits, no parity. | |
| - 0xFF = least significant 8 bits of Sensor ID | |
| Byte 2 - Battery and Message type | |
| - Bitmask PBMMMMMM | |
| - 0x80 = Parity | |
| - 0x40 = 1 is battery OK, 0 is battery low | |
| - 0x3f = Message type 0x2f for 06045M lightning detector | |
| Byte 3 - Humidity | |
| - 0x80 - even parity | |
| - 0x7f - humidity | |
| Byte 4 - Status (2 bits) and Temperature MSB (5 bits) | |
| - Bitmask PA?TTTTT (P = Parity, A = Active, T = Temperature) | |
| - 0x80 - even parity | |
| - 0x40 - 1 is Active lightning detection Mode, 0 is standby | |
| - 0x20 - TBD: always off? | |
| - 0x1F - Temperature most significant 5 bits | |
| Byte 5 - Temperature LSB (7 bits, 8th is parity) | |
| - 0x80 - even parity | |
| - 0x7F - Temperature least significant 7 bits | |
| Byte 6 - Lightning Strike count (7 of 8 bit, 8th is parity) | |
| - 0x80 - even parity | |
| - 0x7F - strike count (upper 7 bits) wraps at 255 -> 0 | |
| Byte 7 - Edge of Storm Distance Approximation & other bits | |
| - Bits PLRDDDDD (P = Parity, S = Status, D = Distance | |
| - 0x80 - even parity | |
| - 0x40 - LSB of 8 bit strike counter | |
| - 0x20 - RFI (radio frequency interference) | |
| - 0x1F - distance to edge of storm | |
| value 0x1f is possible invalid value indication (value at power up) | |
| @todo determine mapping function/table. | |
| Byte 8 - checksum. 8 bits, no parity. | |
| Data fields in rtl_433 messages: | |
| - active (vs standby) lightning detection mode | |
| When active: | |
| the AS39335 is in active scanning mode | |
| 6045M will transmit every 8 seconds instead of every 24. | |
| - RFI - radio frequency interference detected | |
| The AS3935 uses broad RFI for detection | |
| Somewhat correlates with the yellow LED on the sensor, but stays set longer | |
| Short periods of RFI appears to be somewhat normal | |
| long periods of RFI on indicates interference, relocate sensor until | |
| yellow LED is no longer on solid | |
| - strike_count - count of detection events, 8 bits | |
| counts up to 255, wraps around to 0 | |
| non-volatile (doesn't reset at power up) | |
| - storm_distance - statistically estimated distance to edge of storm | |
| See AS3935 documentation | |
| sensor will make calculate a distance estimate with each strike event | |
| 0x1f (31) is invalid/undefined value, used at power-up to indicate invalid | |
| Only 5 bits available, needs to cover range of 25 miles/40 KM per spec. | |
| Units unknown, data needed from people with Acurite consoles | |
| - exception - additional analysis of message maybe needed | |
| Suggest reporting raw_msg for further examination. | |
| bits that were invariant (for me) have changed. | |
| Notes: | |
| 2020-08-29 - changed temperature decoding, was 2.0 F too low vs. Acurite Access | |
| @todo - storm_distance conversion to miles/KM (should match Acurite consoles) | |
| */ | |
| static int acurite_6045_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row) | |
| { | |
| float tempf; | |
| uint8_t humidity; | |
| char raw_str[31], *rawp; | |
| uint16_t sensor_id; | |
| uint8_t strike_count, strike_distance; | |
| int battery_low, active, rfi_detect; | |
| int exception = 0; | |
| data_t *data; | |
| int browlen = (bitbuffer->bits_per_row[row] + 7) / 8; | |
| uint8_t *bb = bitbuffer->bb[row]; | |
| char const *channel_str = acurite_getChannel(bb[0]); // same as TXR | |
| // Tower sensor ID is the last 14 bits of byte 0 and 1 | |
| // CCII IIII | IIII IIII | |
| sensor_id = ((bb[0] & 0x3f) << 8) | bb[1]; // same as TXR | |
| battery_low = (bb[2] & 0x40) == 0; | |
| humidity = (bb[3] & 0x7f); // 1-99 %rH, same as TXR | |
| if (humidity > 100) { | |
| decoder_logf(decoder, 1, __func__, "6045m 0x%04X Ch %s : invalid humidity: %d %%rH", | |
| sensor_id, channel_str, humidity); | |
| return DECODE_FAIL_SANITY; | |
| } | |
| active = (bb[4] & 0x40) == 0x40; // Sensor is actively listening for strikes | |
| //message_type = bb[2] & 0x3f; | |
| // 12 bits of temperature after removing parity and status bits. | |
| // Message native format appears to be in 1/10 of a degree Fahrenheit | |
| // Device Specification: -40 to 158 F / -40 to 70 C | |
| // Available range given 12 bits with +1480 offset: -148.0 F to +261.5 F | |
| int temp_raw = ((bb[4] & 0x1F) << 7) | (bb[5] & 0x7F); | |
| tempf = (temp_raw - 1480) * 0.1f; | |
| if (tempf < -40.0 || tempf > 158.0) { | |
| decoder_logf(decoder, 1, __func__, "6045m 0x%04X Ch %s, invalid temperature: %0.1f F", | |
| sensor_id, channel_str, tempf); | |
| return DECODE_FAIL_SANITY; | |
| } | |
| // flag if bits 13/14 of temperature are ever non-zero so | |
| // they can be investigated | |
| if (temp_raw & 0x3000) | |
| exception++; | |
| // Strike count is 8 bits, LSB in following byte | |
| strike_count = ((bb[6] & 0x7f) << 1) | ((bb[7] & 0x40) >> 6); | |
| strike_distance = bb[7] & 0x1f; | |
| rfi_detect = (bb[7] & 0x20) == 0x20; | |
| /* | |
| * 2018-04-21 rct - There are still a number of unknown bits in the | |
| * message that need to be figured out. Add the raw message hex to | |
| * to the structured data output to allow future analysis without | |
| * having to enable debug for long running rtl_433 processes. | |
| */ | |
| rawp = (char *)raw_str; | |
| for (int i=0; i < MIN(browlen, 15); i++) { | |
| sprintf(rawp,"%02x",bb[i]); | |
| rawp += 2; | |
| } | |
| *rawp = '\0'; | |
| // Flag whether this message might need further analysis | |
| if ((bb[4] & 0x20) != 0) // unknown status bits, always off | |
| exception++; | |
| /* clang-format off */ | |
| data = data_make( | |
| "model", "", DATA_STRING, "Acurite-6045M", | |
| "id", NULL, DATA_INT, sensor_id, | |
| "channel", NULL, DATA_STRING, channel_str, | |
| "battery_ok", "Battery", DATA_INT, !battery_low, | |
| "temperature_F", "temperature", DATA_FORMAT, "%.1f F", DATA_DOUBLE, tempf, | |
| "humidity", "humidity", DATA_FORMAT, "%u %%", DATA_INT, humidity, | |
| "strike_count", "strike_count", DATA_INT, strike_count, | |
| "storm_dist", "storm_distance", DATA_INT, strike_distance, | |
| "active", "active_mode", DATA_INT, active, | |
| "rfi", "rfi_detect", DATA_INT, rfi_detect, | |
| "exception", "data_exception", DATA_INT, exception, | |
| "raw_msg", "raw_message", DATA_STRING, raw_str, | |
| NULL); | |
| /* clang-format on */ | |
| decoder_output_data(decoder, data); | |
| return 1; // If we got here 1 valid message was output | |
| } | |
| /** | |
| Acurite 899 Rain Gauge decoder | |
| */ | |
| static int acurite_899_decode(r_device *decoder, bitbuffer_t *bitbuffer, uint8_t *bb) | |
| { | |
| (void)bitbuffer; | |
| // MIC (checkum, parity) validated in calling function | |
| uint16_t sensor_id = ((bb[0] & 0x3f) << 8) | bb[1]; // | |
| int battery_low = (bb[2] & 0x40) == 0; | |
| /* | |
| @todo bug? channel output isn't consistent with the rest of he Acurite | |
| devices in this family, should output ('A', 'B', or 'C') | |
| Currently outputing 00 = A, 01 = B, 10 = C | |
| Leaving as is to maintain compatibility for now | |
| */ | |
| int channel = bb[0] >> 6; | |
| // @todo replace the above with this: | |
| // char const* channel_str = acurite_getChannel(bb[0]); | |
| /* | |
| Rain counter - one tip is 0.01 inch, i.e. 0.254mm | |
| Note: Device native unit arguably Imperial | |
| but this is being converted to metric here, so -C native won't work | |
| Leaving as is to maintain compatibility | |
| */ | |
| int raincounter = ((bb[5] & 0x7f) << 7) | (bb[6] & 0x7f); | |
| /* clang-format off */ | |
| data_t *data; | |
| data = data_make( | |
| "model", "", DATA_STRING, "Acurite-Rain899", | |
| "id", "", DATA_INT, sensor_id, | |
| "channel", "", DATA_INT, channel, | |
| // "channel", NULL, DATA_STRING, channel_str, | |
| "battery_ok", "Battery", DATA_INT, !battery_low, | |
| "rain_mm", "Rainfall Accumulation", DATA_FORMAT, "%.2f mm", DATA_DOUBLE, raincounter * 0.254, | |
| "mic", "Integrity", DATA_STRING, "CHECKSUM", | |
| NULL); | |
| /* clang-format on */ | |
| decoder_output_data(decoder, data); | |
| return 1; // if we got here, 1 message was output | |
| } | |
| /** | |
| Acurite 3n1 Weather Station decoder | |
| */ | |
| static int acurite_3n1_decode(r_device *decoder, bitbuffer_t *bitbuffer, uint8_t *bb) | |
| { | |
| // MIC (checkum, parity) validated in calling function | |
| (void)bitbuffer; | |
| char const* channel_str = acurite_getChannel(bb[0]); | |
| // 3n1 sensor ID is 14 bits | |
| uint16_t sensor_id = ((bb[0] & 0x3f) << 8) | bb[1]; | |
| uint8_t message_type = bb[2] & 0x3f; | |
| if (*channel_str == 'E') { | |
| decoder_logf(decoder, 1, __func__, | |
| "bad channel Ch %s, msg type 0x%02x", | |
| channel_str, message_type); | |
| return DECODE_FAIL_SANITY; | |
| } | |
| /* | |
| @todo bug, 3n1 data format includes sequence_num | |
| which was copied from 5n1, but existing code 3n1 uses | |
| 14 bits for ID. so these bits are used twice. | |
| Leaving for compatibility, but probaby sequence_num | |
| doesn't exist and should be deleted. If the 3n1 did use | |
| a sequence number, the ID would change on each output. | |
| */ | |
| uint8_t sequence_num = (bb[0] & 0x30) >> 4; | |
| int battery_low = (bb[2] & 0x40) == 0; | |
| uint8_t humidity = (bb[3] & 0x7f); // 1-99 %rH | |
| if (humidity > 100) { | |
| decoder_logf(decoder, 1, __func__, "3n1 0x%04X Ch %s : invalid humidity: %d %%rH", | |
| sensor_id, channel_str, humidity); | |
| return DECODE_FAIL_SANITY; | |
| } | |
| // note the 3n1 seems to have one more high bit than 5n1 | |
| // Spec: -40 to 158 F | |
| int temp_raw = (bb[4] & 0x1F) << 7 | (bb[5] & 0x7F); | |
| float tempf = (temp_raw - 1480) * 0.1f; // regression yields (rawtemp-1480)*0.1 | |
| if (tempf < -40.0 || tempf > 158.0) { | |
| decoder_logf(decoder, 1, __func__, "3n1 0x%04X Ch %s, invalid temperature: %0.1f F", | |
| sensor_id, channel_str, tempf); | |
| return DECODE_FAIL_SANITY; | |
| } | |
| /* | |
| @todo bug from original decoder | |
| This can't be a float, must be uint8 | |
| leaving for compatibility | |
| */ | |
| float wind_speed_mph = bb[6] & 0x7f; // seems to be plain MPH | |
| /* clang-format off */ | |
| data_t *data; | |
| data = data_make( | |
| "model", "", DATA_STRING, "Acurite-3n1", | |
| "message_type", NULL, DATA_INT, message_type, | |
| "id", NULL, DATA_FORMAT, "0x%02X", DATA_INT, sensor_id, | |
| "channel", NULL, DATA_STRING, channel_str, | |
| "sequence_num", NULL, DATA_INT, sequence_num, | |
| "battery_ok", "Battery", DATA_INT, !battery_low, | |
| "wind_avg_mi_h", "wind_speed", DATA_FORMAT, "%.1f mi/h", DATA_DOUBLE, wind_speed_mph, | |
| "temperature_F", "temperature", DATA_FORMAT, "%.1f F", DATA_DOUBLE, tempf, | |
| "humidity", NULL, DATA_FORMAT, "%u %%", DATA_INT, humidity, | |
| "mic", "Integrity", DATA_STRING, "CHECKSUM", | |
| NULL); | |
| /* clang-format on */ | |
| decoder_output_data(decoder, data); | |
| return 1; // If we got here 1 valid message was output | |
| } | |
| /** | |
| Acurite 5n1 Weather Station decoder | |
| XXX todo docs | |
| */ | |
| static int acurite_5n1_decode(r_device *decoder, bitbuffer_t *bitbuffer, uint8_t* bb) | |
| { | |
| // MIC (checkum, parity) validated in calling function | |
| (void)bitbuffer; | |
| char const* channel_str = acurite_getChannel(bb[0]); | |
| uint16_t sensor_id = ((bb[0] & 0x0f) << 8) | bb[1]; | |
| uint8_t sequence_num = (bb[0] & 0x30) >> 4; | |
| int battery_low = (bb[2] & 0x40) == 0; | |
| uint8_t message_type = bb[2] & 0x3f; | |
| // Wind raw number is cup rotations per 4 seconds | |
| // 8 bits gives range of 0 - 212 KPH | |
| // http://www.wxforum.net/index.php?topic=27244.0 (found from weewx driver) | |
| int wind_speed_raw = ((bb[3] & 0x1F) << 3)| ((bb[4] & 0x70) >> 4); | |
| float wind_speed_kph = 0; | |
| if (wind_speed_raw > 0) { | |
| wind_speed_kph = wind_speed_raw * 0.8278 + 1.0; | |
| } | |
| if (message_type == ACURITE_MSGTYPE_5N1_WINDSPEED_WINDDIR_RAINFALL) { | |
| // Wind speed, wind direction, and rain fall | |
| float wind_dir = acurite_5n1_winddirections[bb[4] & 0x0f] * 22.5f; | |
| // range: 0 to 99.99 in, 0.01 inch increments, accumulated | |
| int raincounter = ((bb[5] & 0x7f) << 7) | (bb[6] & 0x7F); | |
| /* clang-format off */ | |
| data_t *data; | |
| data = data_make( | |
| "model", "", DATA_STRING, "Acurite-5n1", | |
| "message_type", NULL, DATA_INT, message_type, | |
| "id", NULL, DATA_INT, sensor_id, | |
| "channel", NULL, DATA_STRING, channel_str, | |
| "sequence_num", NULL, DATA_INT, sequence_num, | |
| "battery_ok", "Battery", DATA_INT, !battery_low, | |
| "wind_avg_km_h", "wind_speed", DATA_FORMAT, "%.1f km/h", DATA_DOUBLE, wind_speed_kph, | |
| "wind_dir_deg", NULL, DATA_FORMAT, "%.1f", DATA_DOUBLE, wind_dir, | |
| "rain_in", "Rainfall Accumulation", DATA_FORMAT, "%.2f in", DATA_DOUBLE, raincounter * 0.01f, | |
| "mic", "Integrity", DATA_STRING, "CHECKSUM", | |
| NULL); | |
| /* clang-format on */ | |
| decoder_output_data(decoder, data); | |
| } | |
| else if (message_type == ACURITE_MSGTYPE_5N1_WINDSPEED_TEMP_HUMIDITY) { | |
| // Wind speed, temperature and humidity | |
| // range -40 to 158 F | |
| int temp_raw = (bb[4] & 0x0F) << 7 | (bb[5] & 0x7F); | |
| float tempf = (temp_raw - 400) * 0.1f; | |
| if (tempf > 158.0) { | |
| decoder_logf(decoder, 1, __func__, "5n1 0x%04X Ch %s, invalid temperature: %0.1f F", | |
| sensor_id, channel_str, tempf); | |
| return DECODE_FAIL_SANITY; | |
| } | |
| uint8_t humidity = (bb[6] & 0x7f); // 1-99 %rH | |
| if (humidity > 100) { | |
| decoder_logf(decoder, 1, __func__, "5n1 0x%04X Ch %s : invalid humidity: %d %%rH", | |
| sensor_id, channel_str, humidity); | |
| return DECODE_FAIL_SANITY; | |
| } | |
| /* clang-format off */ | |
| data_t *data; | |
| data = data_make( | |
| "model", "", DATA_STRING, "Acurite-5n1", | |
| "message_type", NULL, DATA_INT, message_type, | |
| "id", NULL, DATA_INT, sensor_id, | |
| "channel", NULL, DATA_STRING, channel_str, | |
| "sequence_num", NULL, DATA_INT, sequence_num, | |
| "battery_ok", "Battery", DATA_INT, !battery_low, | |
| "wind_avg_km_h", "wind_speed", DATA_FORMAT, "%.1f km/h", DATA_DOUBLE, wind_speed_kph, | |
| "temperature_F", "temperature", DATA_FORMAT, "%.1f F", DATA_DOUBLE, tempf, | |
| "humidity", NULL, DATA_FORMAT, "%u %%", DATA_INT, humidity, | |
| "mic", "Integrity", DATA_STRING, "CHECKSUM", | |
| NULL); | |
| /* clang-format on */ | |
| decoder_output_data(decoder, data); | |
| } else { | |
| decoder_logf(decoder, 1, __func__, "unknown message type 0x02%x", message_type); | |
| return DECODE_FAIL_SANITY; | |
| } | |
| return 1; // If we got here 1 valid message was output | |
| } | |
| /** | |
| Acurite Atlas weather and lightning sensor. | |
| | Reading | Operating Range | Reading Frequency | Accuracy | | |
| | --- | --- | --- | --- | | |
| | Temperature Range | -40 to 158°F (-40 to 70°C) | 30 seconds | ± 1°F | | |
| | Humidity Range | 1-100% RH | 30 seconds | ± 2% RH | | |
| | Wind Speed | 0-160 mph (0-257 km/h) | 10 seconds | ± 1 mph ≤ 10 mph, ± 10% > 10 mph | | |
| | Wind Direction | 360° | 30 seconds | ± 3° | | |
| | Rain | .01 inch intervals (0.254 mm) | 30 seconds | ± 5% | | |
| | UV Index | 0 to 15 index | 30 seconds | ± 1 | | |
| | Light Intensity | to 120,000 Lumens | 30 seconds | n/a | | |
| | Lightning | Up to 25 miles away (40 km) | 10 seconds | n/a | | |
| The Atlas reports direction with an AS5600 hall effect sensor, it has 12-bit resolution according to the spec sheet. https://ams.com/as5600 | |
| Acurite Atlas Message Type Format: | |
| Message Type 0x25 (Wind Speed, Temperature, Relative Humidity, ???) | |
| Byte 1 Byte 2 Byte 3 Byte 4 Byte 5 Byte 6 Byte 7 Byte 8 Byte 9 Byte 10 | |
| cc??ssdd dddddddd pb011011 pWWWWWWW pWTTTTTT pTTTTTTT pHHHHHHH pCCCCCCC pCCDDDDD kkkkkkkkk | |
| Note: 13 bits for Temp is too much, should only be 11 bits. | |
| Message Type 0x26 (Wind Speed, Wind Vector, Rain Counter, ???) | |
| Byte 1 Byte 2 Byte 3 Byte 4 Byte 5 Byte 6 Byte 7 Byte 8 Byte 9 Byte 10 | |
| cc??ssdd dddddddd pb011100 pWWWWWWW pW?VVVVV pVVVVVRR pRRRRRRR pCCCCCCC pCCDDDDD kkkkkkkkk | |
| CHANNEL:2b xx ~SEQ:2d ~DEVICE:10d xx ~TYPE:6h SPEED:x~7bx~1b DIR:x~5bx~5bxx x~7b x~7b x~7b CHK:8h | |
| Note: 10 bits for Vector is too much, should only be 9 bits. | |
| Note: 7 bits for Rain not enough, should reasonably be 10 bits. | |
| Message Type 0x27 (Wind Speed, UV and Lux data) | |
| Byte 1 Byte 2 Byte 3 Byte 4 Byte 5 Byte 6 Byte 7 Byte 8 Byte 9 Byte 10 | |
| cc??ssdd dddddddd pb011101 pWWWWWWW pW??UUUU pLLLLLLL pLLLLLLL pCCCCCCC pCCDDDDD kkkkkkkkk | |
| Note: 6 bits for UV is too much, should only be 4 bits. | |
| JRH - Definitely only 4 bits, seeing the occasional value of 32 or 34. No idea what the 2 bits between | |
| wind speed and UV are. | |
| CHANNEL:2b xx ~SEQ:2d ~DEVICE:10d xx ~TYPE:6h SPEED:x~7bx~1b UV:~6d LUX:x~7bx~7b x~7b x~7b CHK:8h | |
| Lux needs to multiplied by 10. | |
| - b = bATTERY | |
| - c = cHANNEL | |
| - d = dEVICE | |
| - k = CHECkSUM | |
| - p = pARITY | |
| - s = sEQUENCE | |
| - ? = uNKNOWN | |
| - H = relative Humidity (percent) | |
| - R = Rain (0.01 inch bucket tip count) | |
| - T = Temperature (Fahrenheit. Subtract 400 then divide by 10.) | |
| - V = wind Vector (degrees decimal) | |
| - W = Wind speed (miles per hour) | |
| - U = UV Index | |
| - L = Lux | |
| - C = lightning strike Count | |
| - D = lightning Distance (miles) | |
| */ | |
| static int acurite_atlas_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row) | |
| { | |
| uint8_t humidity, sequence_num, message_type; | |
| char raw_str[31], *rawp; | |
| uint16_t sensor_id; | |
| int raincounter, battery_low; | |
| int exception = 0; | |
| float tempf, wind_dir, wind_speed_mph; | |
| data_t *data; | |
| int browlen = (bitbuffer->bits_per_row[row] + 7) / 8; | |
| uint8_t *bb = bitbuffer->bb[row]; | |
| message_type = bb[2] & 0x3f; | |
| sensor_id = ((bb[0] & 0x03) << 8) | bb[1]; | |
| char const *channel_str = acurite_getChannel(bb[0]); | |
| // There are still a few unknown/unused bits in the message that | |
| // message that could possibly hold some data. Add the raw message hex to | |
| // to the structured data output to allow future analysis without | |
| // having to enable debug for long running rtl_433 processes. | |
| rawp = (char *)raw_str; | |
| for (int i=0; i < MIN(browlen, 15); i++) { | |
| sprintf(rawp,"%02x",bb[i]); | |
| rawp += 2; | |
| } | |
| *rawp = '\0'; | |
| // The sensor sends the same data three times, each of these have | |
| // an indicator of which one of the three it is. This means the | |
| // checksum and first byte will be different for each one. | |
| // The bits 4,5 of byte 0 indicate which copy | |
| // xxxx 00 xx = first copy | |
| // xxxx 01 xx = second copy | |
| // xxxx 10 xx = third copy | |
| sequence_num = (bb[0] & 0x0c) >> 2; | |
| // Battery status is the 7th bit 0x40. 1 = normal, 0 = low | |
| battery_low = (bb[2] & 0x40) == 0; | |
| // Wind speed is 8-bits raw MPH | |
| // Spec is 0-200 MPH | |
| wind_speed_mph = ((bb[3] & 0x7F) << 1) | ((bb[4] & 0x40) >> 6); | |
| if (wind_speed_mph > 200) { | |
| decoder_logf(decoder, 1, __func__, "Atlas 0x%04X Ch %s, invalid wind spped: %.1f MPH", | |
| sensor_id, channel_str, wind_speed_mph); | |
| return DECODE_FAIL_SANITY; | |
| } | |
| /* clang-format off */ | |
| data = data_make( | |
| "model", "", DATA_STRING, "Acurite-Atlas", | |
| "id", NULL, DATA_INT, sensor_id, | |
| "channel", NULL, DATA_STRING, channel_str, | |
| "sequence_num", NULL, DATA_INT, sequence_num, | |
| "battery_ok", "Battery", DATA_INT, !battery_low, | |
| "message_type", NULL, DATA_INT, message_type, | |
| "wind_avg_mi_h", "Wind Speed", DATA_FORMAT, "%.1f mi/h", DATA_DOUBLE, wind_speed_mph, | |
| NULL); | |
| /* clang-format on */ | |
| if (message_type == ACURITE_MSGTYPE_ATLAS_WNDSPD_TEMP_HUM || | |
| message_type == ACURITE_MSGTYPE_ATLAS_WNDSPD_TEMP_HUM_LTNG) { | |
| // Wind speed, temperature and humidity | |
| // Spec: temperature range -40 to 158 F | |
| // There seem to be 13 bits for temperature but only 11 needed. | |
| // Decode as 11 bits, flag exception if the other two bits are ever | |
| // non-zero so they can be investigated. | |
| int temp_raw = (bb[4] & 0x0F) << 7 | (bb[5] & 0x7F); | |
| if ((bb[4] & 0x30) != 0) | |
| exception++; | |
| tempf = (temp_raw - 400) * 0.1; | |
| if (tempf > 158.0) { | |
| decoder_logf(decoder, 1, __func__, "Atlas 0x%04X Ch %s, invalid temperature: %0.1f F", | |
| sensor_id, channel_str, tempf); | |
| return DECODE_FAIL_SANITY; | |
| } | |
| // Fail sanity check over 100% humidity | |
| // Allow 0 because very low battery or defective sensor will report | |
| // those values. | |
| humidity = (bb[6] & 0x7f); | |
| if (humidity > 100) { | |
| decoder_logf(decoder, 1, __func__, "0x%04X Ch %s : Impossible humidity: %d %%rH", | |
| sensor_id, channel_str, humidity); | |
| return DECODE_FAIL_SANITY; | |
| } | |
| if (humidity == 0) | |
| exception++; | |
| /* clang-format off */ | |
| data = data_append(data, | |
| "temperature_F", "temperature", DATA_FORMAT, "%.1f F", DATA_DOUBLE, tempf, | |
| "humidity", NULL, DATA_FORMAT, "%u %%", DATA_INT, humidity, | |
| NULL); | |
| /* clang-format on */ | |
| } | |
| if (message_type == ACURITE_MSGTYPE_ATLAS_WNDSPD_RAIN || | |
| message_type == ACURITE_MSGTYPE_ATLAS_WNDSPD_RAIN_LTNG) { | |
| // Wind speed, wind direction, and rain fall | |
| // Wind direction is in degrees, 0-360, only 9 bits needed | |
| // but historically decoded as 10 bits. | |
| // There seems to be 11 bits available | |
| // As with temperatuve message, flag msg if those two extra bits | |
| // are ever non-zero so they can be investigated | |
| // Note: output as float, but currently can only be decoded an integer | |
| wind_dir = ((bb[4] & 0x1f) << 5) | ((bb[5] & 0x7c) >> 2); | |
| if ((bb[4] & 0x30) != 0) | |
| exception++; | |
| if (wind_dir > 360) { | |
| decoder_logf(decoder, 1, __func__, "Atlas 0x%04X Ch %s, invalid wind direction: %0.1fF", | |
| sensor_id, channel_str, wind_dir); | |
| return DECODE_FAIL_SANITY; | |
| } | |
| // range: 0 to 5.11 in, 0.01 inch increments, accumulated | |
| // JRH: Confirmed 9 bits, counter rolls over after 5.11 inches | |
| raincounter = ((bb[5] & 0x03) << 7) | (bb[6] & 0x7F); | |
| /* clang-format off */ | |
| data = data_append(data, | |
| "wind_dir_deg", NULL, DATA_FORMAT, "%.1f", DATA_DOUBLE, wind_dir, | |
| "rain_in", "Rainfall Accumulation", DATA_FORMAT, "%.2f in", DATA_DOUBLE, raincounter * 0.01f, | |
| NULL); | |
| /* clang-format on */ | |
| } | |
| if (message_type == ACURITE_MSGTYPE_ATLAS_WNDSPD_UV_LUX || | |
| message_type == ACURITE_MSGTYPE_ATLAS_WNDSPD_UV_LUX_LTNG) { | |
| // Wind speed, UV Index, Light Intensity, and optionally Lightning | |
| // Spec UV index is 0-16 (but can only be 0-15) | |
| int uv = (bb[4] & 0x0f); | |
| // Light intensity 0 - 120,000 lumens / 10 | |
| // 14 bits are available (0-16,383) | |
| int lux = ((bb[5] & 0x7f) << 7) | (bb[6] & 0x7F); | |
| if (lux > 12000) { | |
| decoder_logf(decoder, 1, __func__, "Atlas 0x%04X Ch %s, invalid lux %d", | |
| sensor_id, channel_str, lux); | |
| return DECODE_FAIL_SANITY; | |
| } | |
| /* clang-format off */ | |
| data = data_append(data, | |
| "uv", NULL, DATA_INT, uv, | |
| "lux", NULL, DATA_INT, lux * 10, | |
| NULL); | |
| /* clang-format on */ | |
| } | |
| if ((message_type == ACURITE_MSGTYPE_ATLAS_WNDSPD_TEMP_HUM_LTNG || | |
| message_type == ACURITE_MSGTYPE_ATLAS_WNDSPD_RAIN_LTNG || | |
| message_type == ACURITE_MSGTYPE_ATLAS_WNDSPD_UV_LUX_LTNG)) { | |
| // @todo decode strike_distance to miles or KM. | |
| int strike_count = ((bb[7] & 0x7f) << 2) | ((bb[8] & 0x60) >> 5); | |
| int strike_distance = bb[8] & 0x1f; | |
| /* clang-format off */ | |
| data = data_append(data, | |
| "strike_count", NULL, DATA_INT, strike_count, | |
| "strike_distance", NULL, DATA_INT, strike_distance, | |
| NULL); | |
| /* clang-format on */ | |
| } | |
| // @todo only do this if exception != 0, but would be somewhat incompatible | |
| data = data_append(data, | |
| "exception", "data_exception", DATA_INT, exception, | |
| "raw_msg", "raw_message", DATA_STRING, raw_str, | |
| NULL); | |
| decoder_output_data(decoder, data); | |
| return 1; // one valid message decoded | |
| } | |
| /** | |
| Acurite 592TXR Temperature Humidity sensor decoder | |
| Message Type 0x04, 7 bytes | |
| | Byte 0 | Byte 1 | Byte 2 | Byte 3 | Byte 4 | Byte 5 | Byte 6 | | |
| | --------- | --------- | --------- | --------- | --------- | --------- | --------- | | |
| | CCII IIII | IIII IIII | pB00 0100 | pHHH HHHH | p??T TTTT | pTTT TTTT | KKKK KKKK | | |
| - C: Channel 00: C, 10: B, 11: A, (01 is invalid) | |
| - I: Device ID (14 bits) | |
| - B: Battery, 1 is battery OK, 0 is battery low | |
| - M: Message type (6 bits), 0x04 | |
| - T: Temperature Celsius (11 - 14 bits?), + 1000 * 10 | |
| - H: Relative Humidity (%) (7 bits) | |
| - K: Checksum (8 bits) | |
| - p: Parity bit | |
| Notes: | |
| - Temperature | |
| - Encoded as Celsius + 1000 * 10 | |
| - only 11 bits needed for specified range -40 C to 70 C (-40 F - 158 F) | |
| - However 14 bits available for temperature, giving possible range of -100 C to 1538.4 C | |
| - @todo - check if high 3 bits ever used for anything else | |
| */ | |
| static int acurite_tower_decode(r_device *decoder, bitbuffer_t *bitbuffer, uint8_t *bb) | |
| { | |
| // MIC (checkum, parity) validated in calling function | |
| (void)bitbuffer; | |
| int exception = 0; | |
| char const* channel_str = acurite_getChannel(bb[0]); | |
| int sensor_id = ((bb[0] & 0x3f) << 8) | bb[1]; | |
| int battery_low = (bb[2] & 0x40) == 0; | |
| // Spec is relative humidity 1-99% | |
| // Allowing value of 0, very low battery or broken sensor can return 0% or 1% | |
| int humidity = (bb[3] & 0x7f); | |
| if (humidity < 0 || humidity > 100) { | |
| decoder_logf(decoder, 1, __func__, "0x%04X Ch %s : Impossible humidity: %d %%rH", | |
| sensor_id, channel_str, humidity); | |
| return DECODE_FAIL_SANITY; | |
| } | |
| // temperature encoding used by "tower" sensors 592txr | |
| // 14 bits available after removing both parity bits. | |
| // 11 bits needed for specified range -40 C to 70 C (-40 F - 158 F) | |
| // Possible ranges are -100 C to 1538.4 C, but most of that range | |
| // is not possible on Earth. | |
| // pIII IIII pIII IIII | |
| int temp_raw = ((bb[4] & 0x7F) << 7) | (bb[5] & 0x7F); | |
| float tempc = (temp_raw - 1000) * 0.1f; | |
| if (tempc < -40 || tempc > 70) { | |
| decoder_logf(decoder, 1, __func__, "0x%04X Ch %s : Impossible temperature: %0.2f C", | |
| sensor_id, channel_str, tempc); | |
| return DECODE_FAIL_SANITY; | |
| } | |
| // flag if bits 12-14 of temperature are ever non-zero | |
| // so they can be investigated for other possible information | |
| if ((temp_raw & 0x3800) != 0) | |
| exception++; | |
| data_t* data; | |
| /* clang-format off */ | |
| data = data_make( | |
| "model", "", DATA_STRING, "Acurite-Tower", | |
| "id", "", DATA_INT, sensor_id, | |
| "channel", NULL, DATA_STRING, channel_str, | |
| "battery_ok", "Battery", DATA_INT, !battery_low, | |
| "temperature_C", "Temperature", DATA_FORMAT, "%.1f C", DATA_DOUBLE, tempc, | |
| "humidity", "Humidity", DATA_FORMAT, "%u %%", DATA_INT, humidity, | |
| "mic", "Integrity", DATA_STRING, "CHECKSUM", | |
| NULL); | |
| /* clang-format on */ | |
| if (exception) | |
| data_append_exception(data, exception, bb, ACURITE_TXR_BYTELEN); | |
| decoder_output_data(decoder, data); | |
| return 1; | |
| } | |
| /** | |
| Acurite 1190/1192 leak detector | |
| Note: it seems like Acurite has deleted this product and | |
| related information from their website so specs, manual, etc. | |
| aren't easy to find | |
| */ | |
| static int acurite_1190_decode(r_device *decoder, bitbuffer_t *bitbuffer, uint8_t *bb) | |
| { | |
| (void)bitbuffer; | |
| // Channel is the first two bits of the 0th byte | |
| // but only 3 of the 4 possible values are valid | |
| char const* channel_str = acurite_getChannel(bb[0]); | |
| // Tower sensor ID is the last 14 bits of byte 0 and 1 | |
| // CCII IIII | IIII IIII | |
| int sensor_id = ((bb[0] & 0x3f) << 8) | bb[1]; | |
| // Battery status is the 7th bit 0x40. 1 = normal, 0 = low | |
| int battery_low = (bb[2] & 0x40) == 0; | |
| // Leak indicator bit is the 5th bit of byte 3. 1 = wet, 0 = dry | |
| int is_wet = (bb[3] & 0x10) >> 4; | |
| data_t* data; | |
| /* clang-format off */ | |
| data = data_make( | |
| "model", "", DATA_STRING, "Acurite-Leak", | |
| "id", "", DATA_INT, sensor_id, | |
| "channel", NULL, DATA_STRING, channel_str, | |
| "battery_ok", "Battery", DATA_INT, !battery_low, | |
| "leak_detected", "Leak", DATA_INT, is_wet, | |
| "mic", "Integrity", DATA_STRING, "CHECKSUM", | |
| NULL); | |
| /* clang-format on */ | |
| decoder_output_data(decoder, data); | |
| return 1; | |
| } | |
| /** | |
| Decode Acurite 515 Refrigerator/Freezer sensors | |
| Byte 0 | Byte 1 | Byte 2 | Byte 3 | Byte 4 | Byte 5 | |
| CCII IIII | IIII IIII | pBMM MMMM | bTTT TTTT | bTTT TTTT | KKKK KKKK | |
| - C: Channel 00: C, 10: B, 11: A | |
| - I: Device ID (14 bits), volatie, resets at power up | |
| - B: Battery, 1 is battery OK, 0 is battery low | |
| - M: Message type (6 bits), 0x8: Refrigerator, 0x9: Freezer | |
| - T: Temperature Fahrenheit (14 bits?), + 1480 * 10 | |
| - K: Checksum (8 bits) | |
| - p: Parity bit | |
| */ | |
| static int acurite_515_decode(r_device *decoder, bitbuffer_t *bitbuffer, uint8_t *bb) | |
| { | |
| // length, MIC (checkum, parity) validated in calling function | |
| (void)bitbuffer; | |
| int exception = 0; | |
| char channel_type_str[3]; | |
| uint8_t message_type = bb[2] & 0x3f; | |
| // Channel A, B, C, common with other Acurite devices | |
| char const* channel_str = acurite_getChannel(bb[0]); | |
| channel_type_str[0] = channel_str[0]; | |
| if (message_type == ACURITE_MSGTYPE_515_REFRIGERATOR) | |
| channel_type_str[1] = 'R'; | |
| else if (message_type == ACURITE_MSGTYPE_515_FREEZER) | |
| channel_type_str[1] = 'F'; | |
| else { | |
| decoder_logf(decoder, 1, __func__, "unknown message type 0x02%x", message_type); | |
| return DECODE_FAIL_SANITY; | |
| } | |
| channel_type_str[2] = 0; | |
| // Sensor ID is the last 14 bits of byte 0 and 1 | |
| // CCII IIII | IIII IIII | |
| // The sensor ID changes on each power-up of the sensor. | |
| uint16_t sensor_id = ((bb[0] & 0x3f) << 8) | bb[1]; | |
| // temperature encoding 14 bits after removing both parity bits. | |
| // Spec range from Manual: -40 F to 158 F (-40 to 70 C) | |
| // Offset to avoid negative values is 1480 | |
| // Possible encoding range with 14 bits (0-16383) is -148.0 F to 1490.3 F | |
| // Only 12 bits needed to represent -40 F to 158 F with encoding offset of 1480. | |
| // encoding range at 12 bits with +1480 offset: -148.0 F to +261.5 F | |
| int temp_raw = ((bb[3] & 0x7F) << 7) | (bb[4] & 0x7F); | |
| float tempf = (temp_raw - 1480) * 0.1f; | |
| if (tempf < -40.0 || tempf > 158.0) { | |
| decoder_logf(decoder, 1, __func__, "515 0x%04X Ch %s, invalid temperature: %0.1f F", | |
| sensor_id, channel_str, tempf); | |
| return DECODE_FAIL_SANITY; | |
| } | |
| // flag if bits 13 - 14 of temperature are ever non-zero | |
| // so they can be investigated | |
| if ((temp_raw & 0x3000) != 0) | |
| exception++; | |
| // Battery status is the 7th bit 0x40. 1 = normal, 0 = low | |
| int battery_low = (bb[2] & 0x40) == 0; | |
| data_t* data; | |
| /* clang-format off */ | |
| data = data_make( | |
| "model", "", DATA_STRING, "Acurite-515", | |
| "id", "", DATA_INT, sensor_id, | |
| "channel", NULL, DATA_STRING, channel_type_str, | |
| "battery_ok", "Battery", DATA_INT, !battery_low, | |
| "temperature_F", "Temperature", DATA_FORMAT, "%.1f F", DATA_DOUBLE, tempf, | |
| "mic", "Integrity", DATA_STRING, "CHECKSUM", | |
| NULL); | |
| /* clang-format on */ | |
| if (exception) | |
| data_append_exception(data, exception, bb, ACURITE_515_BYTELEN); | |
| decoder_output_data(decoder, data); | |
| return 1; | |
| } | |
| /** | |
| Check Acurite TXR message integrity (length, checksum, parity) | |
| Need to pass in expected length - correct number of bytes for | |
| that message type. | |
| Return 0 for valid roe or DECODE_ABORT_LENGHT, DECODE_FAIL_MIC, DECODE_FAIL_SANITY | |
| Long rows with extra bits/bytes (from demod/bit slicing) | |
| will be accepted as long the bytes up to the expected length | |
| pass checksum and parity tests. | |
| */ | |
| static int acurite_txr_check(r_device *decoder, uint8_t const bb[], unsigned browlen, unsigned explen) | |
| { | |
| // Currently shortest Acurite "TXR" message is 6 bytes | |
| // 5 bytes could possibly be valid, but would only have | |
| // a single data byte after Channel, ID, message type, and checksum | |
| // Really short rows (1-2) bytes, should be rejected quietly earlier | |
| // so real error types can be seen | |
| if (browlen < 6) | |
| return DECODE_ABORT_LENGTH; | |
| if (browlen < explen) { | |
| decoder_log_bitrow(decoder, 1, __func__, bb, browlen * 8, "wrong length for msg type"); | |
| return DECODE_ABORT_LENGTH; | |
| } | |
| // 8 bit checksum in the last byte | |
| if ((add_bytes(bb, explen - 1) & 0xff) != bb[explen - 1]) { | |
| decoder_log_bitrow(decoder, 1, __func__, bb, browlen * 8, "bad checksum"); | |
| return DECODE_FAIL_MIC; | |
| } | |
| // Verify parity bits | |
| // Bytes 2 ... n-1 should all have even parity | |
| // (ID bytes and checksum byte are all 8 bit, so no parity check) | |
| int parity = parity_bytes(&bb[2], explen - 3); | |
| if (parity) { | |
| decoder_log_bitrow(decoder, 1, __func__, bb, browlen * 8,"bad parity"); | |
| return DECODE_FAIL_MIC; | |
| } | |
| // All of these devices have channel (A, B, C) in two bits (mask 0c0) of byte 0 | |
| // 00: C, 10: B, 11: A, (01 aka 'E' is invalid) | |
| // check sanity to cut down an bad messages that pass MIC checks | |
| char const *channel_str = acurite_getChannel(bb[0]); | |
| if (*channel_str == 'E') { | |
| uint8_t message_type = bb[2] & 0x3f; | |
| decoder_logf(decoder, 1, __func__, | |
| "bad channel Ch %s, msg type 0x%02x, msg len %d", | |
| channel_str, message_type, browlen); | |
| return DECODE_FAIL_SANITY; | |
| } | |
| return 0; | |
| } | |
| /** | |
| Process messages for Acurite weather stations, tower and related sensors | |
| @sa acurite_1190_decode() | |
| @sa acurite_515_decode() | |
| @sa acurite_6045_decode() | |
| @sa acurite_899_decode() | |
| #sa acurite_3n1_decode() | |
| @sa acurite_5n1_decode() | |
| @sa acurite_atlas_decode() | |
| @sa acurite_tower_decode() | |
| This callback is used for devices that use a very similar message format: | |
| - 592TXR / 6002RM / 6044m Tower sensor and related temperature/humidity sensors | |
| - Atlas (7-in-1) Weather Station | |
| - Iris (5-in-1) weather station | |
| - Notos (3-in-1) Weather station | |
| - 6045M Lightning Detector with Temperature and Humidity | |
| - 899 Rain Fall Gauge | |
| - 515 Refrigerator/Freezer sensors | |
| - 1190/1192 Water alarm | |
| These devices have a message type in the 3rd byte and an 8 bit checksum | |
| in the last byte. | |
| */ | |
| static int acurite_txr_callback(r_device *decoder, bitbuffer_t *bitbuffer) | |
| { | |
| int decoded = 0; | |
| int error_ret = 0; | |
| int ret = 0; | |
| uint8_t *bb; | |
| uint8_t message_type; | |
| bitbuffer_invert(bitbuffer); | |
| for (uint16_t brow = 0; brow < bitbuffer->num_rows; ++brow) { | |
| int row_bit_cnt = bitbuffer->bits_per_row[brow]; | |
| int browlen = row_bit_cnt / 8; // assumption: safe to round down, extra bits are spurious | |
| bb = bitbuffer->bb[brow]; | |
| // Known messages in this family are between 6 and 10 bytes | |
| if (browlen < 6) { | |
| continue; // quietly skip short rows | |
| } | |
| // Currently known longest message is 10 bytes (Atlas with lightning sensorr) | |
| if (browlen > 10) { | |
| decoder_logf(decoder, 2, __func__, "Skipping wrong len row %u bits %u, bytes %d", | |
| brow, row_bit_cnt, browlen); | |
| error_ret = DECODE_ABORT_LENGTH; | |
| continue; | |
| } | |
| decoder_logf(decoder, 2, __func__, | |
| "row %u bits %u, bytes %d, extra bits %d, msg type 0x%02x", | |
| brow, row_bit_cnt, browlen, row_bit_cnt % 8, bb[2] & 0x3f); | |
| // quietly ignore rows of zeros (ID, msg type, checksum) | |
| if (bb[0] == 0 && bb[1] == 0 && bb[2] == 0 && bb[browlen - 1] == 0) | |
| continue; | |
| // acurite sensors with a common format have a message type | |
| // in the lower 6 bits of the 3rd byte. | |
| // Format: PBMMMMMM | |
| // P = Parity | |
| // B = Battery Normal | |
| // M = Message type | |
| message_type = bb[2] & 0x3f; | |
| // Check so that unknown message type can be flagged | |
| // and dispatching to decoders can be easier to maintain | |
| switch(message_type) { | |
| case ACURITE_MSGTYPE_1190_DETECTOR: | |
| case ACURITE_MSGTYPE_TOWER_SENSOR: | |
| case ACURITE_MSGTYPE_6045M: | |
| case ACURITE_MSGTYPE_5N1_WINDSPEED_WINDDIR_RAINFALL: | |
| case ACURITE_MSGTYPE_5N1_WINDSPEED_TEMP_HUMIDITY: | |
| case ACURITE_MSGTYPE_ATLAS_WNDSPD_TEMP_HUM: | |
| case ACURITE_MSGTYPE_ATLAS_WNDSPD_RAIN: | |
| case ACURITE_MSGTYPE_ATLAS_WNDSPD_UV_LUX: | |
| case ACURITE_MSGTYPE_ATLAS_WNDSPD_TEMP_HUM_LTNG: | |
| case ACURITE_MSGTYPE_ATLAS_WNDSPD_RAIN_LTNG: | |
| case ACURITE_MSGTYPE_ATLAS_WNDSPD_UV_LUX_LTNG: | |
| case ACURITE_MSGTYPE_515_REFRIGERATOR: | |
| case ACURITE_MSGTYPE_515_FREEZER: | |
| case ACURITE_MSGTYPE_3N1_WINDSPEED_TEMP_HUMIDITY: | |
| case ACURITE_MSGTYPE_899_RAINFALL: | |
| break; | |
| default: | |
| decoder_log_bitrow(decoder, 1, __func__, bb, row_bit_cnt, | |
| "Unknown message type"); | |
| error_ret = DECODE_FAIL_SANITY; | |
| continue; | |
| break; | |
| } | |
| // Check message type and dispatch to appropriate decoders | |
| // NOTE: since we are processing each row, do not return | |
| // until all rows have been processed | |
| if (message_type == ACURITE_MSGTYPE_TOWER_SENSOR) { | |
| if ((ret = acurite_txr_check(decoder, bb, browlen, ACURITE_TXR_BYTELEN)) != 0) { | |
| error_ret = ret; | |
| } else { | |
| if ((ret = acurite_tower_decode(decoder, bitbuffer, bb)) > 0) { | |
| decoded += ret; | |
| } else if (ret < 0) { | |
| error_ret = ret; | |
| } | |
| } | |
| } | |
| if (message_type == ACURITE_MSGTYPE_1190_DETECTOR) { | |
| if ((ret = acurite_txr_check(decoder, bb, browlen, ACURITE_1190_BYTELEN)) != 0) { | |
| error_ret = ret; | |
| } else { | |
| if ((ret = acurite_1190_decode(decoder, bitbuffer, bb)) > 0) { | |
| decoded += ret; | |
| } else if (ret < 0) { | |
| error_ret = ret; | |
| } | |
| } | |
| } | |
| if (message_type == ACURITE_MSGTYPE_6045M) { | |
| if ((ret = acurite_txr_check(decoder, bb, browlen, ACURITE_6045_BYTELEN)) != 0) { | |
| error_ret = ret; | |
| } else { | |
| if ((ret = acurite_6045_decode(decoder, bitbuffer, brow)) > 0) { | |
| decoded += ret; | |
| } else if (ret < 0) { | |
| error_ret = ret; | |
| } | |
| } | |
| } | |
| if (message_type == ACURITE_MSGTYPE_515_REFRIGERATOR || | |
| message_type == ACURITE_MSGTYPE_515_FREEZER) { | |
| if ((ret = acurite_txr_check(decoder, bb, browlen, ACURITE_515_BYTELEN)) != 0) { | |
| error_ret = ret; | |
| } else { | |
| if ((ret = acurite_515_decode(decoder, bitbuffer, bb)) > 0) { | |
| decoded += ret; | |
| } else if (ret < 0) { | |
| error_ret = ret; | |
| } | |
| } | |
| } | |
| if (message_type == ACURITE_MSGTYPE_5N1_WINDSPEED_TEMP_HUMIDITY || | |
| message_type == ACURITE_MSGTYPE_5N1_WINDSPEED_WINDDIR_RAINFALL) { | |
| if ((ret = acurite_txr_check(decoder, bb, browlen, ACURITE_5N1_BYTELEN)) != 0) { | |
| error_ret = ret; | |
| } else { | |
| if ((ret = acurite_5n1_decode(decoder, bitbuffer, bb)) > 0) { | |
| decoded += ret; | |
| } else if (ret < 0) { | |
| error_ret = ret; | |
| } | |
| } | |
| } | |
| if (message_type == ACURITE_MSGTYPE_3N1_WINDSPEED_TEMP_HUMIDITY) { | |
| /* | |
| @todo - does 3n1 use parity checking? | |
| 3n1 g001 in rtl_433_test has odd parity the 2nd to last byte in both copies | |
| but g002 passes parity check | |
| */ | |
| if (browlen < ACURITE_3N1_BYTELEN) { | |
| decoder_log_bitrow(decoder, 1, __func__, bb, browlen * 8, "3n1 wrong length"); | |
| error_ret = DECODE_ABORT_LENGTH; | |
| continue; | |
| } | |
| if ((add_bytes(bb, ACURITE_3N1_BYTELEN - 1) & 0xff) != | |
| bb[ACURITE_3N1_BYTELEN - 1]) { | |
| decoder_log_bitrow(decoder, 1, __func__, bb, browlen * 8, "bad checksum"); | |
| error_ret = DECODE_FAIL_MIC; | |
| continue; | |
| } | |
| if ((ret = acurite_3n1_decode(decoder, bitbuffer, bb)) > 0) { | |
| decoded += ret; | |
| } else if (ret < 0) { | |
| error_ret = ret; | |
| } | |
| } | |
| if (message_type == ACURITE_MSGTYPE_899_RAINFALL) { | |
| /* | |
| @todo - does the 899 use parity checking? | |
| The available sample shows a parity bit in the message byte | |
| but there isn't enough accumulated rain in the data bytes | |
| to see if parity is used | |
| */ | |
| if ((ret = acurite_txr_check(decoder, bb, browlen, ACURITE_899_BYTELEN)) != 0) { | |
| error_ret = ret; | |
| } else { | |
| if ((ret = acurite_899_decode(decoder, bitbuffer, bb)) > 0) { | |
| decoded += ret; | |
| } else if (ret < 0) { | |
| error_ret = ret; | |
| } | |
| } | |
| } | |
| // process Atlas | |
| switch(message_type) { | |
| // Atlas messages without lightning sensor installed - 8 bytes | |
| case ACURITE_MSGTYPE_ATLAS_WNDSPD_TEMP_HUM: | |
| case ACURITE_MSGTYPE_ATLAS_WNDSPD_RAIN: | |
| case ACURITE_MSGTYPE_ATLAS_WNDSPD_UV_LUX: | |
| if ((ret = acurite_txr_check(decoder, bb, browlen, ACURITE_ATLAS_BYTELEN)) != 0) { | |
| error_ret = ret; | |
| } else { | |
| if ((ret = acurite_atlas_decode(decoder, bitbuffer, brow)) > 0) { | |
| decoded += ret; | |
| } else if (ret < 0) { | |
| error_ret = ret; | |
| } | |
| } | |
| break; | |
| // Atlas messages with lightning sensor installed - 10 bytes | |
| case ACURITE_MSGTYPE_ATLAS_WNDSPD_TEMP_HUM_LTNG: | |
| case ACURITE_MSGTYPE_ATLAS_WNDSPD_RAIN_LTNG: | |
| case ACURITE_MSGTYPE_ATLAS_WNDSPD_UV_LUX_LTNG: | |
| if ((ret = acurite_txr_check(decoder, bb, browlen, ACURITE_ATLAS_LTNG_BYTELEN)) != 0) { | |
| error_ret = ret; | |
| } else { | |
| if ((ret = acurite_atlas_decode(decoder, bitbuffer, brow)) > 0) { | |
| decoded += ret; | |
| } else if (ret < 0) { | |
| error_ret = ret; | |
| } | |
| } | |
| break; | |
| } | |
| decoder_logf(decoder, 2, __func__, | |
| "stats: row %u, msg type 0x%02x, bytes %d, decoded %d, error %d", | |
| brow, message_type, browlen, decoded, error_ret); | |
| } | |
| if (decoded > 0) | |
| return decoded; | |
| else | |
| return error_ret; | |
| } | |
| /** | |
| Acurite 00986 Refrigerator / Freezer Thermometer. | |
| Includes two sensors and a display, labeled 1 and 2, | |
| by default 1 - Refrigerator, 2 - Freezer. | |
| PPM, 5 bytes, sent twice, no gap between repeaters | |
| start/sync pulses two short, with short gaps, followed by | |
| 4 long pulse/gaps. | |
| @todo, the 2 short sync pulses get confused as data. | |
| Data Format - 5 bytes, sent LSB first, reversed: | |
| TT II II SS CC | |
| - T - Temperature in Fahrenheit, integer, MSB = sign. | |
| Encoding is "Sign and magnitude" | |
| - I - 16 bit sensor ID | |
| changes at each power up | |
| - S - status/sensor type | |
| 0x01 = Sensor 2 | |
| 0x02 = low battery | |
| - C = CRC (CRC-8 poly 0x07, little-endian) | |
| @todo | |
| - needs new PPM demod that can separate out the short | |
| start/sync pulses which confuse things and cause | |
| one data bit to be lost in the check value. | |
| 2018-04 A user with a dedicated receiver indicated the | |
| possibility that the transmitter actually drops the | |
| last bit instead of the demod. | |
| leaving some of the debugging code until the missing | |
| bit issue gets resolved. | |
| */ | |
| static int acurite_986_decode(r_device *decoder, bitbuffer_t *bitbuffer) | |
| { | |
| int const browlen = 5; | |
| uint8_t *bb, sensor_num, status, crc, crcc; | |
| uint8_t br[8]; | |
| int8_t tempf; // Raw Temp is 8 bit signed Fahrenheit | |
| uint16_t sensor_id, valid_cnt = 0; | |
| char sensor_type; | |
| char *channel_str; | |
| int battery_low; | |
| data_t *data; | |
| int result = 0; | |
| for (uint16_t brow = 0; brow < bitbuffer->num_rows; ++brow) { | |
| decoder_logf(decoder, 2, __func__, "row %u bits %u, bytes %d", brow, bitbuffer->bits_per_row[brow], browlen); | |
| if (bitbuffer->bits_per_row[brow] < 39 || | |
| bitbuffer->bits_per_row[brow] > 43 ) { | |
| if (bitbuffer->bits_per_row[brow] > 16) | |
| decoder_log(decoder, 2, __func__,"skipping wrong len"); | |
| result = DECODE_ABORT_LENGTH; | |
| continue; // DECODE_ABORT_LENGTH | |
| } | |
| bb = bitbuffer->bb[brow]; | |
| // Reduce false positives | |
| // may eliminate these with a better PPM (precise?) demod. | |
| if ((bb[0] == 0xff && bb[1] == 0xff && bb[2] == 0xff) || | |
| (bb[0] == 0x00 && bb[1] == 0x00 && bb[2] == 0x00)) { | |
| result = DECODE_ABORT_EARLY; | |
| continue; // DECODE_ABORT_EARLY | |
| } | |
| // Reverse the bits, msg sent LSB first | |
| for (int i = 0; i < browlen; i++) | |
| br[i] = reverse8(bb[i]); | |
| decoder_log_bitrow(decoder, 1, __func__, br, browlen * 8, "reversed"); | |
| tempf = br[0]; | |
| sensor_id = (br[1] << 8) + br[2]; | |
| status = br[3]; | |
| sensor_num = (status & 0x01) + 1; | |
| status = status >> 1; | |
| battery_low = ((status & 1) == 1); | |
| // By default Sensor 1 is the Freezer, 2 Refrigerator | |
| sensor_type = sensor_num == 2 ? 'F' : 'R'; | |
| channel_str = sensor_num == 2 ? "2F" : "1R"; | |
| crc = br[4]; | |
| crcc = crc8le(br, 4, 0x07, 0); | |
| if (crcc != crc) { | |
| decoder_logf_bitrow(decoder, 2, __func__, br, browlen * 8, "bad CRC: %02x -", crc8le(br, 4, 0x07, 0)); | |
| // HACK: rct 2018-04-22 | |
| // the message is often missing the last 1 bit either due to a | |
| // problem with the device or demodulator | |
| // Add 1 (0x80 because message is LSB) and retry CRC. | |
| if (crcc == (crc | 0x80)) { | |
| decoder_logf(decoder, 2, __func__, "CRC fix %02x - %02x", crc, crcc); | |
| } | |
| else { | |
| continue; // DECODE_FAIL_MIC | |
| } | |
| } | |
| if (tempf & 0x80) { | |
| tempf = (tempf & 0x7f) * -1; | |
| } | |
| decoder_logf(decoder, 1, __func__, "sensor 0x%04x - %d%c: %d F", sensor_id, sensor_num, sensor_type, tempf); | |
| /* clang-format off */ | |
| data = data_make( | |
| "model", "", DATA_STRING, "Acurite-986", | |
| "id", NULL, DATA_INT, sensor_id, | |
| "channel", NULL, DATA_STRING, channel_str, | |
| "battery_ok", "Battery", DATA_INT, !battery_low, | |
| "temperature_F", "temperature", DATA_FORMAT, "%f F", DATA_DOUBLE, (float)tempf, | |
| "status", "status", DATA_INT, status, | |
| "mic", "Integrity", DATA_STRING, "CRC", | |
| NULL); | |
| /* clang-format on */ | |
| decoder_output_data(decoder, data); | |
| valid_cnt++; | |
| } | |
| if (valid_cnt) | |
| return 1; | |
| return result; | |
| } | |
| /** | |
| Acurite 606 Temperature sensor | |
| */ | |
| static int acurite_606_decode(r_device *decoder, bitbuffer_t *bitbuffer) | |
| { | |
| data_t *data; | |
| uint8_t *b; | |
| int row; | |
| int16_t temp_raw; // temperature as read from the data packet | |
| float temp_c; // temperature in C | |
| int battery_ok; // the battery status: 1 is good, 0 is low | |
| int sensor_id; // the sensor ID - basically a random number that gets reset whenever the battery is removed | |
| row = bitbuffer_find_repeated_row(bitbuffer, 3, 32); // expected are 6 rows | |
| if (row < 0) | |
| return DECODE_ABORT_EARLY; | |
| if (bitbuffer->bits_per_row[row] > 33) | |
| return DECODE_ABORT_LENGTH; | |
| b = bitbuffer->bb[row]; | |
| if (b[4] != 0) | |
| return DECODE_FAIL_SANITY; | |
| // reject all blank messages | |
| if (b[0] == 0 && b[1] == 0 && b[2] == 0 && b[3] == 0) | |
| return DECODE_FAIL_SANITY; | |
| // calculate the checksum and only continue if we have a matching checksum | |
| uint8_t chk = lfsr_digest8(b, 3, 0x98, 0xf1); | |
| if (chk != b[3]) | |
| return DECODE_FAIL_MIC; | |
| // Processing the temperature: | |
| // Upper 4 bits are stored in nibble 1, lower 8 bits are stored in nibble 2 | |
| // upper 4 bits of nibble 1 are reserved for other usages (e.g. battery status) | |
| sensor_id = b[0]; | |
| battery_ok = (b[1] & 0x80) >> 7; | |
| temp_raw = (int16_t)((b[1] << 12) | (b[2] << 4)); | |
| temp_raw = temp_raw >> 4; | |
| temp_c = temp_raw * 0.1f; | |
| /* clang-format off */ | |
| data = data_make( | |
| "model", "", DATA_STRING, "Acurite-606TX", | |
| "id", "", DATA_INT, sensor_id, | |
| "battery_ok", "Battery", DATA_INT, battery_ok, | |
| "temperature_C", "Temperature", DATA_FORMAT, "%.1f C", DATA_DOUBLE, temp_c, | |
| "mic", "Integrity", DATA_STRING, "CHECKSUM", | |
| NULL); | |
| /* clang-format on */ | |
| decoder_output_data(decoder, data); | |
| return 1; | |
| } | |
| /** | |
| Acurite 590TX temperature/humidity sensor | |
| */ | |
| static int acurite_590tx_decode(r_device *decoder, bitbuffer_t *bitbuffer) | |
| { | |
| data_t *data; | |
| uint8_t *b; | |
| int row; | |
| int sensor_id; // the sensor ID - basically a random number that gets reset whenever the battery is removed | |
| int battery_ok; // the battery status: 1 is good, 0 is low | |
| int channel; | |
| int humidity; | |
| int temp_raw; // temperature as read from the data packet | |
| float temp_c; // temperature in C | |
| row = bitbuffer_find_repeated_row(bitbuffer, 3, 25); // expected are min 3 rows | |
| if (row < 0) | |
| return DECODE_ABORT_EARLY; | |
| if (bitbuffer->bits_per_row[row] > 25) | |
| return DECODE_ABORT_LENGTH; | |
| b = bitbuffer->bb[row]; | |
| if (b[4] != 0) // last byte should be zero | |
| return DECODE_FAIL_SANITY; | |
| // reject rows that are mostly zero | |
| if (b[0] == 0 && b[1] == 0 && b[2] == 0 && b[3] == 0) | |
| return DECODE_FAIL_SANITY; | |
| // parity check: odd parity on bits [0 .. 10] | |
| // i.e. 8 bytes and another 2 bits. | |
| uint8_t parity = b[0]; // parity as byte | |
| parity = (parity >> 4) ^ (parity & 0xF); // fold to nibble | |
| parity = (parity >> 2) ^ (parity & 0x3); // fold to 2 bits | |
| parity ^= b[1] >> 6; // add remaining bits | |
| parity = (parity >> 1) ^ (parity & 0x1); // fold to 1 bit | |
| if (!parity) { | |
| decoder_log(decoder, 1, __func__, "parity check failed"); | |
| return DECODE_FAIL_MIC; | |
| } | |
| // Processing the temperature: | |
| // Upper 4 bits are stored in nibble 1, lower 8 bits are stored in nibble 2 | |
| // upper 4 bits of nibble 1 are reserved for other usages (e.g. battery status) | |
| sensor_id = b[0] & 0xFE; //first 6 bits and it changes each time it resets or change the battery | |
| battery_ok = (b[0] & 0x01); //1=ok, 0=low battery | |
| //next 2 bits are checksum | |
| //next two bits are identify ID (maybe channel ?) | |
| channel = (b[1] >> 4) & 0x03; | |
| temp_raw = (int16_t)(((b[1] & 0x0F) << 12) | (b[2] << 4)); | |
| temp_raw = temp_raw >> 4; | |
| temp_c = (temp_raw - 500) * 0.1f; // NOTE: there seems to be a 50 degree offset? | |
| if (temp_raw >= 0 && temp_raw <= 100) // NOTE: no other way to differentiate humidity from temperature? | |
| humidity = temp_raw; | |
| else | |
| humidity = -1; | |
| /* clang-format off */ | |
| data = data_make( | |
| "model", "", DATA_STRING, "Acurite-590TX", | |
| "id", "", DATA_INT, sensor_id, | |
| "battery_ok", "Battery", DATA_INT, battery_ok, | |
| "channel", "Channel", DATA_INT, channel, | |
| "humidity", "Humidity", DATA_COND, humidity != -1, DATA_INT, humidity, | |
| "temperature_C", "Temperature", DATA_COND, humidity == -1, DATA_FORMAT, "%.1f C", DATA_DOUBLE, temp_c, | |
| "mic", "Integrity", DATA_STRING, "PARITY", | |
| NULL); | |
| /* clang-format on */ | |
| decoder_output_data(decoder, data); | |
| return 1; | |
| } | |
| /** | |
| Acurite 00275rm Room Monitor sensors | |
| */ | |
| static int acurite_00275rm_decode(r_device *decoder, bitbuffer_t *bitbuffer) | |
| { | |
| int result = 0; | |
| bitbuffer_invert(bitbuffer); | |
| // This sensor repeats a signal three times. Combine as fallback. | |
| uint8_t *b_rows[3] = {0}; | |
| int n_rows = 0; | |
| for (int row = 0; row < bitbuffer->num_rows; ++row) { | |
| if (n_rows < 3 && bitbuffer->bits_per_row[row] == 88) { | |
| b_rows[n_rows] = bitbuffer->bb[row]; | |
| n_rows++; | |
| } | |
| } | |
| // Combine signal if exactly three repeats were found | |
| if (n_rows == 3) { | |
| bitbuffer_add_row(bitbuffer); | |
| uint8_t *b = bitbuffer->bb[bitbuffer->num_rows - 1]; | |
| for (int i = 0; i < 11; ++i) { | |
| // The majority bit count wins | |
| b[i] = (b_rows[0][i] & b_rows[1][i]) | | |
| (b_rows[1][i] & b_rows[2][i]) | | |
| (b_rows[2][i] & b_rows[0][i]); | |
| } | |
| bitbuffer->bits_per_row[bitbuffer->num_rows - 1] = 88; | |
| } | |
| // Output the first valid row | |
| for (int row = 0; row < bitbuffer->num_rows; ++row) { | |
| if (bitbuffer->bits_per_row[row] != 88) { | |
| result = DECODE_ABORT_LENGTH; | |
| continue; // return DECODE_ABORT_LENGTH; | |
| } | |
| uint8_t *b = bitbuffer->bb[row]; | |
| // Check CRC | |
| if (crc16lsb(b, 11, 0x00b2, 0x00d0) != 0) { | |
| decoder_log_bitrow(decoder, 1, __func__, b, 11 * 8, "sensor bad CRC"); | |
| result = DECODE_FAIL_MIC; | |
| continue; // return DECODE_FAIL_MIC; | |
| } | |
| // Decode common fields | |
| int id = (b[0] << 16) | (b[1] << 8) | b[3]; | |
| int battery_low = (b[2] & 0x40) == 0; | |
| int model_flag = (b[2] & 1); | |
| int temp_raw = (b[4] << 4) | (b[5] >> 4); | |
| float tempc = (temp_raw - 1000) * 0.1f; | |
| int probe = b[5] & 3; | |
| int humidity = ((b[6] & 0x1f) << 2) | (b[7] >> 6); | |
| // Water probe (detects water leak) | |
| int water = (b[7] & 0x0f) == 15; // valid only if (probe == 1) | |
| // Soil probe (detects temperature) | |
| int ptemp_raw = ((b[7] & 0x0f) << 8) | (b[8]); // valid only if (probe == 2 || probe == 3) | |
| float ptempc = (ptemp_raw - 1000) * 0.1f; | |
| // Spot probe (detects temperature and humidity) | |
| int phumidity = b[9] & 0x7f; // valid only if (probe == 3) | |
| /* clang-format off */ | |
| data_t *data = data_make( | |
| "model", "", DATA_STRING, model_flag ? "Acurite-00275rm" : "Acurite-00276rm", | |
| "subtype", "Probe", DATA_INT, probe, | |
| "id", "", DATA_INT, id, | |
| "battery_ok", "Battery", DATA_INT, !battery_low, | |
| "temperature_C", "Celsius", DATA_FORMAT, "%.1f C", DATA_DOUBLE, tempc, | |
| "humidity", "Humidity", DATA_FORMAT, "%u %%", DATA_INT, humidity, | |
| "water", "", DATA_COND, probe == 1, DATA_INT, water, | |
| "temperature_1_C", "Celsius", DATA_COND, probe == 2, DATA_FORMAT, "%.1f C", DATA_DOUBLE, ptempc, | |
| "temperature_1_C", "Celsius", DATA_COND, probe == 3, DATA_FORMAT, "%.1f C", DATA_DOUBLE, ptempc, | |
| "humidity_1", "Humidity", DATA_COND, probe == 3, DATA_FORMAT, "%u %%", DATA_INT, phumidity, | |
| "mic", "Integrity", DATA_STRING, "CRC", | |
| NULL); | |
| /* clang-format on */ | |
| decoder_output_data(decoder, data); | |
| return 1; | |
| } | |
| // Only returns the latest result, but better than nothing. | |
| return result; | |
| } | |
| static char *acurite_rain_gauge_output_fields[] = { | |
| "model", | |
| "id", | |
| "rain_mm", | |
| NULL, | |
| }; | |
| r_device acurite_rain_896 = { | |
| .name = "Acurite 896 Rain Gauge", | |
| .modulation = OOK_PULSE_PPM, | |
| .short_width = 1000, | |
| .long_width = 2000, | |
| .gap_limit = 3500, | |
| .reset_limit = 5000, | |
| .decode_fn = &acurite_rain_896_decode, | |
| .disabled = 1, // Disabled by default due to false positives on oregon scientific v1 protocol see issue #353 | |
| .fields = acurite_rain_gauge_output_fields, | |
| }; | |
| static char *acurite_th_output_fields[] = { | |
| "model", | |
| "id", | |
| "battery_ok", | |
| "temperature_C", | |
| "humidity", | |
| "status", | |
| "mic", | |
| NULL, | |
| }; | |
| r_device acurite_th = { | |
| .name = "Acurite 609TXC Temperature and Humidity Sensor", | |
| .modulation = OOK_PULSE_PPM, | |
| .short_width = 1000, | |
| .long_width = 2000, | |
| .gap_limit = 3000, | |
| .reset_limit = 10000, | |
| .decode_fn = &acurite_th_decode, | |
| .fields = acurite_th_output_fields, | |
| }; | |
| /* | |
| * For Acurite 592 TXR Temp/Humidity, but | |
| * Should match Acurite 592TX, 5-n-1, etc. | |
| */ | |
| static char *acurite_txr_output_fields[] = { | |
| "model", | |
| "message_type", // TODO: remove this | |
| "id", | |
| "channel", | |
| "sequence_num", | |
| "battery_ok", | |
| "leak_detected", | |
| "temperature_C", | |
| "temperature_F", | |
| "humidity", | |
| "wind_avg_mi_h", | |
| "wind_avg_km_h", | |
| "wind_dir_deg", | |
| "rain_in", | |
| "rain_mm", | |
| "storm_dist", | |
| "strike_count", | |
| "strike_distance", | |
| "uv", | |
| "lux", | |
| "active", | |
| "exception", | |
| "raw_msg", | |
| "rfi", | |
| "mic", | |
| NULL, | |
| }; | |
| r_device acurite_txr = { | |
| .name = "Acurite 592TXR Temp/Humidity, 5n1 Weather Station, 6045 Lightning, 899 Rain, 3N1, Atlas", | |
| .modulation = OOK_PULSE_PWM, | |
| .short_width = 220, // short pulse is 220 us + 392 us gap | |
| .long_width = 408, // long pulse is 408 us + 204 us gap | |
| .sync_width = 620, // sync pulse is 620 us + 596 us gap | |
| .gap_limit = 500, // longest data gap is 392 us, sync gap is 596 us | |
| .reset_limit = 4000, // packet gap is 2192 us | |
| .decode_fn = &acurite_txr_callback, | |
| .fields = acurite_txr_output_fields, | |
| }; | |
| /* | |
| * Acurite 00986 Refrigerator / Freezer Thermometer | |
| * | |
| * Temperature only, Pulse Position | |
| * | |
| * A preamble: 2x of 216 us pulse + 276 us gap, 4x of 1600 us pulse + 1560 us gap | |
| * 39 bits of data: 220 us pulses with short gap of 520 us or long gap of 880 us | |
| * A transmission consists of two packets that run into each other. | |
| * There should be 40 bits of data though. But the last bit can't be detected. | |
| */ | |
| static char *acurite_986_output_fields[] = { | |
| "model", | |
| "id", | |
| "channel", | |
| "battery_ok", | |
| "temperature_F", | |
| "status", | |
| "mic", | |
| NULL, | |
| }; | |
| r_device acurite_986 = { | |
| .name = "Acurite 986 Refrigerator / Freezer Thermometer", | |
| .modulation = OOK_PULSE_PPM, | |
| .short_width = 520, | |
| .long_width = 880, | |
| .gap_limit = 1280, | |
| .reset_limit = 4000, | |
| .decode_fn = &acurite_986_decode, | |
| .fields = acurite_986_output_fields, | |
| }; | |
| /* | |
| * Acurite 00606TX Tower Sensor | |
| * | |
| * Temperature only | |
| * | |
| */ | |
| static char *acurite_606_output_fields[] = { | |
| "model", | |
| "id", | |
| "battery_ok", | |
| "temperature_C", | |
| "mic", | |
| NULL, | |
| }; | |
| static char *acurite_590_output_fields[] = { | |
| "model", | |
| "id", | |
| "battery_ok", | |
| "channel", | |
| "temperature_C", | |
| "humidity", | |
| "mic", | |
| NULL, | |
| }; | |
| // actually tests/acurite/02/gfile002.cu8, check this | |
| //.modulation = OOK_PULSE_PWM, | |
| //.short_width = 576, | |
| //.long_width = 1076, | |
| //.gap_limit = 1200, | |
| //.reset_limit = 12000, | |
| r_device acurite_606 = { | |
| .name = "Acurite 606TX Temperature Sensor", | |
| .modulation = OOK_PULSE_PPM, | |
| .short_width = 2000, | |
| .long_width = 4000, | |
| .gap_limit = 7000, | |
| .reset_limit = 10000, | |
| .decode_fn = &acurite_606_decode, | |
| .fields = acurite_606_output_fields, | |
| }; | |
| static char *acurite_00275rm_output_fields[] = { | |
| "model", | |
| "subtype", | |
| "id", | |
| "battery_ok", | |
| "temperature_C", | |
| "humidity", | |
| "water", | |
| "temperature_1_C", | |
| "humidity_1", | |
| "mic", | |
| NULL, | |
| }; | |
| r_device acurite_00275rm = { | |
| .name = "Acurite 00275rm,00276rm Temp/Humidity with optional probe", | |
| .modulation = OOK_PULSE_PWM, | |
| .short_width = 232, // short pulse is 232 us | |
| .long_width = 420, // long pulse is 420 us | |
| .gap_limit = 520, // long gap is 384 us, sync gap is 592 us | |
| .reset_limit = 708, // no packet gap, sync gap is 592 us | |
| .sync_width = 632, // sync pulse is 632 us | |
| .decode_fn = &acurite_00275rm_decode, | |
| .fields = acurite_00275rm_output_fields, | |
| }; | |
| r_device acurite_590tx = { | |
| .name = "Acurite 590TX Temperature with optional Humidity", | |
| .modulation = OOK_PULSE_PPM, // OOK_PULSE_PWM, | |
| .short_width = 500, // short pulse is 232 us | |
| .long_width = 1500, // long pulse is 420 us | |
| .gap_limit = 1484, // long gap is 384 us, sync gap is 592 us | |
| .reset_limit = 3000, // no packet gap, sync gap is 592 us | |
| .sync_width = 500, // sync pulse is 632 us | |
| .decode_fn = &acurite_590tx_decode, | |
| .fields = acurite_590_output_fields, | |
| }; |