minor: Cleanup flatten Honeywell-CM921 style

This commit is contained in:
Christian W. Zuckschwerdt 2024-03-15 12:49:26 +01:00
parent 3db192c458
commit 3853c31b4a

View file

@ -8,14 +8,15 @@
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
*/
/**
#include "decoder.h"
/** @fn int honeywell_cm921_decode(r_device *decoder, bitbuffer_t *bitbuffer)
Honeywell CM921 Thermostat (subset of Evohome).
868Mhz FSK, PCM, Start/Stop bits, reversed, Manchester.
*/
#include "decoder.h"
// #define _DEBUG
static int decode_10to8(uint8_t const *b, int pos, int end, uint8_t *out)
@ -64,6 +65,7 @@ static data_t *add_hex_string(data_t *data, const char *name, const uint8_t *buf
}
#endif
/*
typedef struct {
int t;
const char s[4];
@ -101,167 +103,7 @@ static void decode_device_id(const uint8_t device_id[3], char *buf, size_t buf_s
snprintf(buf, buf_sz, "%3s:%06d", dev_name, dev_id);
}
static data_t *decode_device_ids(const message_t *msg, data_t *data, int style)
{
char ds[64] = {0}; // up to 4 ids of at most 10+1 chars
for (unsigned i = 0; i < msg->num_device_ids; i++) {
if (i != 0) {
strcat(ds, " ");
}
char buf[16] = {0};
if (style == 0) {
decode_device_id(msg->device_id[i], buf, sizeof(buf));
}
else {
snprintf(buf, sizeof(buf), "%02x%02x%02x",
msg->device_id[i][0],
msg->device_id[i][1],
msg->device_id[i][2]);
}
strcat(ds, buf);
}
return data_append(data, "ids", "Device IDs", DATA_STRING, ds, NULL);
}
#define UNKNOWN_IF(C) do { \
if (C) \
return data_append(data, "unknown", "", DATA_FORMAT, "%04x", DATA_INT, msg->command, NULL); \
} while (0)
static data_t *honeywell_cm921_interpret_message(r_device *decoder, const message_t *msg, data_t *data)
{
// Sources of inspiration:
// https://github.com/Evsdd/The-Evohome-Protocol/wiki
// https://www.domoticaforum.eu/viewtopic.php?f=7&t=5806&start=30
// (specifically https://www.domoticaforum.eu/download/file.php?id=1396)
data = decode_device_ids(msg, data, 1);
switch (msg->command) {
case 0x1030: {
UNKNOWN_IF(msg->payload_length != 16);
data = data_append(data, "zone_idx", "", DATA_FORMAT, "%02x", DATA_INT, msg->payload[0], NULL);
for (unsigned i = 0; i < 5; i++) { // order fixed?
const uint8_t *p = &msg->payload[1 + 3*i];
// *(p+1) == 0x01 always?
int value = *(p+2);
switch (*p) {
case 0xC8: data = data_append(data, "max_flow_temp", "", DATA_INT, value, NULL); break;
case 0xC9: data = data_append(data, "pump_run_time", "", DATA_INT, value, NULL); break;
case 0xCA: data = data_append(data, "actuator_run_time", "", DATA_INT, value, NULL); break;
case 0xCB: data = data_append(data, "min_flow_temp", "", DATA_INT, value, NULL); break;
case 0xCC: /* Unknown, always 0x01? */ break;
default:
decoder_logf(decoder, 1, __func__, "Unknown parameter to 0x1030: %x02d=%04d", *p, value);
}
}
break;
}
case 0x313F: {
UNKNOWN_IF(msg->payload_length != 1 && msg->payload_length != 9);
switch (msg->payload_length) {
case 1:
data = data_append(data, "time_request", "", DATA_INT, msg->payload[0], NULL); break;
case 9: {
//uint8_t const unknown_0 = msg->payload[0]; /* always == 0? */
//uint8_t const unknown_1 = msg->payload[1]; /* direction? */
uint8_t const second = msg->payload[2];
uint8_t const minute = msg->payload[3];
//uint8_t const day_of_week = msg->payload[4] >> 5;
uint8_t const hour = msg->payload[4] & 0x1F;
uint8_t const day = msg->payload[5];
uint8_t const month = msg->payload[6];
uint8_t const year[2] = { msg->payload[7], msg->payload[8] };
char time_str[256];
snprintf(time_str, sizeof(time_str), "%02d:%02d:%02d %02d-%02d-%04d", hour, minute, second, day, month, (year[0] << 8) | year[1]);
data = data_append(data, "datetime", "", DATA_STRING, time_str, NULL);
break;
}
}
break;
}
case 0x0008: {
UNKNOWN_IF(msg->payload_length != 2);
data = data_append(data, "domain_id", "", DATA_INT, msg->payload[0], NULL);
data = data_append(data, "demand", "", DATA_DOUBLE, msg->payload[1] * (1 / 200.0F) /* 0xC8 */, NULL);
break;
}
case 0x3ef0: {
UNKNOWN_IF(msg->payload_length != 3 && msg->payload_length != 6);
switch (msg->payload_length) {
case 3:
data = data_append(data, "status", "", DATA_DOUBLE, msg->payload[1] * (1 / 200.0F) /* 0xC8 */, NULL);
break;
case 6:
data = data_append(data, "boiler_modulation_level", "", DATA_DOUBLE, msg->payload[1] * (1 / 200.0F) /* 0xC8 */, NULL);
data = data_append(data, "flame_status", "", DATA_INT, msg->payload[3], NULL);
break;
}
break;
}
case 0x2309: {
UNKNOWN_IF(msg->payload_length != 3);
data = data_append(data, "zone", "", DATA_INT, msg->payload[0], NULL);
// Observation: CM921 reports a very high setpoint during binding (0x7eff); packet: 143255c1230903017efff7
data = data_append(data, "setpoint", "", DATA_DOUBLE, ((msg->payload[1] << 8) | msg->payload[2]) * (1 / 100.0F), NULL);
break;
}
case 0x1100: {
UNKNOWN_IF(msg->payload_length != 5 && msg->payload_length != 8);
data = data_append(data, "domain_id", "", DATA_INT, msg->payload[0], NULL);
data = data_append(data, "cycle_rate", "", DATA_DOUBLE, msg->payload[1] * (1 / 4.0F), NULL);
data = data_append(data, "minimum_on_time", "", DATA_DOUBLE, msg->payload[2] * (1 / 4.0F), NULL);
data = data_append(data, "minimum_off_time", "", DATA_DOUBLE, msg->payload[3] * (1 / 4.0F), NULL);
if (msg->payload_length == 8)
data = data_append(data, "proportional_band_width", "", DATA_DOUBLE, (msg->payload[5] << 8 | msg->payload[6]) * (1 / 100.0F), NULL);
break;
}
case 0x0009: {
UNKNOWN_IF(msg->payload_length != 3);
data = data_append(data, "device_number", "", DATA_INT, msg->payload[0], NULL);
switch (msg->payload[1]) {
case 0: data = data_append(data, "failsafe_mode", "", DATA_STRING, "off", NULL); break;
case 1: data = data_append(data, "failsafe_mode", "", DATA_STRING, "20-80", NULL); break;
default: data = data_append(data, "failsafe_mode", "", DATA_STRING, "unknown", NULL);
}
break;
}
case 0x3B00: {
UNKNOWN_IF(msg->payload_length != 2);
data = data_append(data, "domain_id", "", DATA_INT, msg->payload[0], NULL);
data = data_append(data, "state", "", DATA_DOUBLE, msg->payload[1] * (1 / 200.0F) /* 0xC8 */, NULL);
break;
}
case 0x30C9: {
size_t num_zones = msg->payload_length / 3;
for (size_t i = 0; i < num_zones; i++) {
char name[256];
snprintf(name, sizeof(name), "temperature (zone %u)", msg->payload[3 * i]);
int16_t temp = msg->payload[3 * i + 1] << 8 | msg->payload[3 * i + 2];
data = data_append(data, name, "", DATA_DOUBLE, temp * (1 / 100.0F), NULL);
}
break;
}
case 0x1fd4: {
int temp = (msg->payload[1] << 8) | msg->payload[2];
data = data_append(data, "ticker", "", DATA_INT, temp, NULL);
break;
}
case 0x3150: {
// example packet Heat Demand: 18 28ad9a 884dd3 3150 0200c6 88
data = data_append(data, "zone", "", DATA_INT, msg->payload[0], NULL);
data = data_append(data, "heat_demand", "", DATA_INT, msg->payload[1], NULL);
break;
}
default: /* Unknown command */
UNKNOWN_IF(1);
}
return data;
}
*/
static uint8_t next(const uint8_t *bb, unsigned *ipos, unsigned num_bytes)
{
@ -405,9 +247,9 @@ static int honeywell_cm921_decode(r_device *decoder, bitbuffer_t *bitbuffer)
}
#endif
message_t message;
message_t msg;
int pr = parse_msg(&packet, 0, &message);
int pr = parse_msg(&packet, 0, &msg);
if (pr <= 0) {
return pr;
@ -416,11 +258,182 @@ static int honeywell_cm921_decode(r_device *decoder, bitbuffer_t *bitbuffer)
/* clang-format off */
data_t *data = data_make(
"model", "", DATA_STRING, "Honeywell-CM921",
"mic", "Integrity", DATA_STRING, "CHECKSUM",
NULL);
/* clang-format on */
data = honeywell_cm921_interpret_message(decoder, &message, data);
// Sources of inspiration:
// https://github.com/Evsdd/The-Evohome-Protocol/wiki
// https://www.domoticaforum.eu/viewtopic.php?f=7&t=5806&start=30
// (specifically https://www.domoticaforum.eu/download/file.php?id=1396)
// Decode Device IDs
char ds[64] = {0}; // up to 4 ids of at most 10+1 chars
for (unsigned i = 0; i < msg.num_device_ids; i++) {
if (i != 0) {
strcat(ds, " ");
}
char buf[16] = {0};
// Unused alternative
// decode_device_id(msg.device_id[i], buf, sizeof(buf));
snprintf(buf, sizeof(buf), "%02x%02x%02x",
msg.device_id[i][0],
msg.device_id[i][1],
msg.device_id[i][2]);
strcat(ds, buf);
}
data = data_append(data, "ids", "Device IDs", DATA_STRING, ds, NULL);
// Interpret Message
switch (msg.command) {
case 0x1030: {
if (msg.payload_length != 16) {
data_append(data, "unknown", "", DATA_FORMAT, "%04x", DATA_INT, msg.command, NULL);
break;
}
data = data_append(data, "zone_idx", "", DATA_FORMAT, "%02x", DATA_INT, msg.payload[0], NULL);
for (unsigned i = 0; i < 5; i++) { // order fixed?
const uint8_t *p = &msg.payload[1 + 3 * i];
// *(p+1) == 0x01 always?
int value = *(p + 2);
switch (*p) {
case 0xC8: data = data_append(data, "max_flow_temp", "", DATA_INT, value, NULL); break;
case 0xC9: data = data_append(data, "pump_run_time", "", DATA_INT, value, NULL); break;
case 0xCA: data = data_append(data, "actuator_run_time", "", DATA_INT, value, NULL); break;
case 0xCB: data = data_append(data, "min_flow_temp", "", DATA_INT, value, NULL); break;
case 0xCC: /* Unknown, always 0x01? */ break;
default:
decoder_logf(decoder, 1, __func__, "Unknown parameter to 0x1030: %x02d=%04d", *p, value);
}
}
break;
}
case 0x313F: {
if (msg.payload_length != 1 && msg.payload_length != 9) {
data_append(data, "unknown", "", DATA_FORMAT, "%04x", DATA_INT, msg.command, NULL);
break;
}
switch (msg.payload_length) {
case 1:
data = data_append(data, "time_request", "", DATA_INT, msg.payload[0], NULL);
break;
case 9: {
// uint8_t const unknown_0 = msg.payload[0]; /* always == 0? */
// uint8_t const unknown_1 = msg.payload[1]; /* direction? */
uint8_t const second = msg.payload[2];
uint8_t const minute = msg.payload[3];
// uint8_t const day_of_week = msg.payload[4] >> 5;
uint8_t const hour = msg.payload[4] & 0x1F;
uint8_t const day = msg.payload[5];
uint8_t const month = msg.payload[6];
uint8_t const year[2] = {msg.payload[7], msg.payload[8]};
char time_str[256];
snprintf(time_str, sizeof(time_str), "%02d:%02d:%02d %02d-%02d-%04d", hour, minute, second, day, month, (year[0] << 8) | year[1]);
data = data_append(data, "datetime", "", DATA_STRING, time_str, NULL);
break;
}
}
break;
}
case 0x0008: {
if (msg.payload_length != 2) {
data_append(data, "unknown", "", DATA_FORMAT, "%04x", DATA_INT, msg.command, NULL);
break;
}
data = data_append(data, "domain_id", "", DATA_INT, msg.payload[0], NULL);
data = data_append(data, "demand", "", DATA_DOUBLE, msg.payload[1] * (1 / 200.0F) /* 0xC8 */, NULL);
break;
}
case 0x3ef0: {
if (msg.payload_length != 3 && msg.payload_length != 6) {
data_append(data, "unknown", "", DATA_FORMAT, "%04x", DATA_INT, msg.command, NULL);
break;
}
switch (msg.payload_length) {
case 3:
data = data_append(data, "status", "", DATA_DOUBLE, msg.payload[1] * (1 / 200.0F) /* 0xC8 */, NULL);
break;
case 6:
data = data_append(data, "boiler_modulation_level", "", DATA_DOUBLE, msg.payload[1] * (1 / 200.0F) /* 0xC8 */, NULL);
data = data_append(data, "flame_status", "", DATA_INT, msg.payload[3], NULL);
break;
}
break;
}
case 0x2309: {
if (msg.payload_length != 3) {
data_append(data, "unknown", "", DATA_FORMAT, "%04x", DATA_INT, msg.command, NULL);
break;
}
data = data_append(data, "zone", "", DATA_INT, msg.payload[0], NULL);
// Observation: CM921 reports a very high setpoint during binding (0x7eff); packet: 143255c1230903017efff7
data = data_append(data, "setpoint", "", DATA_DOUBLE, ((msg.payload[1] << 8) | msg.payload[2]) * (1 / 100.0F), NULL);
break;
}
case 0x1100: {
if (msg.payload_length != 5 && msg.payload_length != 8) {
data_append(data, "unknown", "", DATA_FORMAT, "%04x", DATA_INT, msg.command, NULL);
break;
}
data = data_append(data, "domain_id", "", DATA_INT, msg.payload[0], NULL);
data = data_append(data, "cycle_rate", "", DATA_DOUBLE, msg.payload[1] * (1 / 4.0F), NULL);
data = data_append(data, "minimum_on_time", "", DATA_DOUBLE, msg.payload[2] * (1 / 4.0F), NULL);
data = data_append(data, "minimum_off_time", "", DATA_DOUBLE, msg.payload[3] * (1 / 4.0F), NULL);
if (msg.payload_length == 8)
data = data_append(data, "proportional_band_width", "", DATA_DOUBLE, (msg.payload[5] << 8 | msg.payload[6]) * (1 / 100.0F), NULL);
break;
}
case 0x0009: {
if (msg.payload_length != 3) {
data_append(data, "unknown", "", DATA_FORMAT, "%04x", DATA_INT, msg.command, NULL);
break;
}
data = data_append(data, "device_number", "", DATA_INT, msg.payload[0], NULL);
switch (msg.payload[1]) {
case 0: data = data_append(data, "failsafe_mode", "", DATA_STRING, "off", NULL); break;
case 1: data = data_append(data, "failsafe_mode", "", DATA_STRING, "20-80", NULL); break;
default: data = data_append(data, "failsafe_mode", "", DATA_STRING, "unknown", NULL);
}
break;
}
case 0x3B00: {
if (msg.payload_length != 2) {
data_append(data, "unknown", "", DATA_FORMAT, "%04x", DATA_INT, msg.command, NULL);
break;
}
data = data_append(data, "domain_id", "", DATA_INT, msg.payload[0], NULL);
data = data_append(data, "state", "", DATA_DOUBLE, msg.payload[1] * (1 / 200.0F) /* 0xC8 */, NULL);
break;
}
case 0x30C9: {
size_t num_zones = msg.payload_length / 3;
for (size_t i = 0; i < num_zones; i++) {
char name[256];
snprintf(name, sizeof(name), "temperature (zone %u)", msg.payload[3 * i]);
int16_t temp = msg.payload[3 * i + 1] << 8 | msg.payload[3 * i + 2];
data = data_append(data, name, "", DATA_DOUBLE, temp * (1 / 100.0F), NULL);
}
break;
}
case 0x1fd4: {
int temp = (msg.payload[1] << 8) | msg.payload[2];
data = data_append(data, "ticker", "", DATA_INT, temp, NULL);
break;
}
case 0x3150: {
// example packet Heat Demand: 18 28ad9a 884dd3 3150 0200c6 88
data = data_append(data, "zone", "", DATA_INT, msg.payload[0], NULL);
data = data_append(data, "heat_demand", "", DATA_INT, msg.payload[1], NULL);
break;
}
default: /* Unknown command */
data_append(data, "unknown", "", DATA_FORMAT, "%04x", DATA_INT, msg.command, NULL);
break;
}
#ifdef _DEBUG
data = add_hex_string(data, "Packet", packet.bb[row], packet.bits_per_row[row] / 8);
@ -433,6 +446,12 @@ static int honeywell_cm921_decode(r_device *decoder, bitbuffer_t *bitbuffer)
data = data_append(data, "# man errors", "", DATA_INT, man_errors, NULL);
#endif
/* clang-format off */
data = data_append(data,
"mic", "Integrity", DATA_STRING, "CHECKSUM",
NULL);
/* clang-format on */
decoder_output_data(decoder, data);
return 1;