minor: Cleanup flatten Honeywell-CM921 style
This commit is contained in:
parent
3db192c458
commit
3853c31b4a
1 changed files with 187 additions and 168 deletions
|
@ -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;
|
||||
|
|
Loading…
Add table
Reference in a new issue