17constexpr size_t DHW_HEADER_LEN = 4;
19constexpr size_t DHW_BYTE_WRITE_LEN = 5;
21constexpr size_t DHW_WATER_TEMPERATURE_LEN = 2;
22constexpr size_t DHW_ENERGY_LEN = 4;
23constexpr size_t DHW_HEATING_STATE_LEN = 5;
25constexpr size_t DHW_HEATING_BYTE = 0;
26constexpr size_t DHW_ELEMENT_BYTE = 2;
27constexpr size_t DHW_COMPRESSOR_BYTE = 3;
28constexpr size_t DHW_FAN_BYTE = 4;
30constexpr float DHW_TENTHS_PER_DEGREE = 10.0F;
32constexpr size_t DHW_STATUS_FPI1_OFFSET = 4;
33constexpr size_t DHW_STATUS_FPI2_OFFSET = 5;
34constexpr size_t DHW_STATUS_VALUES_OFFSET = 6;
36constexpr size_t DHW_FP_VALUE_LEN = 2;
38constexpr uint8_t DHW_FPI2_FP15_BIT =
fp_bitmap_bit(DHW_FP_CAPABILITIES);
39constexpr uint8_t DHW_FPI2_FP16_BIT =
fp_bitmap_bit(DHW_FP_OPERATING_MODE);
41constexpr uint16_t DHW_FP16_FIELD_MASK = 0x3;
42constexpr uint16_t DHW_FP16_FIELD_ON = 1;
43constexpr uint8_t DHW_FP16_BOOST_SHIFT = 8;
44constexpr uint8_t DHW_FP16_AWAY_SHIFT = 10;
47 {DHW_CAPABILITY_BOOST, DHW_FP16_BOOST_SHIFT, DHW_OPERATING_MODE_BOOST_ON, DHW_REG_BOOST_DURATION,
48 DHW_BOOST_START_DURATION},
49 {DHW_CAPABILITY_AWAY, DHW_FP16_AWAY_SHIFT, DHW_OPERATING_MODE_AWAY_ON, DHW_REG_AWAY_DURATION,
50 DHW_AWAY_UNTIL_TURNED_OFF},
52static_assert(
sizeof(DHW_OPERATING_FEATURES) /
sizeof(DHW_OPERATING_FEATURES[0]) ==
54 "one row per DhwOperatingFeature");
59uint16_t read_be16(
const uint8_t *bytes) {
return static_cast<uint16_t
>((bytes[0] << BITS_PER_BYTE) | bytes[1]); }
70 out[2] = DHW_REGISTER_HIGH_BYTE;
72 out[DHW_HEADER_LEN] = value;
73 return DHW_BYTE_WRITE_LEN;
83 if (mode > DHW_MODE_MAX)
85 return encode_dhw_byte_write(DHW_REG_MODE, mode, out);
89 return DHW_OPERATING_FEATURES[
static_cast<size_t>(which)];
99 if (!std::isfinite(celsius) || celsius < DHW_TARGET_MIN_C || celsius > DHW_TARGET_MAX_C)
113 if (value ==
nullptr || len < DHW_WATER_TEMPERATURE_LEN)
115 const auto raw =
static_cast<int16_t
>(read_be16(value));
116 celsius =
static_cast<float>(raw) / DHW_TENTHS_PER_DEGREE;
121 if (value ==
nullptr || len < 1 || value[0] > DHW_MODE_MAX)
123 mode =
static_cast<DhwMode>(value[0]);
128 if (value ==
nullptr || len < DHW_HEATING_STATE_LEN)
130 state.
heating = value[DHW_HEATING_BYTE] != 0;
131 state.
element = value[DHW_ELEMENT_BYTE] != 0;
132 state.
heat_pump = value[DHW_COMPRESSOR_BYTE] != 0 || value[DHW_FAN_BYTE] != 0;
137 if (value ==
nullptr || len < DHW_ENERGY_LEN)
139 wh = (
static_cast<uint32_t
>(value[0]) << (3U * BITS_PER_BYTE)) |
140 (
static_cast<uint32_t
>(value[1]) << (2U * BITS_PER_BYTE)) | (
static_cast<uint32_t
>(value[2]) << BITS_PER_BYTE) |
141 static_cast<uint32_t
>(value[3]);
146 if (value ==
nullptr || len < 1)
158 celsius = std::round(raw * DHW_TENTHS_PER_DEGREE) / DHW_TENTHS_PER_DEGREE;
163 if (reply.
cmd != CMD_PRIVATE_RESP || reply.
data_len < DHW_STATUS_MAIN_PARAMETER_OFFSET + 2)
169 if (reply.
cmd != CMD_PRIVATE_RESP || reply.
data_len < DHW_STATUS_VALUES_OFFSET)
171 const uint8_t fpi2 = reply.
data[DHW_STATUS_FPI2_OFFSET];
172 if (reply.
data[DHW_STATUS_FPI1_OFFSET] != DHW_STATUS_FPI1 || (fpi2 & DHW_FPI2_FP15_BIT) == 0 ||
173 (fpi2 & DHW_FPI2_FP16_BIT) == 0)
177 size_t offset = DHW_STATUS_VALUES_OFFSET;
178 uint16_t capabilities = 0;
179 uint16_t operating_mode = 0;
180 for (
int bit = BITS_PER_BYTE - 1; bit >= 0; bit--) {
181 if ((fpi2 & (1U << bit)) == 0)
183 if (offset + DHW_FP_VALUE_LEN > reply.
data_len)
185 const uint16_t value = read_be16(reply.
data + offset);
186 offset += DHW_FP_VALUE_LEN;
187 if ((1U << bit) == DHW_FPI2_FP15_BIT) {
188 capabilities = value;
189 }
else if ((1U << bit) == DHW_FPI2_FP16_BIT) {
190 operating_mode = value;
constexpr size_t HEATING_REPLY_VALUE_OFFSET
Offset of the first value byte in a register reply: the four header bytes it echoes.
bool decode_dhw_mode(const uint8_t *value, size_t len, DhwMode &mode)
Operating mode from the value of register 0x0006.
constexpr size_t HEATING_PAYLOAD_MAX_SIZE
Largest payload any function produces: a set of a 16-bit value (4-byte header + 2 value bytes).
bool encode_temperature_main_parameter(float celsius, float min_c, float max_c, uint16_t &main_parameter)
Encode a temperature as a main parameter over an encoding range.
DhwMode
Operating mode as register 0x0006 holds it.
@ MANUAL_ECO_OFF
Manual mode with ECO off (the electric element may help).
bool decode_temperature_main_parameter(uint16_t main_parameter, float min_c, float max_c, float &celsius)
Decode a main parameter as a temperature over an encoding range (the inverse of encode_temperature_ma...
bool decode_dhw_heating_state(const uint8_t *value, size_t len, DhwHeatingState &state)
What is heating, from the value of register 0x0021.
DhwOperatingFeature
The two features the operating-mode word switches; dhw_operating_feature_wire() holds their wire fact...
bool encode_dhw_target(float celsius, uint16_t &main_parameter)
Encode a target temperature as a main parameter over DHW_TARGET_MIN_C..DHW_TARGET_MAX_C.
bool decode_dhw_operating_status(const IoFrame &reply, DhwOperatingStatus &status)
Capabilities and operating-mode state from the reply to the status read DHW_STATUS_FPI1 / DHW_STATUS_...
bool heating_reply_echoes_register(const IoFrame &reply, uint8_t reg_hi, uint8_t reg_lo)
Is this frame a register reply that echoes a request for this register?
bool decode_dhw_water_temperature(const uint8_t *value, size_t len, float &celsius)
Water temperature from the value of register 0x0017: signed 16-bit big-endian tenths.
constexpr uint8_t HEATING_DIRECTION_SET
Payload byte at index 1 of a write ("set") request.
size_t encode_dhw_register_read(uint8_t index, uint8_t out[HEATING_PAYLOAD_MAX_SIZE])
Encode a read of one family-0x00 register: 0C 60 00 <index>.
bool decode_dhw_energy_wh(const uint8_t *value, size_t len, uint32_t &wh)
Energy since installation from the value of register 0x0004: unsigned 32-bit big-endian,...
constexpr uint8_t fp_bitmap_bit(uint8_t fp)
Bit of functional parameter fp (1-16) in its bitmap: FPI1 holds FP1-FP8 and FPI2 FP9-FP16,...
size_t encode_dhw_duration_write(DhwOperatingFeature which, uint8_t out[HEATING_PAYLOAD_MAX_SIZE])
Encode the duration write a start of which sends before its FP16 EXECUTE: 0C 61 00 00 01 for boost (D...
bool decode_dhw_register_reply(const IoFrame &reply, uint8_t index, const uint8_t *&value, size_t &len)
The value bytes of a reply to a family-0x00 register read.
constexpr uint8_t HEATING_PAYLOAD_PREFIX
Leading payload byte, common to every function.
size_t encode_heating_register_read(uint8_t reg_hi, uint8_t reg_lo, uint8_t out[HEATING_PAYLOAD_MAX_SIZE])
Encode a read ("get") of an arbitrary register into a CMD_WRITE_PRIVATE (0x20) payload.
bool decode_dhw_target(uint16_t main_parameter, float &celsius)
Target temperature from a main parameter.
size_t encode_dhw_mode_write(uint8_t mode, uint8_t out[HEATING_PAYLOAD_MAX_SIZE])
Encode a write of the operating mode: 0C 61 00 06 <mode>.
const DhwOperatingFeatureWire & dhw_operating_feature_wire(DhwOperatingFeature which)
The wire facts of which: the one place each feature's FP15 bit, FP16 field and duration live.
bool decode_dhw_status_target(const IoFrame &reply, float &celsius)
Target temperature from a status reply (CMD_PRIVATE_RESP) of a domestic hot water heater.
bool decode_dhw_duration(const uint8_t *value, size_t len, uint8_t &duration)
A duration from the value of register 0x0000 or 0x0005: one byte.
IO-Homecontrol command IDs, result codes and protocol enumerations.
Pure codec for a domestic hot water heater: its family-0x00 registers and its target temperature.
Heating profiles: what a climate card offers for each kind of heater, and how.
Fundamental IO-Homecontrol frame and crypto size constants.
What register 0x0021 says is heating.
bool heat_pump
Bytes 3 and 4: the heat pump runs (both were always set together).
bool element
Byte 2: the electric element is on (set during boost only).
bool heating
Byte 0: the heater is heating at all (heat pump, element or both).
The wire facts of one DhwOperatingFeature: what FP15, FP16 and the duration registers say about it.
uint8_t start_duration
Duration a start writes before the FP16 EXECUTE.
uint8_t duration_register
Register that holds its duration (DHW_REG_*_DURATION).
The operating-mode part of the status read DHW_STATUS_FPI1 / DHW_STATUS_FPI2 (its main parameter,...
bool boost
FP16 relaunch field (bits 8-9) is 1: a boost runs.
bool away
FP16 absence field (bits 10-11) is 1: the holiday mode is on.
uint16_t capabilities
FP15: DHW_CAPABILITY_* bits.
Parsed IO‑Homecontrol frame (CTRL0/1 + addresses + command + data).
uint8_t data[FRAME_MAX_DATA_SIZE]
Command parameters (0–23 bytes). Never includes mac.
uint8_t data_len
Actual length of data.