mirror of
https://github.com/merbanan/rtl_433.git
synced 2026-04-22 10:37:03 -04:00
Add support for Vaillant calorMatic 340f (VRT 340f) wireless central ceating control (868MHz)
This commit is contained in:
committed by
Benjamin Larsson
parent
2ee2edc68b
commit
2acd8f9253
@@ -80,6 +80,7 @@
|
||||
DECL(acurite_00275rm) \
|
||||
DECL(lacrosse_tx35) \
|
||||
DECL(lacrosse_tx29) \
|
||||
DECL(vaillant_vrt340f) \
|
||||
DECL(fineoffset_WH25) \
|
||||
DECL(fineoffset_WH0530)
|
||||
|
||||
|
||||
@@ -91,6 +91,7 @@ add_executable(rtl_433
|
||||
devices/maverick_et73x.c
|
||||
devices/rftech.c
|
||||
devices/lacrosse_tx35.c
|
||||
devices/vaillant_vrt340f.c
|
||||
|
||||
)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
235
src/devices/vaillant_vrt340f.c
Normal file
235
src/devices/vaillant_vrt340f.c
Normal 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
|
||||
};
|
||||
Reference in New Issue
Block a user