/
lacrosse.c
196 lines (169 loc) · 7.73 KB
/
lacrosse.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
/** @file
LaCrosse TX 433 Mhz Temperature and Humidity Sensors.
Copyright (C) 2015 Robert C. 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.
*/
/**
LaCrosse TX 433 Mhz Temperature and Humidity Sensors.
- Tested: TX-7U and TX-6U (Temperature only)
- Not Tested but should work: TX-3, TX-4
- also TFA Dostmann 30.3120.90 sensor (for e.g. 35.1018.06 (WS-9015) station)
- also TFA Dostmann 30.3121 sensor
Protocol Documentation: http://www.f6fbb.org/domo/sensors/tx3_th.php
Message is 44 bits, 11 x 4 bit nybbles:
[00] [cnt = 10] [type] [addr] [addr + parity] [v1] [v2] [v3] [iv1] [iv2] [check]
Notes:
- Zero Pulses are longer (1400 uS High, 1000 uS Low) = 2400 uS
- One Pulses are shorter (550 uS High, 1000 uS Low) = 1600 uS
- Sensor id changes when the battery is changed
- Primary Value are BCD with one decimal place: vvv = 12.3
- Secondary value is integer only intval = 12, seems to be a repeat of primary
This may actually be an additional data check because the 4 bit checksum
and parity bit is pretty week at detecting errors.
- Temperature is in Celsius with 50.0 added (to handle negative values)
- Humidity values appear to be integer precision, decimal always 0.
- There is a 4 bit checksum and a parity bit covering the three digit value
- Parity check for TX-3 and TX-4 might be different.
- Msg sent with one repeat after 30 mS
- Temperature and humidity are sent as separate messages
- Frequency for each sensor may be could be off by as much as 50-75 khz
- LaCrosse Sensors in other frequency ranges (915 Mhz) use FSK not OOK
so they can't be decoded by rtl_433 currently.
- Temperature and Humidity are sent in different messages bursts.
*/
#include "decoder.h"
#define LACROSSE_TX_BITLEN 44
#define LACROSSE_NYBBLE_CNT 11
static int lacrossetx_decode(r_device *decoder, bitbuffer_t *bitbuffer)
{
int events = 0;
int result = 0;
for (int row = 0; row < bitbuffer->num_rows; ++row) {
// break out the message nybbles into separate bytes
// The LaCrosse protocol is based on 4 bit nybbles.
uint8_t *pRow = bitbuffer->bb[row];
uint8_t msg_nybbles[LACROSSE_NYBBLE_CNT];
int16_t rowlen = bitbuffer->bits_per_row[row];
// Actual Packet should start with 0x0A and be 6 bytes
// actual message is 44 bit, 11 x 4 bit nybbles.
if (rowlen != LACROSSE_TX_BITLEN) {
result = DECODE_ABORT_LENGTH;
continue; // DECODE_ABORT_LENGTH
}
if (pRow[0] != 0x0a) {
result = DECODE_ABORT_EARLY;
continue; // DECODE_ABORT_EARLY
}
for (int i = 0; i < LACROSSE_NYBBLE_CNT; i++) {
msg_nybbles[i] = 0;
}
// Move nybbles into a byte array
// Compute parity and checksum at the same time.
uint8_t parity = 0;
for (int i = 0; i < 44; i++) {
uint8_t rbyte_no = i / 8;
uint8_t rbit_no = 7 - (i % 8);
uint8_t mnybble_no = i / 4;
uint8_t mbit_no = 3 - (i % 4);
uint8_t bit = (pRow[rbyte_no] & (1 << rbit_no)) ? 1 : 0;
msg_nybbles[mnybble_no] |= (bit << mbit_no);
// Check parity on three bytes of data value
// TX3U might calculate parity on all data including
// sensor id and redundant integer data
if (mnybble_no > 4 && mnybble_no < 8) {
parity += bit;
}
//decoder_logf(decoder, 0, __func__, "recv: [%d/%d] %d -> msg [%d/%d] %02x, Parity: %d",
// rbyte_no, rbit_no, bit, mnybble_no, mbit_no, msg_nybbles[mnybble_no], parity);
}
uint8_t parity_bit = msg_nybbles[4] & 0x01;
parity += parity_bit;
// Validate Checksum (4 bits in last nybble)
uint8_t checksum = 0;
for (int i = 0; i < 10; i++) {
checksum = (checksum + msg_nybbles[i]) & 0x0F;
}
// decoder_logf(decoder, 0, __func__,"Parity: %d, parity bit %d, Good %d", parity, parity_bit, parity % 2);
if (checksum != msg_nybbles[10] || (parity % 2 != 0)) {
decoder_logf(decoder, 2, __func__,
"LaCrosse TX Checksum/Parity error: Comp. %d != Recv. %d, Parity %d",
checksum, msg_nybbles[10], parity);
result = DECODE_FAIL_MIC;
continue; // DECODE_FAIL_MIC
}
// TODO: check if message length is a valid value
//uint8_t msg_len = msg_nybbles[1];
uint8_t msg_type = msg_nybbles[2];
uint8_t sensor_id = (msg_nybbles[3] << 3) + (msg_nybbles[4] >> 1);
uint16_t msg_value_raw = (msg_nybbles[5] << 8) | (msg_nybbles[6] << 4) | msg_nybbles[7];
float msg_value = msg_nybbles[5] * 10 + msg_nybbles[6] + msg_nybbles[7] * 0.1f;
int msg_value_int = msg_nybbles[8] * 10 + msg_nybbles[9];
// Check Repeated data values as another way of verifying
// message integrity.
if (msg_nybbles[5] != msg_nybbles[8]
|| msg_nybbles[6] != msg_nybbles[9]) {
decoder_logf(decoder, 1, __func__,
"Sensor %02x, type: %d: message value mismatch int(%.1f) != %d?\n",
sensor_id, msg_type, msg_value, msg_value_int);
result = DECODE_FAIL_SANITY;
continue; // DECODE_FAIL_SANITY
}
if (msg_type == 0x00) {
float temp_c = msg_value - 50.0f;
/* clang-format off */
data_t *data = data_make(
"model", "", DATA_STRING, "LaCrosse-TX",
"id", "", DATA_INT, sensor_id,
"temperature_C", "Temperature", DATA_FORMAT, "%.1f C", DATA_DOUBLE, temp_c,
"mic", "Integrity", DATA_STRING, "PARITY",
NULL);
/* clang-format on */
decoder_output_data(decoder, data);
events++;
}
else if (msg_type == 0x0E) {
/* clang-format off */
data_t *data = data_make(
"model", "", DATA_STRING, "LaCrosse-TX",
"id", "", DATA_INT, sensor_id,
"humidity", "Humidity", DATA_COND, msg_value_raw != 0xff, DATA_FORMAT, "%.1f %%", DATA_DOUBLE, msg_value,
"mic", "Integrity", DATA_STRING, "PARITY",
NULL);
/* clang-format on */
decoder_output_data(decoder, data);
events++;
}
else {
// TODO: this should be reported/counted as exception, not considered debug
decoder_logf(decoder, 1, __func__,
"Sensor %02x: Unknown Reading type %d, % 3.1f (%d)\n",
sensor_id, msg_type, msg_value, msg_value_int);
}
}
if (events) {
return events;
}
return result;
}
static char const *const output_fields[] = {
"model",
"id",
"temperature_C",
"humidity",
"mic",
NULL,
};
r_device const lacrossetx = {
.name = "LaCrosse TX Temperature / Humidity Sensor",
.modulation = OOK_PULSE_PWM,
.short_width = 550, // 550 us pulse + 1000 us gap is 1
.long_width = 1400, // 1400 us pulse + 1000 us gap is 0
.gap_limit = 3000, // max gap is 1000 us
.reset_limit = 8000, // actually: packet gap is 29000 us
.sync_width = 0, // not used
.decode_fn = &lacrossetx_decode,
.fields = output_fields,
};