Add support for Vaillant calorMatic 340f (VRT 340f) wireless central ceating control (868MHz)

This commit is contained in:
Reinhold Kainhofer
2017-05-11 18:50:55 +02:00
committed by Benjamin Larsson
parent 2ee2edc68b
commit 2acd8f9253
4 changed files with 238 additions and 0 deletions

View File

@@ -80,6 +80,7 @@
DECL(acurite_00275rm) \
DECL(lacrosse_tx35) \
DECL(lacrosse_tx29) \
DECL(vaillant_vrt340f) \
DECL(fineoffset_WH25) \
DECL(fineoffset_WH0530)

View File

@@ -91,6 +91,7 @@ add_executable(rtl_433
devices/maverick_et73x.c
devices/rftech.c
devices/lacrosse_tx35.c
devices/vaillant_vrt340f.c
)

View File

@@ -76,6 +76,7 @@ rtl_433_SOURCES = baseband.c \
devices/honeywell.c \
devices/maverick_et73x.c \
devices/rftech.c \
devices/vaillant_vrt340f.c \
devices/wg_pb12v1.c
rtl_433_LDADD = $(LIBRTLSDR) $(LIBM)

View File

@@ -0,0 +1,235 @@
#include "rtl_433.h"
#include "pulse_demod.h"
#include "data.h"
#include "util.h"
// Protocol of the Vaillant VRT 340f (calorMatic 340f) central heating control
// http://wiki.kainhofer.com/hardware/vaillantvrt340f
// The data is sent differential Manchester encoded (OOK_PULSE_CLOCK_BITS in
// rtl_433 terms) with bit-stuffing (after five 1 bits an extra 0 bit is inserted)
//
// All bytes are sent with least significant bit FIRST (1000 0111 = 0xE1)
//
// 0x00 00 7E | 6D F6 | 00 20 00 | 00 | 80 | B4 | 00 | FD 49 | FF 00
// SYNC+HD. | DevID | CONST? |Rep.|Wtr.|Htg.|Btr.|Checksm| EPILOGUE
//
// CONST? ... Unknown, but constant in all observed signals
// Rep. ... Repeat indicator: 0x00=original signal, 0x01=first repeat
// Wtr. ... pre-heated Water: 0x80=ON, 0x88=OFF (bit 8 is always set)
// Htg. ... Heating: 0x00=OFF, 0xB4=ON (2-point), 0x01-0x7F=target heating water temp
// (bit 8 indicates 2-point heating mode, bits 1-7 the heating water temp)
// Btr. ... Battery: 0x00=OK, 0x01=LOW
// Checksm... Checksum (2-byte signed int): = -sum(bytes 4-12)
// Copyright (C) 2017, Reinhold Kainhofer, reinhold@kainhofer.com
// License: GPL v2+ (or at your choice, any other OSI-approved Open Source license)
static int16_t
calculate_checksum (uint8_t *buff, int from, int to) {
int16_t checksum = 0;
for (int byteCnt = from; byteCnt <= to; byteCnt++) {
checksum += (int16_t)buff[byteCnt];
}
return -checksum;
}
static int
validate_checksum (uint8_t * msg, int from, int to, int cs_from, int cs_to)
{
// Fields cs_from and cs_to hold the 2-byte checksum as signed int
int16_t expected = msg[cs_from]*0x100+ msg[cs_to];
int16_t calculated = calculate_checksum (msg, from, to);
if (expected != calculated) {
if (debug_output >= 1) {
fprintf(stderr, "Checksum error in Vaillant VRT340f. Expected: %04x Calculated: %04x\n", expected, calculated);
fprintf(stderr, "Message (data content of bytes %d-%d): ", from, to); int i; for (i=from; i<=to; i++) fprintf(stderr, "%02x ", msg[i]); fprintf(stderr, "\n\n");
}
}
return expected == calculated;
}
static uint16_t
get_device_id (uint8_t * msg, int pos)
{
uint16_t deviceID = msg[pos]*0x100 + msg[pos+1];
return deviceID;
}
static uint8_t
get_heating_mode (uint8_t * msg)
{
uint8_t mode = 0;
uint8_t tmp = msg[10];
if (tmp==0) {
mode = 0;
} else if (tmp >> 7) { // highest bit set => automatic (2-point) mode
mode = 1;
} else { // highest bit not set, but value given => analogue mode (bits 1-7 hold temperature)
mode = 2;
}
return mode;
}
static uint8_t
get_target_temperature(uint8_t * msg)
{
uint8_t temp = (msg[10] & 0x7F); // highest bit indicates auto(2-point) / analogue mode
return temp;
}
static uint8_t
get_water_preheated(uint8_t * msg)
{
uint8_t water = (msg[9] & 8) == 0; // bit 4 indicates water: 0=ON, 1=OFF
return water; // if not zero, water is pre-heated
}
static uint8_t
get_battery_status(uint8_t * msg)
{
uint8_t status = msg[11] != 0; // if not zero, battery is low
return status;
}
static int
vaillant_vrt340_parser (bitbuffer_t *bitbuffer)
{
bitrow_t *bb = bitbuffer->bb;
char time_str[LOCAL_TIME_BUFLEN];
data_t *data;
local_time_str(0, time_str);
// TODO: Use repeat signal for error checking / correction!
// each row needs to have at least 128 bits (plus a few more due to bit stuffing)
if (bitbuffer->bits_per_row[0]<128)
return 0;
// The protocol uses bit-stuffing => remove 0 bit after five consecutive 1 bits
// Also, each byte is represented with least significant bit first -> swap them!
bitbuffer_t bits = {0};
int ones = 0;
for (uint16_t k = 0; k < bitbuffer->bits_per_row[0]; k++) {
int b = bitrow_get_bit(bb[0], k);
if (b==1) {
bitbuffer_add_bit(&bits, 1);
ones++;
} else {
if (ones != 5) { // Ignore a 0 bit after five consecutive 1 bits:
bitbuffer_add_bit(&bits, 0);
}
ones = 0;
}
}
uint16_t bitcount = bits.bits_per_row[0];
// Change to least-significant-bit last (protocol uses least-siginificant-bit first) for hex representation:
for (uint16_t k = 0; k <= (uint16_t)(bitcount-1)/8; k++) {
bits.bb[0][k] = reverse8(bits.bb[0][k]);
}
bb = bits.bb;
/* DEBUG: print out the received packet */
/*
fprintf (stderr, "Vaillant bitcount=%d; data=", bitcount);
for (int i = 0 ; i < bitcount/8 ; i++) {
fprintf (stderr, "%02x ", bb[0][i]);
}
fprintf(stderr, "\n");
*/
// A correct message has 128 bits plus potentially two extra bits for clock sync at the end
if(!(128 <= bitcount && bitcount <= 131) && !(168 <= bitcount && bitcount <= 171))
return 0;
// "Normal package":
if ((bb[0][0] == 0x00) && (bb[0][1] == 0x00) && (bb[0][2] == 0x7e) && (128 <= bitcount && bitcount <= 131)) {
if (!validate_checksum (bb[0], /* Data from-to: */3,11, /*Checksum from-to:*/12,13)) {
return 0;
}
// Device ID starts at bit 4:
uint16_t deviceID = get_device_id (bb[0], 3);
uint8_t heating_mode = get_heating_mode (bb[0]); // 0=OFF, 1=ON (2-point heating), 2=ON (analogue heating)
uint8_t target_temperature = get_target_temperature (bb[0]);
uint8_t water_preheated = get_water_preheated(bb[0]); // 1=Pre-heat, 0=no pre-heated water
uint8_t isBatteryLow = get_battery_status(bb[0]);
data = data_make("time", "", DATA_STRING, time_str,
"model", "", DATA_STRING, "Vaillant VRT340f Central Heating Thermostat",
"device", "Device ID", DATA_FORMAT, "0x%04X", DATA_INT, deviceID,
"heating", "Heating Mode", DATA_STRING, (heating_mode==0)?"OFF":((heating_mode==1)?"ON (2-point)":"ON (analogue)"),
"heating_temp", "Heating Water Temp.", DATA_FORMAT, "%d", DATA_INT, (int16_t)target_temperature,
"water", "Pre-heated Water", DATA_STRING, water_preheated ? "ON" : "off",
"battery", "Battery", DATA_STRING, isBatteryLow ? "Low" : "Ok",
NULL);
data_acquired_handler(data);
return 1;
}
// "RF detection package":
if ((bb[0][0] == 0x00) && (bb[0][1] == 0x00) && (bb[0][2] == 0x7E) && (168 <= bitcount && bitcount <= 171)) {
if (!validate_checksum (bb[0], /* Data from-to: */3,16, /*Checksum from-to:*/17,18)) {
return 0;
}
// Device ID starts at bit 12:
uint16_t deviceID = get_device_id (bb[0], 11);
data = data_make("time", "", DATA_STRING, time_str,
"model", "", DATA_STRING, "Vaillant VRT340f Central Heating Thermostat (RF Detection)",
"device", "Device ID", DATA_INT, deviceID,
NULL);
data_acquired_handler(data);
return 1;
}
return 0;
}
static int
vaillant_vrt340_callback (bitbuffer_t *bitbuffer)
{
return vaillant_vrt340_parser (bitbuffer);
}
PWM_Precise_Parameters vaillant_vrt340_clock_bits_parameters = {
.pulse_tolerance = 30,
.pulse_sync_width = 0, // No sync bit used
};
static char *output_fields[] = {
"time",
"model",
"device",
"heating",
"heating_temp",
"water",
"battery",
NULL
};
r_device vaillant_vrt340f = {
.name = "Vaillant calorMatic 340f Central Heating Control",
.modulation = OOK_PULSE_CLOCK_BITS,
.short_limit = 836,
.long_limit = 1648, // not used
.reset_limit = 4000,
.json_callback = &vaillant_vrt340_callback,
.disabled = 0,
.demod_arg = (uintptr_t)&vaillant_vrt340_clock_bits_parameters,
.fields = output_fields
};