Bugfixes
[rtl-433.git] / src / devices / lacrosse_tx35.c
1 /*
2  * LaCrosse/StarMétéo/Conrad TX35DTH-IT, TX29-IT,  Temperature and Humidity Sensors.
3  * Tune to 868240000Hz
4  *
5 *
6 Protocol
7 ========
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
10 Bits :
11 1010 1010 0010 1101 1101 0100 1001 0010 1000 0100 0100 1000 0110 1010 1110 1100
12 Bytes num :
13 ----1---- ----2---- ----3---- ----4---- ----5---- ----6---- ----7---- ----8----
14 ~~~~~~~~~ 1st byte
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)
22                                           ~ 3rd bits of byte 5
23 new battery indicator
24                                            ~ 4th bits of byte 5
25 unkown, unused
26                                              ~~~~ ~~~~ ~~~~ 2nd nibble of byte 5 and byte 6
27 temperature, in bcd *10 +40
28                                                             ~ 1st bit of byte 7
29 weak battery
30                                                              ~~~ ~~~~ 2-8 bits of byte 7
31 humidity, in%. If == 0x6a : no humidity sensor
32                                                                       ~~~~ ~~~~ byte 8
33 crc8 of bytes
34
35
36 Developer's comments
37 ====================
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 
43 this type of message.
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)
46
47 How to make a decoder : https://enavarro.me/ajouter-un-decodeur-ask-a-rtl_433.html
48 */
49
50 #include "rtl_433.h"
51 #include "util.h"
52 #include "data.h"
53
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
59
60 /**
61  ** Generic decoder for LaCrosse "IT+" (instant transmission) protocol
62  ** Param device29or35 contain "29" or "35" depending of the device.
63  **/
64 static int lacrosse_it(bitbuffer_t *bitbuffer, uint8_t device29or35) {
65         char time_str[LOCAL_TIME_BUFLEN];
66         uint8_t *bb;
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
72         int valid = 0;
73         data_t *data;
74         int events = 0; 
75         
76         static const uint8_t preamble[] = {
77           0xaa, // 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)
81         };
82         
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];
87                 
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);
96
97                 /*
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
102                  */             
103                 r_crc = out[7];
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);
110                         // reject row
111                         continue;
112                 }
113
114                 /*
115                  * Now that message "envelope" has been validated,
116                  * start parsing data.
117                  */
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",
132                                                          NULL);
133         }
134         else {
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",
144                                                          NULL);
145         }
146                 //      humidity = -1; // The TX29-IT sensor do not have humidity. It is replaced by a special value
147                 
148
149                 data_acquired_handler(data);
150                 events++;
151         }
152         return events;
153 }
154
155 /**
156  ** Wrapper for the TX29 device
157  **/
158 static int lacrossetx29_callback(bitbuffer_t *bitbuffer) {
159         return lacrosse_it(bitbuffer, LACROSSE_TX29_MODEL);
160 }
161
162 /**
163  ** Wrapper for the TX35 device
164  **/
165 static int lacrossetx35_callback(bitbuffer_t *bitbuffer) {
166         return lacrosse_it(bitbuffer, LACROSSE_TX35_MODEL);
167 }
168
169 static char *output_fields[] = {
170         "time",
171         "brand",
172         "model",
173         "id",
174         "battery",
175         "newbattery",
176         "status",
177         "temperature_C",
178         "humidity",
179         "crc",
180         NULL
181 };
182
183 // Receiver for the TX29 device
184 r_device lacrosse_tx29 = {
185         .name           = "LaCrosse TX29IT Temperature sensor",
186         .modulation     = FSK_PULSE_PCM,
187         .short_limit    = 55,
188         .long_limit     = 55,
189         .reset_limit    = 4000,
190         .json_callback  = &lacrossetx29_callback,
191         .disabled       = 0,
192         .demod_arg      = 0,
193         .fields         = output_fields,
194 };
195
196 // Receiver for the TX35 device
197 r_device lacrosse_tx35 = {
198         .name           = "LaCrosse TX35DTH-IT Temperature sensor",
199         .modulation     = FSK_PULSE_PCM,
200         .short_limit    = 105,
201         .long_limit     = 105,
202         .reset_limit    = 4000,
203         .json_callback  = &lacrossetx35_callback,
204         .disabled       = 0,
205         .demod_arg      = 0,
206         .fields         = output_fields,
207 };