1 /** @file
2     Vaillant VRT 340f (calorMatic 340f) central heating control.
3 
4     Copyright (C) 2017 Reinhold Kainhofer <reinhold@kainhofer.com>
5 
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10 */
11 /**
12 Vaillant VRT 340f (calorMatic 340f) central heating control.
13 
14     http://wiki.kainhofer.com/hardware/vaillantvrt340f
15 
16 The data is sent differential Manchester encoded
17 with bit-stuffing (after five 1 bits an extra 0 bit is inserted)
18 
19 All bytes are sent with least significant bit FIRST (1000 0111 = 0xE1)
20 
21     0x00 00 7E | 6D F6 | 00 20 00 | 00 | 80 | B4 | 00 | FD 49 | FF 00
22       SYNC+HD. | DevID | CONST?   |Rep.|Wtr.|Htg.|Btr.|Checksm| EPILOGUE
23 
24 - CONST? ... Unknown, but constant in all observed signals
25 - Rep.   ... Repeat indicator: 0x00=original signal, 0x01=first repeat
26 - Wtr.   ... pre-heated Water: 0x80=ON, 0x88=OFF (bit 8 is always set)
27 - Htg.   ... Heating: 0x00=OFF, 0xB4=ON (2-point), 0x01-0x7F=target heating water temp
28              (bit 8 indicates 2-point heating mode, bits 1-7 the heating water temp)
29 - Btr.   ... Battery: 0x00=OK, 0x01=LOW
30 - Checksm... Checksum (2-byte signed int): = -sum(bytes 4-12)
31 
32 */
33 
34 
35 #include "decoder.h"
36 
validate_checksum(r_device * decoder,uint8_t * b,int from,int to,int cs_from,int cs_to)37 static int validate_checksum(r_device *decoder, uint8_t *b, int from, int to, int cs_from, int cs_to)
38 {
39     // Fields cs_from and cs_to hold the 2-byte checksum as signed int
40     int expected = (b[cs_from] << 8) | b[cs_to];
41     int calculated = add_bytes(&b[from], to-from+1);
42     int chk        = (calculated + expected) & 0xffff;
43 
44     if (chk) {
45         if (decoder->verbose) {
46             fprintf(stderr, "Checksum error in Vaillant VRT340f.  Expected: %04x  Calculated: %04x\n", expected, calculated);
47             bitrow_printf(&b[from], (to - from + 1) * 8, "Message (data content of bytes %d-%d): ", from, to);
48         }
49     }
50     return !chk;
51 }
52 
vaillant_vrt340_callback(r_device * decoder,bitbuffer_t * bitbuffer)53 static int vaillant_vrt340_callback(r_device *decoder, bitbuffer_t *bitbuffer)
54 {
55     uint8_t *b = bitbuffer->bb[0];
56 
57     // TODO: Use repeat signal for error checking / correction!
58 
59     // each row needs to have at least 128 bits (plus a few more due to bit stuffing)
60     if (bitbuffer->bits_per_row[0] < 128)
61         return DECODE_ABORT_LENGTH;
62 
63     // The protocol uses bit-stuffing => remove 0 bit after five consecutive 1 bits
64     // Also, each byte is represented with least significant bit first -> swap them!
65     bitbuffer_t bits = {0};
66     int ones = 0;
67     for (uint16_t k = 0; k < bitbuffer->bits_per_row[0]; k++) {
68         int bit = bitrow_get_bit(b, k);
69         if (bit == 1) {
70             bitbuffer_add_bit(&bits, 1);
71             ones++;
72         } else {
73             if (ones != 5) { // Ignore a 0 bit after five consecutive 1 bits:
74                 bitbuffer_add_bit(&bits, 0);
75             }
76             ones = 0;
77         }
78     }
79 
80     b = bits.bb[0];
81     uint16_t bitcount = bits.bits_per_row[0];
82 
83     // Change to least-significant-bit last (protocol uses least-significant-bit first)
84     reflect_bytes(b, (bitcount - 1) / 8);
85 
86     // A correct message has 128 bits plus potentially two extra bits for clock sync at the end
87     if (!(128 <= bitcount && bitcount <= 131) && !(168 <= bitcount && bitcount <= 171))
88         return DECODE_ABORT_LENGTH;
89 
90     // "Normal package":
91     if ((b[0] == 0x00) && (b[1] == 0x00) && (b[2] == 0x7e) && (128 <= bitcount && bitcount <= 131)) {
92 
93         if (!validate_checksum(decoder, b, /* Data from-to: */3, 11, /*Checksum from-to:*/12, 13)) {
94             return DECODE_FAIL_MIC;
95         }
96 
97         // Device ID starts at byte 4:
98         int device_id          = (b[3] << 8) | b[4];
99         int heating_mode       = (b[10] >> 7);    // highest bit indicates automatic (2-point) / analogue mode
100         int target_temperature = (b[10] & 0x7f);  // highest bit indicates auto(2-point) / analogue mode
101         int water_preheated    = (b[9] & 8) == 0; // bit 4 indicates water: 1=Pre-heat, 0=no pre-heated water
102         int battery_low        = b[11] != 0;      // if not zero, battery is low
103 
104         /* clang-format off */
105         data_t *data = data_make(
106                 "model",        "",                     DATA_STRING, "Vaillant-VRT340f",
107                 "id",           "Device ID",            DATA_FORMAT, "0x%04X", DATA_INT, device_id,
108                 "heating",      "Heating Mode",         DATA_STRING, (heating_mode == 0 && target_temperature == 0) ? "OFF" : heating_mode ? "ON (2-point)" : "ON (analogue)",
109                 "heating_temp", "Heating Water Temp.",  DATA_FORMAT, "%d", DATA_INT, target_temperature,
110                 "water",        "Pre-heated Water",     DATA_STRING, water_preheated ? "ON" : "off",
111                 "battery_ok",   "Battery",              DATA_INT,    !battery_low,
112                 NULL);
113         /* clang-format on */
114         decoder_output_data(decoder, data);
115 
116         return 1;
117     }
118 
119     // "RF detection package":
120     if ((b[0] == 0x00) && (b[1] == 0x00) && (b[2] == 0x7E) && (168 <= bitcount && bitcount <= 171)) {
121 
122         if (!validate_checksum(decoder, b, /* Data from-to: */ 3, 16, /*Checksum from-to:*/ 17, 18)) {
123             return DECODE_FAIL_MIC;
124         }
125 
126         // Device ID starts at byte 12:
127         int device_id = (b[11] << 8) | b[12];
128 
129         /* clang-format off */
130         data_t *data = data_make(
131                 "model",        "",                     DATA_STRING, "Vaillant-VRT340f",
132                 "id",           "Device ID",            DATA_INT,    device_id,
133                 NULL);
134         /* clang-format on */
135         decoder_output_data(decoder, data);
136 
137         return 1;
138     }
139 
140     return DECODE_FAIL_SANITY;
141 }
142 
143 static char *output_fields[] = {
144         "model",
145         "id",
146         "heating",
147         "heating_temp",
148         "water",
149         "battery_ok",
150         NULL,
151 };
152 
153 r_device vaillant_vrt340f = {
154         .name        = "Vaillant calorMatic VRT340f Central Heating Control",
155         .modulation  = OOK_PULSE_DMC,
156         .short_width = 836,  // half-bit width 836 us
157         .long_width  = 1648, // bit width 1648 us
158         .reset_limit = 4000,
159         .tolerance   = 120, // us
160         .decode_fn   = &vaillant_vrt340_callback,
161         .disabled    = 0,
162         .fields      = output_fields,
163 };
164