1 /* LaCrosse Color Forecast Station (model C85845), or other LaCrosse product
2 * utilizing the remote temperature/humidity sensor TX141TH-Bv2 transmitting
3 * in the 433.92 MHz band. Product pages:
4 * http://www.lacrossetechnology.com/c85845-color-weather-station/
5 * http://www.lacrossetechnology.com/tx141th-bv2-temperature-humidity-sensor
7 * The TX141TH-Bv2 protocol is OOK modulated PWM with fixed period of 625 us
8 * for data bits, preambled by four long startbit pulses of fixed period equal
9 * to ~1666 us. Hence, it is similar to Bresser Thermo-/Hygro-Sensor 3CH (bresser_3ch.c
10 * included in this source code) with the exception that OOK_PULSE_PWM_TERNARY
11 * modulation type is technically more correct than OOK_PULSE_PWM_RAW.
13 * A single data packet looks as follows:
14 * 1) preamble - 833 us high followed by 833 us low, repeated 4 times:
18 * 2) a train of 40 data pulses with fixed 625 us period follows immediately:
19 * --- -- -- --- --- -- ---
20 * | | | | | | | | | | | | | |
21 * -- --- --- -- -- --- -- ....
22 * A logical 1 is 417 us of high followed by 208 us of low.
23 * A logical 0 is 208 us of high followed by 417 us of low.
24 * Thus, in the pictorial example above the bits are 1 0 0 1 1 0 1 ....
26 * The TX141TH-Bv2 sensor sends 12 of identical packets, one immediately following
27 * the other, in a single burst. These 12-packet bursts repeat every 50 seconds. At
28 * the end of the last packet there are two 833 us pulses ("post-amble"?).
30 * The data is grouped in 5 bytes / 10 nybbles
31 * [id] [id] [flags] [temp] [temp] [temp] [humi] [humi] [chk] [chk]
33 * The "id" is an 8 bit random integer generated when the sensor powers up for the
34 * first time; "flags" are 4 bits for battery low indicator, test button press,
35 * and channel; "temp" is 12 bit unsigned integer which encodes temperature in degrees
37 * temp_c = temp/10 - 50
38 * to account for the -40 C -- 60 C range; "humi" is 8 bit integer indicating
39 * relative humidity in %. The method of calculating "chk", the presumed 8-bit checksum
40 * remains a complete mystery at the moment of this writing, and I am not totally sure
41 * if the last is any kind of CRC. I've run reveng 1.4.4 on exemplary data with all
42 * available CRC algorithms and found no match. Be my guest if you want to
43 * solve it - for example, if you figure out why the following two pairs have identical
44 * checksums you'll become a hero:
46 * 0x87 0x02 0x3c 0x3b 0xe1
47 * 0x87 0x02 0x7d 0x37 0xe1
49 * 0x87 0x01 0xc3 0x31 0xd8
50 * 0x87 0x02 0x28 0x37 0xd8
52 * Developer's comment 1: because of our choice of the OOK_PULSE_PWM_TERNARY type, the input
53 * array of bits will look like this:
54 * bitbuffer:: Number of rows: 25
59 * [04] {40} 87 02 67 39 f6
63 * [08] {40} 87 02 67 39 f6
67 * [12] {40} 87 02 67 39 f6
71 * [16] {40} 87 02 67 39 f6
75 * [20] {40} 87 02 67 39 f6
79 * [24] {280} 87 02 67 39 f6 87 02 67 39 f6 87 02 67 39 f6 87 02 67 39 f6 87 02 67 39 f6 87 02 67 39 f6 87 02 67 39 f6
80 * which is a direct consequence of two factors: (1) pulse_demod_pwm_ternary() always assuming
81 * only one startbit, and (2) bitbuffer_add_row() not adding rows beyond BITBUF_ROWS. This is
82 * OK because the data is clearly processable and the unique pattern minimizes the chance of
83 * confusion with other sensors, particularly Bresser 3CH.
85 * Developer's comment 2: with unknown CRC (see above) the obvious way of checking the data
86 * integrity is making use of the 12 packet repetition. In principle, transmission errors are
87 * be relatively rare, thus the most frequent packet (statistical mode) should represent
88 * the true data. Therefore, in the fisrt part of the callback routine the mode is determined
89 * for the first 4 bytes of the data compressed into a single 32-bit integer. Since the packet
90 * count is small, no sophisticated mode algorithm is necessary; a simple array of <data,count>
91 * structures is sufficient. The added bonus is that relative count enables us to determine
92 * the quality of radio transmission.
94 * Copyright (C) 2017 Robert Fraczkiewicz (aromring@gmail.com)
95 * This program is free software; you can redistribute it and/or modify
96 * it under the terms of the GNU General Public License as published by
97 * the Free Software Foundation; either version 2 of the License, or
98 * (at your option) any later version.
105 #define LACROSSE_TX141TH_BITLEN 40
106 #define LACROSSE_TX141TH_BYTELEN 5 // = LACROSSE_TX141TH_BITLEN / 8
107 #define LACROSSE_TX141TH_PACKETCOUNT 12
110 int32_t data; // First 4 data bytes compressed into 32-bit integer
111 int8_t count; // Count
114 static int lacrosse_tx141th_bv2_callback(bitbuffer_t *bitbuffer) {
115 bitrow_t *bb = bitbuffer->bb;
117 char time_str[LOCAL_TIME_BUFLEN];
118 local_time_str(0, time_str);
119 int i,j,k,nbytes,npacket,kmax;
120 uint8_t id=0,status=0,battery_low=0,test=0,humidity=0,maxcount;
123 data_and_count dnc[LACROSSE_TX141TH_PACKETCOUNT] = {0};
126 bitbuffer_print(bitbuffer);
129 npacket=0; // Number of unique packets
130 for(i=0; i<BITBUF_ROWS; ++i) {
131 j=bitbuffer->bits_per_row[i];
132 if(j>=LACROSSE_TX141TH_BITLEN) {
134 for(j=0;j<nbytes;j+=LACROSSE_TX141TH_BYTELEN) {
135 uint32_t *d=(uint32_t *)(bb[i]+j);
137 for(k=0;k<npacket;++k) {
138 if(*d==dnc[k].data) {
145 dnc[npacket].data=*d;
146 dnc[npacket].count=1;
147 if(npacket+1<LACROSSE_TX141TH_PACKETCOUNT) ++npacket;
154 fprintf(stderr, "%d unique packet(s)\n", npacket);
155 for(k=0;k<npacket;++k) {
156 fprintf(stderr, "%08x \t %d \n", dnc[k].data,dnc[k].count);
160 // Find the most frequent data packet, if necessary
164 for(k=0;k<npacket;++k) {
165 if(dnc[k].count>maxcount) {
166 maxcount=dnc[k].count;
172 // Unpack the data bytes back to eliminate dependence on the platform endiannes!
173 uint8_t *bytes=(uint8_t*)(&(dnc[kmax].data));
176 battery_low=(status & 0x80) >> 7;
177 test=(status & 0x40) >> 6;
178 temp_raw=((status & 0x0F) << 8) + bytes[2];
179 temp_f = 9.0*((float)temp_raw)/50.0-58.0; // Temperature in F
182 if (0==id || 0==humidity || humidity > 100 || temp_f < -40.0 || temp_f > 140.0) {
184 fprintf(stderr, "LaCrosse TX141TH-Bv2 data error\n");
189 data = data_make("time", "Date and time", DATA_STRING, time_str,
190 "temperature", "Temperature in deg F", DATA_FORMAT, "%.2f F", DATA_DOUBLE, temp_f,
191 "humidity", "Humidity", DATA_FORMAT, "%u %%", DATA_INT, humidity,
192 "id", "Sensor ID", DATA_FORMAT, "%02x", DATA_INT, id,
193 "model", "", DATA_STRING, "LaCrosse TX141TH-Bv2 sensor",
194 "battery", "Battery", DATA_STRING, battery_low ? "LOW" : "OK",
195 "test", "Test?", DATA_STRING, test ? "Yes" : "No",
197 data_acquired_handler(data);
203 static char *output_fields[] = {
214 r_device lacrosse_TX141TH_Bv2 = {
215 .name = "LaCrosse TX141TH-Bv2 sensor",
216 .modulation = OOK_PULSE_PWM_TERNARY,
217 .short_limit = 312, // short pulse is ~208 us, long pulse is ~417 us
218 .long_limit = 625, // long gap (with short pulse) is ~417 us, sync gap is ~833 us
219 .reset_limit = 1500, // maximum gap is 1250 us (long gap + longer sync gap on last repeat)
220 .json_callback = &lacrosse_tx141th_bv2_callback,
222 .demod_arg = 2, // Longest pulses are startbits
223 .fields = output_fields,