1 /* Danfoss CFR Thermostat sensor protocol
3 * Manual: http://na.heating.danfoss.com/PCMPDF/Vi.88.R1.22%20CFR%20Thrm.pdf
5 * No protocol information found, so protocol is reverse engineered.
6 * Sensor uses FSK modulation and Pulse Code Modulated (direct bit sequence) data.
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
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.
17 * Example: <Received bits> | <6b/4b decoded nibbles>
18 * 365C A9A6 936C 4DA6 A96A 6B29 4F19 72B2 | E02 111E C4 6616 7C14 B02C
21 * #0 -#2 -- Prefix - always 0xE02 (decoded)
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
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.
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;
44 // Mapping from 6 bits to 4 bits
45 static uint8_t danfoss_decode_nibble(uint8_t byte) {
46 uint8_t out = 0xFF; // Error
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
70 static int danfoss_CFR_callback(bitbuffer_t *bitbuffer) {
71 uint8_t bytes[NUM_BYTES]; // Decoded bytes with two 4 bit nibbles in each
73 char time_str[LOCAL_TIME_BUFLEN];
75 local_time_str(0, time_str);
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
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
84 fprintf(stderr, "Danfoss: short package. Header index: %u\n", bit_offset);
85 bitbuffer_print(bitbuffer);
89 bit_offset += 6; // Skip first nibble 0xE to get byte alignment and remove from CRC calculation
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) {
97 fprintf(stderr, "Danfoss: 6b/4b decoding error\n");
98 bitbuffer_print(bitbuffer);
102 bytes[n] = (nibble_h << 4) | nibble_l;
105 // Output raw decoded data for debug
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]);
111 fprintf(stderr, "Danfoss: Raw 6b/4b decoded = %s\n", str_raw);
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])
119 if (debug_output) fprintf(stderr, "Danfoss: Prefix or CRC error.\n");
124 unsigned id = (bytes[1] << 8) | bytes[2];
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";
134 float temp_meas = (float)bytes[5] + (float)bytes[4] / 256.0;
135 float temp_setp = (float)bytes[7] + (float)bytes[6] / 256.0;
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",
147 data_acquired_handler(data);
155 static char *output_fields[] = {
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,
176 .fields = output_fields