Редизайн на основе текущей ветки мейнстрима + новые устройства.
[rtl-433.git] / src / devices / danfoss.c
1 /* Danfoss CFR Thermostat sensor protocol
2  *
3  * Manual: http://na.heating.danfoss.com/PCMPDF/Vi.88.R1.22%20CFR%20Thrm.pdf
4  *
5  * No protocol information found, so protocol is reverse engineered.
6  * Sensor uses FSK modulation and Pulse Code Modulated (direct bit sequence) data.
7  *
8  * Example received raw data package:
9  *   bitbuffer:: Number of rows: 1
10  *   [00] {255} 2a aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa 36 5c a9 a6 93 6c 4d a6 a9 6a 6b 29 4f 19 72 b2
11  *
12  * The package starts with a long (~128 bit) synchronization preamble (0xaa).
13  * Sensor data consists of 21 nibbles of 4 bit, which are encoded with a 4b/6b encoder, resulting
14  * in an encoded sequence of 126 bits (~16 encoded bytes)
15  * The package may end with a noise bit or two.
16  *
17  * Example: <Received bits> | <6b/4b decoded nibbles>
18  *  365C A9A6 936C 4DA6 A96A 6B29 4F19 72B2 | E02 111E C4 6616 7C14 B02C
19  *
20  * Nibble content:
21  *  #0 -#2  -- Prefix - always 0xE02 (decoded)
22  *  #3 -#6  -- Sensor ID
23  *  #7      -- Message Count. Rolling counter incremented at each unique message.
24  *  #8      -- Switch setting -> 2="day", 4="timer", 8="night"
25  *  #9 -#10 -- Temperature decimal <value>/256
26  *  #11-#12 -- Temperature integer (in Celcius)
27  *  #13-#14 -- Set point decimal <value>/256
28  *  #15-#16 -- Set point integer (in Celcius)
29  *  #17-#20 -- CRC16, poly 0x1021, includes nibble #1-#16
30  *
31  * Copyright (C) 2016 Tommy Vestermark
32  * This program is free software; you can redistribute it and/or modify
33  * it under the terms of the GNU General Public License as published by
34  * the Free Software Foundation; either version 2 of the License, or
35  * (at your option) any later version.
36  */
37 #include "rtl_433.h"
38 #include "util.h"
39
40 #define NUM_BYTES 10    // Output contains 21 nibbles, but skip first nibble 0xE, as it is not part of CRC and to get byte alignment
41 static const uint8_t HEADER[] = { 0x36, 0x5c }; // Encoded prefix. Full prefix is 3 nibbles => 18 bits (but checking 16 is ok)
42 static const uint16_t CRC_POLY = 0x1021;
43
44 // Mapping from 6 bits to 4 bits
45 static uint8_t danfoss_decode_nibble(uint8_t byte) {
46         uint8_t out = 0xFF;     // Error
47         switch(byte) {
48                 case 0x0B:      out = 0xD;      break;
49                 case 0x0D:      out = 0xE;      break;
50                 case 0x0E:      out = 0x3;      break;
51                 case 0x13:      out = 0x4;      break;
52                 case 0x15:      out = 0xA;      break;
53                 case 0x16:      out = 0xF;      break;
54                 case 0x19:      out = 0x9;      break;
55                 case 0x1A:      out = 0x6;      break;
56                 case 0x25:      out = 0x0;      break;
57                 case 0x26:      out = 0x7;      break;
58                 case 0x29:      out = 0x1;      break;
59                 case 0x2A:      out = 0x5;      break;
60                 case 0x2C:      out = 0xC;      break;
61                 case 0x31:      out = 0xB;      break;
62                 case 0x32:      out = 0x2;      break;
63                 case 0x34:      out = 0x8;      break;
64                 default:        break;  // Error
65         }
66         return out;
67 }
68
69
70 static int danfoss_CFR_callback(bitbuffer_t *bitbuffer) {
71         uint8_t bytes[NUM_BYTES];       // Decoded bytes with two 4 bit nibbles in each
72         data_t *data;
73         char time_str[LOCAL_TIME_BUFLEN];
74
75         local_time_str(0, time_str);
76
77         // Validate package
78         unsigned bits = bitbuffer->bits_per_row[0];
79         if (bits >= 246 && bits <= 260) {       // Normal size is 255, but allow for some noise in preamble
80                 // Find a package
81                 unsigned bit_offset = bitbuffer_search(bitbuffer, 0, 112, HEADER, sizeof(HEADER)*8);    // Normal index is 128, skip first 14 bytes to find faster
82                 if (bits-bit_offset < 126) {    // Package should be at least 126 bits
83                         if (debug_output) {
84                                 fprintf(stderr, "Danfoss: short package. Header index: %u\n", bit_offset);
85                                 bitbuffer_print(bitbuffer);
86                         }
87                         return 0;
88                 }
89                 bit_offset += 6;        // Skip first nibble 0xE to get byte alignment and remove from CRC calculation
90
91                 // Decode input 6 bit nibbles to output 4 bit nibbles (packed in bytes)
92                 for (unsigned n=0; n<NUM_BYTES; ++n) {
93                         uint8_t nibble_h = danfoss_decode_nibble(bitrow_get_byte(bitbuffer->bb[0], n*12+bit_offset) >> 2);
94                         uint8_t nibble_l = danfoss_decode_nibble(bitrow_get_byte(bitbuffer->bb[0], n*12+bit_offset+6) >> 2);
95                         if (nibble_h > 0xF || nibble_l > 0xF) {
96                                 if (debug_output) {
97                                         fprintf(stderr, "Danfoss: 6b/4b decoding error\n");
98                                         bitbuffer_print(bitbuffer);
99                                 }
100                                 return 0;
101                         }
102                         bytes[n] = (nibble_h << 4) | nibble_l;
103                 }
104
105                 // Output raw decoded data for debug
106                 if (debug_output) {
107                         char str_raw[NUM_BYTES*2+4];    // Add some extra space for line end
108                         for (unsigned n=0; n<NUM_BYTES; ++n) {
109                                 sprintf(str_raw+n*2, "%02X", bytes[n]);
110                         }
111                         fprintf(stderr, "Danfoss: Raw 6b/4b decoded = %s\n", str_raw);
112                 }
113
114                 // Validate Prefix and CRC
115                 uint16_t crc_calc = crc16_ccitt(bytes, NUM_BYTES-2, CRC_POLY, 0);
116                 if (bytes[0] != 0x02            // Somewhat redundant to header search, but checks last bits
117                  || crc_calc != (((uint16_t)bytes[8] << 8) | bytes[9])
118                 ) {
119                         if (debug_output) fprintf(stderr, "Danfoss: Prefix or CRC error.\n");
120                         return 0;
121                 }
122
123                 // Decode data
124                 unsigned id = (bytes[1] << 8) | bytes[2];
125
126                 char *str_sw;
127                 switch (bytes[3] & 0x0F) {
128                         case 2:  str_sw = "DAY"; break;
129                         case 4:  str_sw = "TIMER"; break;
130                         case 8:  str_sw = "NIGHT"; break;
131                         default: str_sw = "ERROR";
132                 }
133
134                 float temp_meas  = (float)bytes[5] + (float)bytes[4] / 256.0;
135                 float temp_setp  = (float)bytes[7] + (float)bytes[6] / 256.0;
136
137                 // Output data
138                 data = data_make(
139                      "time",            "",             DATA_STRING,    time_str,
140                      "model",           "",             DATA_STRING,    "Danfoss CFR Thermostat",
141                      "id",              "ID",           DATA_INT,       id,
142                      "temperature_C",   "Temperature",  DATA_FORMAT,    "%.2f C", DATA_DOUBLE, temp_meas,
143                      "setpoint_C",      "Setpoint",     DATA_FORMAT,    "%.2f C", DATA_DOUBLE, temp_setp,
144                      "switch",          "Switch",       DATA_STRING,    str_sw,
145                      "crc", "", DATA_STRING, "ok",
146                      NULL);
147                 data_acquired_handler(data);
148
149                 return 1;
150         }
151         return 0;
152 }
153
154
155 static char *output_fields[] = {
156     "time",
157     "brand"
158     "model"
159     "id"
160     "temperature_C",
161     "setpoint_C",
162     "switch",
163     "crc",
164     NULL
165 };
166
167 r_device danfoss_CFR = {
168         .name           = "Danfoss CFR Thermostat",
169         .modulation     = FSK_PULSE_PCM,
170         .short_limit    = 100,  // NRZ decoding
171         .long_limit     = 100,  // Bit width
172         .reset_limit    = 500,  // Maximum run is 4 zeroes/ones
173         .json_callback  = &danfoss_CFR_callback,
174         .disabled       = 0,
175         .demod_arg      = 0,
176         .fields         = output_fields
177 };