2 * LaCrosse/StarMétéo/Conrad TX35DTH-IT, TX29-IT, Temperature and Humidity Sensors.
8 Example Data (gfile-tx29.data : https://github.com/merbanan/rtl_433_tests/tree/master/tests/lacrosse/06)
9 a a 2 d d 4 9 2 8 4 4 8 6 a e c
11 1010 1010 0010 1101 1101 0100 1001 0010 1000 0100 0100 1000 0110 1010 1110 1100
13 ----1---- ----2---- ----3---- ----4---- ----5---- ----6---- ----7---- ----8----
15 preamble, always "0xaa"
16 ~~~~~~~~~~~~~~~~~~~ bytes 2 and 3
17 brand identifier, always 0x2dd4
18 ~~~~ 1st nibble of bytes 4
19 datalength (always 9) in nibble, including this field and crc
20 ~~~~ ~~ 2nd nibble of bytes 4 and 1st and 2nd bits of byte 5
21 Random device id (6 bits)
26 ~~~~ ~~~~ ~~~~ 2nd nibble of byte 5 and byte 6
27 temperature, in bcd *10 +40
30 ~~~ ~~~~ 2-8 bits of byte 7
31 humidity, in%. If == 0x6a : no humidity sensor
38 I have noticed that depending of the device, the message received has different length.
39 It seems some sensor send a long preamble (33 bits, 0 / 1 alternated), and some send only
40 one byte as the preamble. I own 3 sensors TX29, and two of them send a long preamble.
41 So this decoder synchronize on the 0xaa 0x2d 0xd4 preamble, so many 0xaa can occurs before.
42 Also, I added 0x9 in the preamble (the weather data length), because this decoder only handle
44 TX29 and TX35 share the same protocol, but pulse are different length, thus this decoder
45 handle the two signal and we use two r_device struct (only differing by the pulse width)
47 How to make a decoder : https://enavarro.me/ajouter-un-decodeur-ask-a-rtl_433.html
54 #define LACROSSE_TX29_NOHUMIDSENSOR 0x6a // Sensor do not support humidty
55 #define LACROSSE_TX35_CRC_POLY 0x31
56 #define LACROSSE_TX35_CRC_INIT 0x00
57 #define LACROSSE_TX29_MODEL 29 // Model number
58 #define LACROSSE_TX35_MODEL 35
61 ** Generic decoder for LaCrosse "IT+" (instant transmission) protocol
62 ** Param device29or35 contain "29" or "35" depending of the device.
64 static int lacrosse_it(bitbuffer_t *bitbuffer, uint8_t device29or35) {
65 char time_str[LOCAL_TIME_BUFLEN];
67 uint16_t brow, row_nbytes;
68 uint8_t msg_type, r_crc, c_crc;
69 uint8_t sensor_id, newbatt, battery_low;
70 uint8_t humidity; // in %. If > 100 : no humidity sensor
71 float temp_c; // in °C
76 static const uint8_t preamble[] = {
78 0x2d, // brand identifer
79 0xd4, // brand identifier
80 0x90, // data length (this decoder work only with data length of 9, so we hardcode it on the preamble)
83 uint8_t out[8] = {0}; // array of byte to decode
84 local_time_str(0, time_str);
85 for (brow = 0; brow < bitbuffer->num_rows; ++brow) {
86 bb = bitbuffer->bb[brow];
88 // Validate message and reject it as fast as possible : check for preamble
89 unsigned int start_pos = bitbuffer_search(bitbuffer, brow, 0, preamble, 28);
90 if(start_pos == bitbuffer->bits_per_row[brow])
91 continue; // no preamble detected, move to the next row
92 if (debug_output >= 1)
93 fprintf(stderr, "LaCrosse TX29/35 detected, buffer is %d bits length, device is TX%d\n", bitbuffer->bits_per_row[brow], device29or35);
94 // remove preamble and keep only 64 bits
95 bitbuffer_extract_bytes(bitbuffer, brow, start_pos, out, 64);
98 * Check message integrity (CRC/Checksum/parity)
99 * Normally, it is computed on the whole message, from byte 0 (preamble) to byte 6,
100 * but preamble is always the same, so we can speed the process by doing a crc check
101 * only on byte 3,4,5,6
104 c_crc = crc8(&out[3], 4, LACROSSE_TX35_CRC_POLY, LACROSSE_TX35_CRC_INIT);
105 if (r_crc != c_crc) {
106 // example debugging output
107 if (debug_output >= 1)
108 fprintf(stderr, "%s LaCrosse TX29/35 bad CRC: calculated %02x, received %02x\n",
109 time_str, c_crc, r_crc);
115 * Now that message "envelope" has been validated,
116 * start parsing data.
118 sensor_id = ((out[3] & 0x0f) << 2) | ((out[4]>>6) & 0x03);
119 temp_c = 10.0 * (out[4] & 0x0f) + 1.0 *((out[5]>>4) & 0x0f) + 0.1 * (out[5] & 0x0f) - 40.0;
120 newbatt = (out[4] >> 5) & 1;
121 battery_low = (out[6]>>7) & 1;
122 humidity = 1 * (out[6] & 0x7F); // Bit 1 to 7 of byte 6
123 if (humidity == LACROSSE_TX29_NOHUMIDSENSOR) {
124 data = data_make("time", "", DATA_STRING, time_str,
125 "brand", "", DATA_STRING, "LaCrosse",
126 "model", "", DATA_STRING, (device29or35 == 29 ? "TX29-IT" : "TX35DTH-IT"),
127 "id", "", DATA_INT, sensor_id,
128 "battery", "Battery", DATA_STRING, battery_low ? "LOW" : "OK",
129 "newbattery", "NewBattery", DATA_INT, newbatt,
130 "temperature_C", "Temperature", DATA_FORMAT, "%.1f C", DATA_DOUBLE, temp_c,
131 "crc", "CRC", DATA_STRING, "OK",
135 data = data_make("time", "", DATA_STRING, time_str,
136 "brand", "", DATA_STRING, "LaCrosse",
137 "model", "", DATA_STRING, (device29or35 == 29 ? "TX29-IT" : "TX35DTH-IT"),
138 "id", "", DATA_INT, sensor_id,
139 "battery", "Battery", DATA_STRING, battery_low ? "LOW" : "OK",
140 "newbattery", "NewBattery", DATA_INT, newbatt,
141 "temperature_C", "Temperature", DATA_FORMAT, "%.1f C", DATA_DOUBLE, temp_c,
142 "humidity", "Humidity", DATA_FORMAT, "%u %%", DATA_INT, humidity,
143 "crc", "CRC", DATA_STRING, "OK",
146 // humidity = -1; // The TX29-IT sensor do not have humidity. It is replaced by a special value
149 data_acquired_handler(data);
156 ** Wrapper for the TX29 device
158 static int lacrossetx29_callback(bitbuffer_t *bitbuffer) {
159 return lacrosse_it(bitbuffer, LACROSSE_TX29_MODEL);
163 ** Wrapper for the TX35 device
165 static int lacrossetx35_callback(bitbuffer_t *bitbuffer) {
166 return lacrosse_it(bitbuffer, LACROSSE_TX35_MODEL);
169 static char *output_fields[] = {
183 // Receiver for the TX29 device
184 r_device lacrosse_tx29 = {
185 .name = "LaCrosse TX29IT Temperature sensor",
186 .modulation = FSK_PULSE_PCM,
190 .json_callback = &lacrossetx29_callback,
193 .fields = output_fields,
196 // Receiver for the TX35 device
197 r_device lacrosse_tx35 = {
198 .name = "LaCrosse TX35DTH-IT Temperature sensor",
199 .modulation = FSK_PULSE_PCM,
203 .json_callback = &lacrossetx35_callback,
206 .fields = output_fields,