1505 lines
58 KiB
C
1505 lines
58 KiB
C
/** @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:
|
|
- 5-n-1 weather sensor, Model; VN1TXC, 06004RM
|
|
- 5-n-1 pro weather sensor, Model: 06014RM
|
|
- 896 Rain gauge, Model: 00896
|
|
- 592TXR / 06002RM Tower sensor (temperature and humidity)
|
|
(Note: Some newer sensors share the 592TXR coding for compatibility.
|
|
- 609TXC "TH" temperature and humidity sensor (609A1TX)
|
|
- Acurite 986 Refrigerator / Freezer Thermometer
|
|
- Acurite 606TX temperature sensor
|
|
- Acurite 6045M Lightning Detector (Work in Progress)
|
|
- Acurite 00275rm and 00276rm temp. and humidity with optional probe.
|
|
*/
|
|
|
|
#include "decoder.h"
|
|
|
|
// ** Acurite 5n1 functions **
|
|
|
|
#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
|
|
|
|
// ** Acurite known message types
|
|
//#define ACURITE_MSGTYPE_TOWER_SENSOR 0x04
|
|
#define ACURITE_MSGTYPE_515_REFRIGERATOR 0x08
|
|
#define ACURITE_MSGTYPE_515_FREEZER 0x09
|
|
#define ACURITE_MSGTYPE_6045M 0x2f
|
|
#define ACURITE_MSGTYPE_5N1_WINDSPEED_WINDDIR_RAINFALL 0x31
|
|
#define ACURITE_MSGTYPE_5N1_WINDSPEED_TEMP_HUMIDITY 0x38
|
|
#define ACURITE_MSGTYPE_3N1_WINDSPEED_TEMP_HUMIDITY 0x20
|
|
#define ACURITE_MSGTYPE_RAINFALL 0x30
|
|
|
|
#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_ATLAS_WNDSPD_TEMP_HUM_LTNG 0x25
|
|
#define ACURITE_MSGTYPE_ATLAS_WNDSPD_RAIN_LTNG 0x26
|
|
#define ACURITE_MSGTYPE_ATLAS_WNDSPD_UV_LUX_LTNG 0x27
|
|
|
|
// 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];
|
|
}
|
|
|
|
static char const *acurite_getChannelAndType(uint8_t byte, uint8_t mtype)
|
|
{
|
|
static char const *channel_strs[] = {"CR", "ER", "BR", "AR", "CF", "EF", "BF", "AF"}; // 'E' stands for error
|
|
|
|
int channel = ((mtype & 0x01) << 2) | ((byte & 0xC0) >> 6);
|
|
return channel_strs[channel];
|
|
}
|
|
|
|
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
|
|
|
|
if (decoder->verbose > 1) {
|
|
fprintf(stderr, "%s: Total Rain is %2.1fmm\n", __func__, total_rain);
|
|
bitrow_printf(b, bitbuffer->bits_per_row[0], "%s: Raw Message ", __func__);
|
|
}
|
|
|
|
/* 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];
|
|
|
|
/* 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 - check parity on bytes 2 - 7
|
|
@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;
|
|
// uint8_t message_type, l_status;
|
|
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
|
|
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: -140.0 F to +261.5 F
|
|
int temp_raw = ((bb[4] & 0x1F) << 7) | (bb[5] & 0x7F);
|
|
tempf = (temp_raw - 1480) * 0.1f;
|
|
|
|
// 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;
|
|
//l_status = (bb[7] & 0x60) >> 5;
|
|
|
|
/*
|
|
* 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
|
|
(humidity > 100) ||
|
|
(tempf > 158) ||
|
|
(tempf < -40)) {
|
|
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, // @todo convert to bool
|
|
"rfi", "rfi_detect", DATA_INT, rfi_detect, // @todo convert to bool
|
|
"exception", "data_exception", DATA_INT, exception, // @todo convert to bool
|
|
"raw_msg", "raw_message", DATA_STRING, raw_str,
|
|
NULL);
|
|
/* clang-format on */
|
|
|
|
decoder_output_data(decoder, data);
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
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];
|
|
|
|
// {80} 82 f3 65 00 88 72 22 00 9f 95 {80} 86 f3 65 00 88 72 22 00 9f 99 {80} 8a f3 65 00 88 72 22 00 9f 9d
|
|
// {80} 82 f3 66 00 05 e4 81 00 9f e4 {80} 86 f3 66 00 05 e4 81 00 9f e8 {80} 8a f3 66 00 05 e4 81 00 9f ec
|
|
// {80} 82 f3 e7 00 00 00 96 00 9f 91 {80} 86 f3 e7 00 00 00 96 00 9f 95 {80} 8a f3 e7 00 00 00 96 00 9f 99
|
|
// {80} 82 f3 66 00 05 60 81 00 9f 60 {80} 86 f3 66 00 05 60 81 00 9f 64 {80} 8a f3 66 00 05 60 81 00 9f 68
|
|
// {80} 82 f3 65 00 88 71 24 00 9f 96 {80} 86 f3 65 00 88 71 24 00 9f 9a {80} 8a f3 65 00 88 71 24 00 9f 9e
|
|
// {80} 82 f3 65 00 88 71 a5 00 9f 17 {80} 86 f3 65 00 88 71 a5 00 9f 1b {80} 8a f3 65 00 88 71 a5 00 9f 1f
|
|
|
|
// bitrow_printf(bb, bitbuffer->bits_per_row[brow], "%s: Acurite Atlas raw msg: ", __func__);
|
|
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
|
|
wind_speed_mph = ((bb[3] & 0x7F) << 1) | ((bb[4] & 0x40) >> 6);
|
|
|
|
/* 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
|
|
|
|
// range -40 to 160 F
|
|
// TODO: are there really 13 bits? use 11 for now.
|
|
int temp_raw = (bb[4] & 0x0F) << 7 | (bb[5] & 0x7F);
|
|
tempf = (temp_raw - 400) * 0.1;
|
|
|
|
humidity = (bb[6] & 0x7f); // 1-99 %rH
|
|
|
|
/* 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_dir = ((bb[4] & 0x1f) << 5) | ((bb[5] & 0x7c) >> 2);
|
|
|
|
// 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, Lightning?
|
|
int uv = (bb[4] & 0x0f);
|
|
int lux = ((bb[5] & 0x7f) << 7) | (bb[6] & 0x7F);
|
|
|
|
/* 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 */
|
|
}
|
|
|
|
data = data_append(data,
|
|
"exception", "data_exception", DATA_INT, exception, // @todo convert to bool
|
|
"raw_msg", "raw_message", DATA_STRING, raw_str,
|
|
NULL);
|
|
|
|
decoder_output_data(decoder, data);
|
|
|
|
return 1; // one valid message decoded
|
|
}
|
|
|
|
/**
|
|
This callback handles several Acurite devices that use a very
|
|
similar RF encoding and data format:
|
|
|
|
- 592TXR temperature and humidity sensor
|
|
- 5-n-1 weather station
|
|
- 6045M Lightning Detector with Temperature and Humidity
|
|
- Atlas
|
|
|
|
CC RR IIII | IIII IIII | pBMMMMMM | pxxWWWWW | pWWWTTTT | pTTTTTTT | pSSSSSSS
|
|
C:2d R:2d ID:12d 1x BATT:1b TYPE:6h 1x ?2b W:5b 1x 3b T:4b 1x 7b S: 1x 7d
|
|
|
|
@todo - refactor, move 5n1 and txr decoding into separate functions.
|
|
@todo - TBD Are parity and checksum the same across these devices?
|
|
(opportunity to DRY-up and simplify?)
|
|
|
|
*/
|
|
static int acurite_txr_decode(r_device *decoder, bitbuffer_t *bitbuffer)
|
|
{
|
|
int browlen, valid = 0;
|
|
uint8_t *bb;
|
|
float tempc, tempf, wind_dir, wind_speed_kph, wind_speed_mph;
|
|
uint8_t humidity, sequence_num, message_type;
|
|
// uint8_t sensor_status;
|
|
uint16_t sensor_id;
|
|
int raincounter, battery_low;
|
|
data_t *data;
|
|
|
|
bitbuffer_invert(bitbuffer);
|
|
|
|
for (uint16_t brow = 0; brow < bitbuffer->num_rows; ++brow) {
|
|
browlen = (bitbuffer->bits_per_row[brow] + 7)/8;
|
|
bb = bitbuffer->bb[brow];
|
|
|
|
if (decoder->verbose > 1)
|
|
fprintf(stderr, "%s: row %u bits %u, bytes %d \n", __func__, brow, bitbuffer->bits_per_row[brow], browlen);
|
|
|
|
if ((bitbuffer->bits_per_row[brow] < ACURITE_TXR_BITLEN ||
|
|
bitbuffer->bits_per_row[brow] > ACURITE_5N1_BITLEN + 1)
|
|
&& bitbuffer->bits_per_row[brow] != ACURITE_6045_BITLEN
|
|
&& bitbuffer->bits_per_row[brow] != ACURITE_ATLAS_BITLEN
|
|
&& bitbuffer->bits_per_row[brow] != ACURITE_515_BITLEN) {
|
|
if (decoder->verbose > 1 && bitbuffer->bits_per_row[brow] > 16)
|
|
fprintf(stderr, "%s: skipping wrong len\n", __func__);
|
|
continue; // DECODE_ABORT_LENGTH
|
|
}
|
|
|
|
// There will be 1 extra false zero bit added by the demod.
|
|
// this forces an extra zero byte to be added
|
|
if (bb[browlen - 1] == 0)
|
|
browlen--;
|
|
|
|
// sum of first n-1 bytes modulo 256 should equal nth byte
|
|
// also disregard a row of all zeros
|
|
int sum = add_bytes(bb, browlen - 1);
|
|
if (sum == 0 || (sum & 0xff) != bb[browlen - 1]) {
|
|
if (decoder->verbose)
|
|
bitrow_printf(bb, bitbuffer->bits_per_row[brow], "%s: bad checksum: ", __func__);
|
|
continue; // DECODE_FAIL_MIC
|
|
}
|
|
|
|
if (decoder->verbose) {
|
|
fprintf(stderr, "%s: Parity: ", __func__);
|
|
for (int i = 0; i < browlen; i++) {
|
|
fprintf(stderr, "%d", parity8(bb[i]));
|
|
}
|
|
fprintf(stderr, "\n");
|
|
}
|
|
|
|
// acurite sensors with a common format appear to 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;
|
|
|
|
// tower sensor messages are 7 bytes.
|
|
// TODO: - see if there is a type in the message that
|
|
// can be used instead of length to determine type
|
|
if (browlen == ACURITE_TXR_BITLEN / 8) {
|
|
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
|
|
sensor_id = ((bb[0] & 0x3f) << 8) | bb[1];
|
|
//sensor_status = bb[2]; // TODO:, uses parity? & 0x07f
|
|
humidity = (bb[3] & 0x7f); // 1-99 %rH
|
|
// 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)
|
|
// range -100 C to 1538.4 C
|
|
int temp_raw = ((bb[4] & 0x7F) << 7) | (bb[5] & 0x7F);
|
|
tempc = temp_raw * 0.1 - 100;
|
|
// Battery status is the 7th bit 0x40. 1 = normal, 0 = low
|
|
battery_low = (bb[2] & 0x40) == 0;
|
|
|
|
/* 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 */
|
|
|
|
decoder_output_data(decoder, data);
|
|
valid++;
|
|
}
|
|
|
|
// 515 sensor messages are 6 bytes.
|
|
if (browlen == ACURITE_515_BITLEN / 8) {
|
|
char const *channel_str = acurite_getChannelAndType(bb[0], message_type);
|
|
|
|
// 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.
|
|
sensor_id = ((bb[0] & 0x3f) << 8) | bb[1];
|
|
|
|
// Sensor type (refrigerator, freezer) is determined by the message_type.
|
|
if (message_type != ACURITE_MSGTYPE_515_REFRIGERATOR
|
|
&& message_type != ACURITE_MSGTYPE_515_FREEZER) {
|
|
if (decoder->verbose > 1) {
|
|
fprintf(stderr, "%s: Acurite 515 sensor 0x%04X Ch %s, Unknown message type 0x%02x\n",
|
|
__func__, sensor_id, channel_str, message_type);
|
|
}
|
|
continue; // DECODE_FAIL_MIC
|
|
}
|
|
|
|
// temperature encoding used by 515 sensors
|
|
// 14 bits available after removing both parity bits.
|
|
int temp_raw = ((bb[3] & 0x7F) << 7) | (bb[4] & 0x7F);
|
|
tempf = (temp_raw - 1480) * 0.1f;
|
|
// Battery status is the 7th bit 0x40. 1 = normal, 0 = low
|
|
battery_low = (bb[2] & 0x40) == 0;
|
|
|
|
/* clang-format off */
|
|
data = data_make(
|
|
"model", "", DATA_STRING, "Acurite-515",
|
|
"id", "", 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,
|
|
"mic", "Integrity", DATA_STRING, "CHECKSUM",
|
|
NULL);
|
|
/* clang-format on */
|
|
|
|
decoder_output_data(decoder, data);
|
|
valid++;
|
|
}
|
|
|
|
// The 5-n-1 weather sensor messages are 8 bytes.
|
|
else if (message_type == ACURITE_MSGTYPE_5N1_WINDSPEED_WINDDIR_RAINFALL ||
|
|
message_type == ACURITE_MSGTYPE_5N1_WINDSPEED_TEMP_HUMIDITY ||
|
|
message_type == ACURITE_MSGTYPE_3N1_WINDSPEED_TEMP_HUMIDITY ||
|
|
message_type == ACURITE_MSGTYPE_RAINFALL) {
|
|
if (decoder->verbose)
|
|
bitrow_printf(bb, bitbuffer->bits_per_row[brow], "%s: Acurite 5n1 raw msg: ", __func__);
|
|
char const *channel_str = acurite_getChannel(bb[0]);
|
|
|
|
// 5-n-1 sensor ID is the last 12 bits of byte 0 & 1
|
|
// byte 0 | byte 1
|
|
// CC RR IIII | IIII IIII
|
|
sensor_id = ((bb[0] & 0x0f) << 8) | bb[1];
|
|
// 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 5,4 of byte 0 indicate which copy of the 65 bit data string
|
|
// 00 = first copy
|
|
// 01 = second copy
|
|
// 10 = third copy
|
|
// 1100 xxxx = channel A 1st copy
|
|
// 1101 xxxx = channel A 2nd copy
|
|
// 1110 xxxx = channel A 3rd copy
|
|
sequence_num = (bb[0] & 0x30) >> 4;
|
|
battery_low = (bb[2] & 0x40) == 0;
|
|
|
|
// Only for 5N1, range: 0 to 159 kph
|
|
// raw number is cup rotations per 4 seconds
|
|
// http://www.wxforum.net/index.php?topic=27244.0 (found from weewx driver)
|
|
int speed_raw = ((bb[3] & 0x1F) << 3)| ((bb[4] & 0x70) >> 4);
|
|
wind_speed_kph = 0;
|
|
if (speed_raw > 0) {
|
|
wind_speed_kph = speed_raw * 0.8278 + 1.0;
|
|
}
|
|
|
|
if (message_type == ACURITE_MSGTYPE_5N1_WINDSPEED_WINDDIR_RAINFALL) {
|
|
// Wind speed, wind direction, and rain fall
|
|
wind_dir = acurite_5n1_winddirections[bb[4] & 0x0f] * 22.5f;
|
|
|
|
// range: 0 to 99.99 in, 0.01 inch increments, accumulated
|
|
raincounter = ((bb[5] & 0x7f) << 7) | (bb[6] & 0x7F);
|
|
|
|
/* clang-format off */
|
|
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);
|
|
valid++;
|
|
}
|
|
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);
|
|
tempf = (temp_raw - 400) * 0.1f;
|
|
|
|
humidity = (bb[6] & 0x7f); // 1-99 %rH
|
|
|
|
/* clang-format off */
|
|
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);
|
|
valid++;
|
|
}
|
|
else if (message_type == ACURITE_MSGTYPE_3N1_WINDSPEED_TEMP_HUMIDITY) {
|
|
// Wind speed, temperature and humidity for 3-n-1
|
|
sensor_id = ((bb[0] & 0x3f) << 8) | bb[1]; // 3-n-1 sensor ID is the bottom 14 bits of byte 0 & 1
|
|
humidity = (bb[3] & 0x7f); // 1-99 %rH
|
|
|
|
// note the 3n1 seems to have one more high bit than 5n1
|
|
int temp_raw = (bb[4] & 0x1F) << 7 | (bb[5] & 0x7F);
|
|
tempf = (temp_raw - 1480) * 0.1f; // regression yields (rawtemp-1480)*0.1
|
|
|
|
wind_speed_mph = bb[6] & 0x7f; // seems to be plain MPH
|
|
|
|
/* clang-format off */
|
|
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);
|
|
valid++;
|
|
}
|
|
else if (message_type == ACURITE_MSGTYPE_RAINFALL) {
|
|
// Rain Fall Gauge 899
|
|
// The high 2 bits of byte zero are the channel (bits 7,6), 00 = A, 01 = B, 10 = C
|
|
int channel = bb[0] >> 6;
|
|
raincounter = ((bb[5] & 0x7f) << 7) | (bb[6] & 0x7f); // one tip is 0.01 inch, i.e. 0.254mm
|
|
|
|
/* clang-format off */
|
|
data = data_make(
|
|
"model", "", DATA_STRING, "Acurite-Rain899",
|
|
"id", "", DATA_INT, sensor_id,
|
|
"channel", "", DATA_INT, channel,
|
|
"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);
|
|
valid++;
|
|
}
|
|
else {
|
|
if (decoder->verbose > 1) {
|
|
fprintf(stderr, "%s: Acurite 5n1 sensor 0x%04X Ch %s, Status %02X, Unknown message type 0x%02x\n",
|
|
__func__, sensor_id, channel_str, bb[3], message_type);
|
|
}
|
|
}
|
|
}
|
|
|
|
else if (message_type == ACURITE_MSGTYPE_6045M) {
|
|
// TODO: check parity and reject if invalid
|
|
valid += acurite_6045_decode(decoder, bitbuffer, brow);
|
|
}
|
|
|
|
else if ((message_type == ACURITE_MSGTYPE_ATLAS_WNDSPD_TEMP_HUM ||
|
|
message_type == ACURITE_MSGTYPE_ATLAS_WNDSPD_RAIN ||
|
|
message_type == ACURITE_MSGTYPE_ATLAS_WNDSPD_UV_LUX ||
|
|
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)) {
|
|
valid += acurite_atlas_decode(decoder, bitbuffer, brow);
|
|
}
|
|
}
|
|
|
|
return valid;
|
|
}
|
|
|
|
/**
|
|
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) {
|
|
|
|
if (decoder->verbose > 1)
|
|
fprintf(stderr, "%s: row %u bits %u, bytes %d \n", __func__, brow, bitbuffer->bits_per_row[brow], browlen);
|
|
|
|
if (bitbuffer->bits_per_row[brow] < 39 ||
|
|
bitbuffer->bits_per_row[brow] > 43 ) {
|
|
if (decoder->verbose > 1 && bitbuffer->bits_per_row[brow] > 16)
|
|
fprintf(stderr,"%s: skipping wrong len\n", __func__);
|
|
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]);
|
|
|
|
if (decoder->verbose)
|
|
bitrow_printf(br, browlen * 8, "%s: reversed: ", __func__);
|
|
|
|
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) {
|
|
if (decoder->verbose > 1)
|
|
bitrow_printf(br, browlen * 8, "%s: bad CRC: %02x -", __func__, 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)) {
|
|
if (decoder->verbose > 1)
|
|
fprintf(stderr, "%s: CRC fix %02x - %02x\n", __func__, crc, crcc);
|
|
}
|
|
else {
|
|
continue; // DECODE_FAIL_MIC
|
|
}
|
|
}
|
|
|
|
if (tempf & 0x80) {
|
|
tempf = (tempf & 0x7f) * -1;
|
|
}
|
|
|
|
if (decoder->verbose)
|
|
fprintf(stderr, "%s: sensor 0x%04x - %d%c: %d F\n", __func__, 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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 all blank messages
|
|
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) {
|
|
if (decoder->verbose) {
|
|
fprintf(stderr, "%s: parity check failed\n", __func__);
|
|
}
|
|
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;
|
|
}
|
|
|
|
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) {
|
|
uint8_t *b = bitbuffer->bb[bitbuffer->num_rows];
|
|
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] = 88;
|
|
bitbuffer->num_rows += 1;
|
|
}
|
|
|
|
// 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) {
|
|
if (decoder->verbose)
|
|
bitrow_printf(b, 11 * 8, "%s: sensor bad CRC: ", __func__);
|
|
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);
|
|
float tempc = ((b[4] << 4) | (b[5] >> 4)) * 0.1 - 100;
|
|
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)
|
|
float ptempc = (((b[7] & 0x0f) << 8) | b[8]) * 0.1 - 100; // valid only if (probe == 2 || probe == 3)
|
|
// 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",
|
|
"battery_ok",
|
|
"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, 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_decode,
|
|
.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,
|
|
};
|