Bugfixes
[rtl-433.git] / src / devices / lacrosse.c
1 /* LaCrosse TX 433 Mhz Temperature and Humidity Sensors
2  * Tested: TX-7U and TX-6U (Temperature only)
3  *
4  * Not Tested but should work: TX-3, TX-4
5  *
6  * Copyright (C) 2015 Robert C. Terzi
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * Protocol Documentation: http://www.f6fbb.org/domo/sensors/tx3_th.php
14  *
15  * Message is 44 bits, 11 x 4 bit nybbles:
16  *
17  * [00] [cnt = 10] [type] [addr] [addr + parity] [v1] [v2] [v3] [iv1] [iv2] [check]
18  *
19  * Notes:
20  * - Zero Pulses are longer (1,400 uS High, 1,000 uS Low) = 2,400 uS
21  * - One Pulses are shorter (  550 uS High, 1,000 uS Low) = 1,600 uS
22  * - Sensor id changes when the battery is changed
23  * - Primay Value are BCD with one decimal place: vvv = 12.3
24  * - Secondary value is integer only intval = 12, seems to be a repeat of primary
25  *   This may actually be an additional data check because the 4 bit checksum
26  *   and parity bit is  pretty week at detecting errors.
27  * - Temperature is in Celsius with 50.0 added (to handle negative values)
28  * - Humidity values appear to be integer precision, decimal always 0.
29  * - There is a 4 bit checksum and a parity bit covering the three digit value
30  * - Parity check for TX-3 and TX-4 might be different.
31  * - Msg sent with one repeat after 30 mS
32  * - Temperature and humidity are sent as separate messages
33  * - Frequency for each sensor may be could be off by as much as 50-75 khz
34  * - LaCrosse Sensors in other frequency ranges (915 Mhz) use FSK not OOK
35  *   so they can't be decoded by rtl_433 currently.
36  *
37  * TO DO:
38  * - Now that we have a demodulator that isn't stripping the first bit
39  *   the detect and decode could be collapsed into a single reasonably
40  *   readable function.
41  *
42  * - Make the time stamp output a generat utility function.
43  */
44
45 #include "rtl_433.h"
46 #include "util.h"
47 #include "data.h"
48
49 #define LACROSSE_TX_BITLEN      44
50 #define LACROSSE_NYBBLE_CNT     11
51
52
53 // Check for a valid LaCrosse TX Packet
54 //
55 // Return message nybbles broken out into bytes
56 // for clarity.  The LaCrosse protocol is based
57 // on 4 bit nybbles.
58 //
59 // Domodulation
60 // Long bits = 0
61 // short bits = 1
62 //
63 static int lacrossetx_detect(uint8_t *pRow, uint8_t *msg_nybbles, int16_t rowlen) {
64     int i;
65     uint8_t rbyte_no, rbit_no, mnybble_no, mbit_no;
66     uint8_t bit, checksum, parity_bit, parity = 0;
67
68     // Actual Packet should start with 0x0A and be 6 bytes
69     // actual message is 44 bit, 11 x 4 bit nybbles.
70     if (rowlen == LACROSSE_TX_BITLEN && pRow[0] == 0x0a) {
71
72         for (i = 0; i < LACROSSE_NYBBLE_CNT; i++) {
73             msg_nybbles[i] = 0;
74         }
75
76         // Move nybbles into a byte array
77         // Compute parity and checksum at the same time.
78         for (i = 0; i < 44; i++) {
79             rbyte_no = i / 8;
80             rbit_no = 7 - (i % 8);
81             mnybble_no = i / 4;
82             mbit_no = 3 - (i % 4);
83             bit = (pRow[rbyte_no] & (1 << rbit_no)) ? 1 : 0;
84             msg_nybbles[mnybble_no] |= (bit << mbit_no);
85
86             // Check parity on three bytes of data value
87             // TX3U might calculate parity on all data including
88             // sensor id and redundant integer data
89             if (mnybble_no > 4 && mnybble_no < 8) {
90                 parity += bit;
91             }
92
93             //      fprintf(stdout, "recv: [%d/%d] %d -> msg [%d/%d] %02x, Parity: %d %s\n", rbyte_no, rbit_no,
94             //              bit, mnybble_no, mbit_no, msg_nybbles[mnybble_no], parity,
95             //              ( mbit_no == 0 ) ? "\n" : "" );
96         }
97
98         parity_bit = msg_nybbles[4] & 0x01;
99         parity += parity_bit;
100
101         // Validate Checksum (4 bits in last nybble)
102         checksum = 0;
103         for (i = 0; i < 10; i++) {
104             checksum = (checksum + msg_nybbles[i]) & 0x0F;
105         }
106
107         // fprintf(stdout,"Parity: %d, parity bit %d, Good %d\n", parity, parity_bit, parity % 2);
108
109         if (checksum == msg_nybbles[10] && (parity % 2 == 0)) {
110             return 1;
111         } else {
112             if (debug_output > 1) {
113                 fprintf(stdout,
114                         "LaCrosse TX Checksum/Parity error: Comp. %d != Recv. %d, Parity %d\n",
115                         checksum, msg_nybbles[10], parity);
116             }
117             return 0;
118         }
119     }
120
121     return 0;
122 }
123
124 // LaCrosse TX-6u, TX-7u,  Temperature and Humidity Sensors
125 // Temperature and Humidity are sent in different messages bursts.
126 static int lacrossetx_callback(bitbuffer_t *bitbuffer) {
127     bitrow_t *bb = bitbuffer->bb;
128
129     int i, m, valid = 0;
130     int events = 0;
131     uint8_t *buf;
132     uint8_t msg_nybbles[LACROSSE_NYBBLE_CNT];
133     uint8_t sensor_id, msg_type, msg_len, msg_parity, msg_checksum;
134     int msg_value_int;
135     float msg_value = 0, temp_c = 0;
136     time_t time_now;
137     char time_str[LOCAL_TIME_BUFLEN];
138     data_t *data;
139
140     for (m = 0; m < BITBUF_ROWS; m++) {
141         valid = 0;
142         // break out the message nybbles into separate bytes
143         if (lacrossetx_detect(bb[m], msg_nybbles, bitbuffer->bits_per_row[m])) {
144
145             msg_len = msg_nybbles[1];
146             msg_type = msg_nybbles[2];
147             sensor_id = (msg_nybbles[3] << 3) + (msg_nybbles[4] >> 1);
148             msg_parity = msg_nybbles[4] & 0x01;
149             msg_value = msg_nybbles[5] * 10 + msg_nybbles[6]
150                 + msg_nybbles[7] / 10.0;
151             msg_value_int = msg_nybbles[8] * 10 + msg_nybbles[9];
152             msg_checksum = msg_nybbles[10];
153
154             time(&time_now);
155             local_time_str(0, time_str);
156
157             // Check Repeated data values as another way of verifying
158             // message integrity.
159             if (msg_nybbles[5] != msg_nybbles[8] ||
160                 msg_nybbles[6] != msg_nybbles[9]) {
161                 if (debug_output) {
162                     fprintf(stderr,
163                             "LaCrosse TX Sensor %02x, type: %d: message value mismatch int(%3.1f) != %d?\n",
164                             sensor_id, msg_type, msg_value, msg_value_int);
165                 }
166                 continue;
167             }
168
169             switch (msg_type) {
170             case 0x00:
171                 temp_c = msg_value - 50.0;
172                 data = data_make("time",          "",            DATA_STRING, time_str,
173                                  "model",         "",            DATA_STRING, "LaCrosse TX Sensor",
174                                  "id",            "",            DATA_INT, sensor_id,
175                                  "temperature_C", "Temperature", DATA_FORMAT, "%.1f C", DATA_DOUBLE, temp_c,
176                                  NULL);
177                 data_acquired_handler(data);
178                 events++;
179                 break;
180
181             case 0x0E:
182                 data = data_make("time",          "",            DATA_STRING, time_str,
183                                  "model",         "",            DATA_STRING, "LaCrosse TX Sensor",
184                                  "id",            "",            DATA_INT, sensor_id,
185                                  "humidity",      "Humidity", DATA_FORMAT, "%.1f %%", DATA_DOUBLE, msg_value,
186                                  NULL);
187                 data_acquired_handler(data);
188                 events++;
189                 break;
190
191             default:
192                 // @todo this should be reported/counted as exception, not considered debug
193                 if (debug_output) {
194                     fprintf(stderr,
195                             "%s LaCrosse Sensor %02x: Unknown Reading type %d, % 3.1f (%d)\n",
196                             time_str, sensor_id, msg_type, msg_value, msg_value_int);
197                 }
198             }
199         }
200     }
201
202     return events;
203 }
204
205 static char *output_fields[] = {
206     "time",
207     "model",
208     "id",
209     "temperature_C",
210     "humidity",
211     NULL
212 };
213
214 r_device lacrossetx = {
215     .name           = "LaCrosse TX Temperature / Humidity Sensor",
216     .modulation     = OOK_PULSE_PWM_RAW,
217     .short_limit    = 952,
218     .long_limit     = 3000,
219     /// .reset_limit    = 32000,
220     .reset_limit    = 8000,
221     .json_callback  = &lacrossetx_callback,
222     .disabled       = 0,
223     .demod_arg      = 0,        // No Startbit removal
224     .fields = output_fields,
225 };