23constexpr uint8_t POSITION_PERCENT_MAX = 100;
26constexpr uint8_t EXECUTE_ORIGINATOR = ORIGINATOR_USER_REMOTE;
40constexpr uint8_t EXECUTE_ACEI =
41 (ACEI_LEVEL_USER_DEFAULT << ACEI_LEVEL_SHIFT) | (1 << ACEI_EXTENDED_SHIFT) | ACEI_VALID_BIT;
57constexpr uint8_t EXECUTE_ACEI_FORCE_OPEN =
58 (ACEI_LEVEL_PROTECTION_HUMAN << ACEI_LEVEL_SHIFT) | (1 << ACEI_EXTENDED_SHIFT) | ACEI_VALID_BIT;
85constexpr uint8_t KEY_EXTRACTION_DISCOVER_RESP_FLAGS = 0xDD;
93constexpr uint16_t KEY_EXTRACTION_DISCOVER_RESP_TIMESTAMP = 0x000E;
103constexpr uint16_t LOW_BYTE_MASK = 0xFF;
105constexpr size_t EXECUTE_PAYLOAD_SIZE = 8;
107constexpr uint8_t EXECUTE_POSITION_LAYOUT_FLAG = 0x80;
109constexpr uint8_t EXECUTE_POSITION_PROFILE = 0x06;
117constexpr uint8_t EXECUTE_PROFILE_SILENT = 0x05;
119constexpr size_t EXECUTE_SPECIAL_PAYLOAD_SIZE = 6;
121constexpr uint16_t MAIN_PARAMETER_LOW_BYTE_MASK = 0xFF;
124constexpr size_t EXECUTE_SECOND_BITMAP_FP_PAYLOAD_SIZE = 8;
126constexpr uint8_t EXECUTE_FP9_BITMAP = 0x80;
128constexpr uint8_t EXECUTE_FP16_BITMAP = 0x01;
132constexpr size_t PRIVATE2_LONG_PAYLOAD_SIZE = 6;
137constexpr uint8_t PRIVATE2_EXTENDED_BLOCK_FLAG = 0x80;
139constexpr uint8_t STATUS_UPDATE_ACK_PAYLOAD[] = {0x05, 0x00};
141constexpr uint8_t SET_CONFIG1_STATUS_BROADCAST_PAYLOAD[] = {0xE0, 0x10, 0x0A, 0x08, 0x00};
143constexpr uint8_t IDENTIFY_PARAMETER = 0xFF;
147bool build_main_parameter_execute(
IoFrame &f,
const uint8_t *own,
const uint8_t *dst,
bool low_power, uint8_t acei,
148 uint8_t main_high, uint8_t main_low) {
152 const uint8_t payload[EXECUTE_SPECIAL_PAYLOAD_SIZE] = {EXECUTE_ORIGINATOR, acei, main_high, main_low, 0x00, 0x00};
153 return set_cmd(f, CMD_EXECUTE, payload,
sizeof(payload));
158inline std::array<uint8_t, EXECUTE_PAYLOAD_SIZE> make_position_payload(uint8_t acei, uint8_t position,
159 bool silent =
false) {
162 return {EXECUTE_ORIGINATOR, acei,
static_cast<uint8_t
>(POSITION_WIRE_SCALE * position), 0x00,
163 EXECUTE_POSITION_LAYOUT_FLAG, POS_FAVORITE, silent ? EXECUTE_PROFILE_SILENT : EXECUTE_POSITION_PROFILE, 0x00};
171constexpr uint8_t ONEWAY_EXECUTE_PARAMS_SIZE = 6;
177constexpr uint8_t ONEWAY_SEQUENCE_SIZE = 2;
182constexpr uint8_t ONEWAY_EXECUTE_MAC_SPAN_SIZE = 1 + ONEWAY_EXECUTE_PARAMS_SIZE;
185constexpr uint8_t ONEWAY_EXECUTE_SEQUENCE_OFFSET = ONEWAY_EXECUTE_PARAMS_SIZE;
186constexpr uint8_t ONEWAY_EXECUTE_MAC_OFFSET = ONEWAY_EXECUTE_SEQUENCE_OFFSET + ONEWAY_SEQUENCE_SIZE;
189constexpr uint8_t ONEWAY_EXECUTE_PAYLOAD_SIZE = ONEWAY_EXECUTE_MAC_OFFSET + HMAC_SIZE;
191constexpr uint8_t ONEWAY_EXECUTE_FP1 = 0x00;
192constexpr uint8_t ONEWAY_EXECUTE_FP2 = 0x00;
209void init_1w_broadcast_frame(
IoFrame &f,
const uint8_t src[NODE_ID_SIZE],
DeviceType target_type) {
212 uint8_t dst[NODE_ID_SIZE];
224bool build_1w_execute(
IoFrame &f,
const uint8_t src[NODE_ID_SIZE],
DeviceType target_type, uint8_t main0, uint8_t main1,
225 uint16_t sequence,
const uint8_t controller_key[AES_KEY_SIZE], uint8_t acei,
bool broadcast_all) {
226 uint8_t payload[ONEWAY_EXECUTE_PAYLOAD_SIZE] = {EXECUTE_ORIGINATOR, acei, main0, main1, ONEWAY_EXECUTE_FP1,
232 uint8_t mac_span[ONEWAY_EXECUTE_MAC_SPAN_SIZE];
233 mac_span[0] = CMD_EXECUTE;
234 memcpy(&mac_span[1], payload, ONEWAY_EXECUTE_PARAMS_SIZE);
240 &payload[ONEWAY_EXECUTE_MAC_OFFSET]))
243 payload[ONEWAY_EXECUTE_SEQUENCE_OFFSET] =
static_cast<uint8_t
>(sequence >> BITS_PER_BYTE);
244 payload[ONEWAY_EXECUTE_SEQUENCE_OFFSET + 1] =
static_cast<uint8_t
>(sequence);
251 return set_cmd(f, CMD_EXECUTE, payload,
sizeof(payload));
259constexpr uint8_t ONEWAY_ADD_ENC_KEY_OFFSET = 0;
260constexpr uint8_t ONEWAY_ADD_MANUFACTURER_OFFSET = AES_KEY_SIZE;
261constexpr uint8_t ONEWAY_ADD_DATA_OFFSET = AES_KEY_SIZE + 1;
262constexpr uint8_t ONEWAY_ADD_SEQUENCE_OFFSET = AES_KEY_SIZE + 2;
263constexpr uint8_t ONEWAY_ADD_PAYLOAD_SIZE = AES_KEY_SIZE + 4;
266constexpr uint8_t ONEWAY_ADD_DATA_VALUE = 0x01;
269constexpr uint8_t ONEWAY_ADD_MAC_SPAN_SIZE = 1 + AES_KEY_SIZE;
273constexpr uint8_t ONEWAY_REMOVE_DATA_OFFSET = 0;
274constexpr uint8_t ONEWAY_REMOVE_SEQUENCE_OFFSET = 1;
275constexpr uint8_t ONEWAY_REMOVE_MAC_OFFSET = 3;
276constexpr uint8_t ONEWAY_REMOVE_PAYLOAD_SIZE = 9;
278constexpr uint8_t ONEWAY_REMOVE_DATA_VALUE = 0x00;
281constexpr uint8_t ONEWAY_REMOVE_MAC_SPAN_SIZE = 2;
285bool create_no_payload_request(
IoFrame &f,
const uint8_t *own,
const uint8_t *dst,
bool low_power, uint8_t cmd) {
297 if (position > POSITION_PERCENT_MAX)
302 const auto payload = make_position_payload(EXECUTE_ACEI, position, silent);
303 return set_cmd(f, CMD_EXECUTE, payload.
data(), payload.size());
314 uint8_t main_byte = 0;
315 uint8_t modifier_byte = 0;
318 main_byte = POS_STOP;
319 modifier_byte = 0x00;
322 main_byte = POS_FAVORITE;
323 modifier_byte = 0x00;
326 main_byte = POS_FAVORITE;
327 modifier_byte = POS_VENT_MODIFIER;
341 const uint8_t extended[EXECUTE_PAYLOAD_SIZE] = {
342 EXECUTE_ORIGINATOR, EXECUTE_ACEI, main_byte, modifier_byte, EXECUTE_POSITION_LAYOUT_FLAG,
343 POS_FAVORITE, EXECUTE_PROFILE_SILENT, 0x00};
344 return set_cmd(f, CMD_EXECUTE, extended,
sizeof(extended));
346 const uint8_t payload[EXECUTE_SPECIAL_PAYLOAD_SIZE] = {EXECUTE_ORIGINATOR, EXECUTE_ACEI, main_byte,
347 modifier_byte, 0x00, 0x00};
348 return set_cmd(f, CMD_EXECUTE, payload,
sizeof(payload));
364 const auto payload = make_position_payload(EXECUTE_ACEI_FORCE_OPEN, open_position);
365 return set_cmd(f, CMD_EXECUTE, payload.
data(), payload.size());
370 bool force_open_priority) {
371 if (position > POSITION_PERCENT_MAX)
373 return build_main_parameter_execute(f, own, dst, low_power,
374 force_open_priority ? EXECUTE_ACEI_FORCE_OPEN : EXECUTE_ACEI,
375 static_cast<uint8_t
>(POSITION_WIRE_SCALE * position), 0x00);
380 uint16_t main_parameter) {
381 return build_main_parameter_execute(f, own, dst, low_power, EXECUTE_ACEI,
382 static_cast<uint8_t
>(main_parameter >> BITS_PER_BYTE),
383 static_cast<uint8_t
>(main_parameter & MAIN_PARAMETER_LOW_BYTE_MASK));
389 uint16_t main_parameter, uint8_t second_bitmap, uint16_t value) {
394 const uint8_t payload[EXECUTE_SECOND_BITMAP_FP_PAYLOAD_SIZE] = {
397 static_cast<uint8_t
>(main_parameter >> BITS_PER_BYTE),
398 static_cast<uint8_t
>(main_parameter & MAIN_PARAMETER_LOW_BYTE_MASK),
401 static_cast<uint8_t
>(value >> BITS_PER_BYTE),
402 static_cast<uint8_t
>(value & MAIN_PARAMETER_LOW_BYTE_MASK)};
403 return set_cmd(f, CMD_EXECUTE, payload,
sizeof(payload));
408 uint16_t main_parameter, uint16_t mode_flags) {
414 uint16_t main_parameter, uint16_t fp16) {
420 if (level > HEATING_LEVEL_MAX)
422 return build_main_parameter_execute(f, own, dst, low_power, EXECUTE_ACEI, HEATING_LEVEL_MP_PREFIX, level);
428 uint16_t sequence,
const uint8_t controller_key[AES_KEY_SIZE], uint8_t acei,
429 bool broadcast_all) {
430 if (position > POSITION_PERCENT_MAX)
432 return build_1w_execute(f, src, target_type,
static_cast<uint8_t
>(POSITION_WIRE_SCALE * position), 0x00, sequence,
433 controller_key, acei, broadcast_all);
443 uint16_t sequence,
const uint8_t controller_key[AES_KEY_SIZE], uint8_t acei,
444 bool broadcast_all) {
452 main0 = POS_FAVORITE;
455 main0 = POS_FAVORITE;
456 main1 = POS_VENT_MODIFIER;
461 return build_1w_execute(f, src, target_type, main0, main1, sequence, controller_key, acei, broadcast_all);
469 uint16_t sequence,
const uint8_t controller_key[AES_KEY_SIZE],
bool with_mac) {
470 uint8_t payload[ONEWAY_ADD_PAYLOAD_SIZE] = {0};
477 payload[ONEWAY_ADD_MANUFACTURER_OFFSET] = manufacturer;
478 payload[ONEWAY_ADD_DATA_OFFSET] = ONEWAY_ADD_DATA_VALUE;
479 payload[ONEWAY_ADD_SEQUENCE_OFFSET] =
static_cast<uint8_t
>(sequence >> BITS_PER_BYTE);
480 payload[ONEWAY_ADD_SEQUENCE_OFFSET + 1] =
static_cast<uint8_t
>(sequence);
487 uint8_t mac_span[ONEWAY_ADD_MAC_SPAN_SIZE];
488 mac_span[0] = CMD_ONEWAY_ADD_CONTROLLER;
489 memcpy(&mac_span[1], &payload[ONEWAY_ADD_ENC_KEY_OFFSET], AES_KEY_SIZE);
491 uint8_t mac[HMAC_SIZE];
497 init_1w_broadcast_frame(f, src, target_type);
499 if (!
set_cmd(f, CMD_ONEWAY_ADD_CONTROLLER, payload,
sizeof(payload)))
507 memcpy(f.
mac, mac, HMAC_SIZE);
515 const uint8_t controller_key[AES_KEY_SIZE]) {
516 uint8_t payload[ONEWAY_REMOVE_PAYLOAD_SIZE] = {0};
517 payload[ONEWAY_REMOVE_DATA_OFFSET] = ONEWAY_REMOVE_DATA_VALUE;
518 payload[ONEWAY_REMOVE_SEQUENCE_OFFSET] =
static_cast<uint8_t
>(sequence >> BITS_PER_BYTE);
519 payload[ONEWAY_REMOVE_SEQUENCE_OFFSET + 1] =
static_cast<uint8_t
>(sequence);
523 uint8_t mac_span[ONEWAY_REMOVE_MAC_SPAN_SIZE] = {CMD_ONEWAY_REMOVE, payload[ONEWAY_REMOVE_DATA_OFFSET]};
526 if (!
crypto::create_1w_hmac(mac_span,
sizeof(mac_span), sequence, controller_key, &payload[ONEWAY_REMOVE_MAC_OFFSET]))
529 init_1w_broadcast_frame(f, src, target_type);
531 return set_cmd(f, CMD_ONEWAY_REMOVE, payload,
sizeof(payload));
543 uint8_t d[3] = {function_id, sub_index, 0x00};
544 return set_cmd(f, CMD_PRIVATE, d,
sizeof(d));
553 return create_no_payload_request(f, own, dst, low_power, CMD_GET_NAME);
559 return create_no_payload_request(f, own, dst, low_power, CMD_GET_GENERAL_INFO3);
564 return create_no_payload_request(f, own, dst, low_power, CMD_GET_INFO1);
569 return create_no_payload_request(f, own, dst, low_power, CMD_GET_INFO2);
573 const uint8_t payload[DEVICE_NAME_WRITE_PAYLOAD_SIZE]) {
577 return set_cmd(f, CMD_SET_NAME, payload, DEVICE_NAME_WRITE_PAYLOAD_SIZE);
585 const uint8_t payload[2] = {ORIGINATOR_USER_REMOTE, IDENTIFY_PARAMETER};
586 return set_cmd(f, CMD_IDENTIFY, payload,
sizeof(payload));
594 size_t payload_len) {
595 if (payload ==
nullptr || payload_len == 0 || payload_len > FRAME_MAX_DATA_SIZE)
600 return set_cmd(f, CMD_WRITE_PRIVATE, payload,
static_cast<uint8_t
>(payload_len));
609 auto const tilt_value =
610 static_cast<uint16_t
>((POSITION_PERCENT_MAX - tilt_percent) * STATUS_POS_MAX / POSITION_PERCENT_MAX);
611 uint8_t d[EXECUTE_PAYLOAD_SIZE] = {EXECUTE_ORIGINATOR,
615 STATUS_TILT_SELECTOR,
616 static_cast<uint8_t
>(tilt_value >> BITS_PER_BYTE),
617 static_cast<uint8_t
>(tilt_value),
619 return set_cmd(f, CMD_EXECUTE, d,
sizeof(d));
624 uint8_t position, uint8_t tilt_percent) {
625 if (position > POSITION_PERCENT_MAX)
631 auto const tilt_value =
632 static_cast<uint16_t
>((POSITION_PERCENT_MAX - tilt_percent) * STATUS_POS_MAX / POSITION_PERCENT_MAX);
633 uint8_t d[EXECUTE_PAYLOAD_SIZE] = {EXECUTE_ORIGINATOR,
635 static_cast<uint8_t
>(2 * position),
637 STATUS_TILT_SELECTOR,
638 static_cast<uint8_t
>(tilt_value >> BITS_PER_BYTE),
639 static_cast<uint8_t
>(tilt_value),
641 return set_cmd(f, CMD_EXECUTE, d,
sizeof(d));
653 uint8_t block, uint8_t function_id) {
657 uint8_t d[4] = {function_id, selector, block, 0x00};
658 return set_cmd(f, CMD_PRIVATE, d,
sizeof(d));
667 uint8_t d[2 + STATUS_MP_FP_PARAM_COUNT + 1] = {PRIVATE_STATUS_MP_FP, STATUS_MP_FP_FPI1_MASK};
668 for (uint8_t i = 0; i < STATUS_MP_FP_PARAM_COUNT; i++)
669 d[2 + i] = STATUS_MP_FP_PARAM_TYPE;
670 d[2 + STATUS_MP_FP_PARAM_COUNT] = STATUS_MP_FP_FPI2;
671 return set_cmd(f, CMD_PRIVATE, d,
sizeof(d));
680 const uint8_t d[LIMITATION_REQ_DATA_SIZE] = {
static_cast<uint8_t
>(type), LIMITATION_PARAM_MP, 0x00};
681 return set_cmd(f, CMD_LIMITATION_STATUS_REQ, d,
sizeof(d));
706 uint8_t d[PRIVATE2_LONG_PAYLOAD_SIZE] = {POS_UNKNOWN, 0x00, PRIVATE2_EXTENDED_BLOCK_FLAG,
707 POS_FAVORITE, modifier, 0x00};
708 return set_cmd(f, CMD_PRIVATE2, d,
sizeof(d));
710 uint8_t d[4] = {POS_FAVORITE, modifier, 0x00, 0x00};
711 return set_cmd(f, CMD_PRIVATE2, d,
sizeof(d));
719 set_dst(f, BROADCAST_DISCOVER);
721 return set_cmd(f, CMD_DISCOVER_REQ);
732 bool ack_capable,
bool payload_enabled, uint8_t payload,
const uint8_t *system_key) {
737 f.
ctrl1 |= CTRL1_ACK;
742 case CMD_DISCOVER_REQ:
743 return set_cmd(f, CMD_DISCOVER_REQ);
745 case CMD_DISCOVER_SPE_REQ: {
746 if (system_key ==
nullptr)
748 uint8_t nonce[HMAC_SIZE];
755 uint8_t hmac[HMAC_SIZE];
758 uint8_t payload_buf[HMAC_SIZE * 2];
759 memcpy(payload_buf, nonce, HMAC_SIZE);
760 memcpy(payload_buf + HMAC_SIZE, hmac, HMAC_SIZE);
761 return set_cmd(f, CMD_DISCOVER_SPE_REQ, payload_buf,
sizeof(payload_buf));
764 case CMD_DISCOVER_ALT_REQ: {
767 return set_cmd(f, CMD_DISCOVER_ALT_REQ, &payload, 1);
768 return set_cmd(f, CMD_DISCOVER_ALT_REQ);
779 uint8_t manufacturer_id) {
784 uint8_t payload[DISCOVERY_RESP_FULL_SIZE] = {0};
788 memcpy(&payload[DISCOVERY_RESP_BACKBONE_OFFSET], own, NODE_ID_SIZE);
789 payload[DISCOVERY_RESP_MANUFACTURER_OFFSET] = manufacturer_id;
791 payload[DISCOVERY_RESP_FLAGS_OFFSET] = KEY_EXTRACTION_DISCOVER_RESP_FLAGS;
792 payload[DISCOVERY_RESP_TIMESTAMP_OFFSET] =
793 static_cast<uint8_t
>(KEY_EXTRACTION_DISCOVER_RESP_TIMESTAMP >> BITS_PER_BYTE);
794 payload[DISCOVERY_RESP_TIMESTAMP_OFFSET + 1] =
795 static_cast<uint8_t
>(KEY_EXTRACTION_DISCOVER_RESP_TIMESTAMP & LOW_BYTE_MASK);
796 return set_cmd(f, CMD_DISCOVER_RESP, payload,
sizeof(payload));
827 bool low_power,
bool ack) {
833 f.
ctrl1 |= CTRL1_ACK;
856 uint8_t out_key[AES_KEY_SIZE]) {
857 const uint8_t key_init_cmd = CMD_KEY_INIT;
858 return crypto::crypt_key(&key_init_cmd, 1, challenge, transfer_payload, out_key);
863 const uint8_t challenge[HMAC_SIZE]) {
865 f.
ctrl1 |= CTRL1_PRIORITY;
868 return set_cmd(f, CMD_LAUNCH_KEY_TRANSFER, challenge, HMAC_SIZE);
883 uint8_t out_key[AES_KEY_SIZE]) {
886 uint8_t iv_data[FRAME_MAX_SIZE];
900 const uint8_t key[AES_KEY_SIZE],
const uint8_t challenge[HMAC_SIZE]) {
908 uint8_t enc_key[AES_KEY_SIZE];
911 return set_cmd(f, CMD_KEY_TRANSFER, enc_key, AES_KEY_SIZE);
917 const uint8_t challenge[HMAC_SIZE],
bool start,
bool low_power) {
921 return set_cmd(f, CMD_CHALLENGE_REQ, challenge, HMAC_SIZE);
938 uint8_t challenge[HMAC_SIZE];
947 const uint8_t challenge[HMAC_SIZE]) {
955 const uint8_t challenge[HMAC_SIZE],
const IoFrame &origin,
const uint8_t *key,
956 bool end,
bool low_power) {
960 uint8_t hmac[HMAC_SIZE];
963 return set_cmd(f, CMD_CHALLENGE_RESP, hmac, HMAC_SIZE);
969 uint8_t hmac[HMAC_SIZE]) {
970 uint8_t transcript[FRAME_MAX_SIZE];
977 const uint8_t *key) {
978 uint8_t transcript[FRAME_MAX_SIZE];
986 const IoFrame &origin,
const uint8_t *key) {
1009 return set_cmd(f, CMD_NODE_VERIFY_RESP, own, NODE_ID_SIZE);
1016 const uint8_t challenge[HMAC_SIZE],
const IoFrame &origin,
const uint8_t *key) {
1029 return set_cmd(f, CMD_STATUS_UPDATE_RESP, STATUS_UPDATE_ACK_PAYLOAD,
sizeof(STATUS_UPDATE_ACK_PAYLOAD));
1042 return set_cmd(f, CMD_SET_CONFIG1, SET_CONFIG1_STATUS_BROADCAST_PAYLOAD,
1043 sizeof(SET_CONFIG1_STATUS_BROADCAST_PAYLOAD));
void generate_challenge(uint8_t out[HMAC_SIZE])
Generate 6 random bytes for a challenge using the ESP32 hardware RNG.
bool create_hmac(const uint8_t *data, uint8_t len, const uint8_t challenge[HMAC_SIZE], const uint8_t key[AES_KEY_SIZE], uint8_t hmac[HMAC_SIZE])
Create a 6-byte HMAC for authentication (proprietary IO-Homecontrol scheme).
bool crypt_1w_key(const uint8_t node[NODE_ID_SIZE], const uint8_t in[AES_KEY_SIZE], uint8_t out[AES_KEY_SIZE])
Encrypt or decrypt a 1W controller key during add-controller key adoption (CMD 0x30).
bool verify_hmac(const uint8_t *data, uint8_t len, const uint8_t hmac[HMAC_SIZE], const uint8_t challenge[HMAC_SIZE], const uint8_t key[AES_KEY_SIZE])
Verify a received HMAC using constant-time comparison.
bool crypt_key(const uint8_t *data, uint8_t len, const uint8_t challenge[HMAC_SIZE], const uint8_t in[AES_KEY_SIZE], uint8_t out[AES_KEY_SIZE])
Encrypt or decrypt a system key during pairing.
bool create_1w_hmac(const uint8_t *data, uint8_t len, uint16_t sequence, const uint8_t controller_key[AES_KEY_SIZE], uint8_t hmac[HMAC_SIZE])
Create the 6-byte authenticator for a 1W frame.
bool create_force_open(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power, uint8_t open_position)
Build a force-open execute frame (0x00): an ordinary position command to the device's wire-scale "ful...
bool set_cmd(IoFrame &f, uint8_t cmd, const uint8_t *params, uint8_t params_len)
Set command and payload.
bool create_identify(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power)
Build an authenticated device-identify request (0x1E).
static uint8_t frame_transcript(const IoFrame &frame, uint8_t out[FRAME_MAX_SIZE])
Copy a frame's command byte and payload into out: the bytes a challenge response (0x3D) authenticates...
bool create_get_name(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power)
Build a get-name request (0x50).
void encode_packed_device_type(DeviceType type, uint8_t subtype, uint8_t &type_msb, uint8_t &type_subtype)
Encode a DeviceType/subtype pair into the two-byte packed metadata format used by discovery responses...
static bool create_device_terminal_ack(IoFrame &f, const uint8_t *own, const uint8_t *dst, uint8_t cmd)
Build a bare device→hub terminal acknowledgement: no payload, END set, START and LOW_POWER clear.
bool create_get_status(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power)
Build a get-status request (0x03). The device responds with its current position.
bool create_set_name(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power, const uint8_t payload[DEVICE_NAME_WRITE_PAYLOAD_SIZE])
Build an authenticated set-name request (0x52) using a fixed zero-padded Latin-1 payload.
bool create_write_private(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power, const uint8_t *payload, size_t payload_len)
Build a generic CMD_WRITE_PRIVATE (0x20) frame around a caller-supplied payload — the one builder beh...
void encode_broadcast_address(DeviceType type, uint8_t out[NODE_ID_SIZE])
Encode a device type into its typed-broadcast destination address — the exact inverse of broadcast_ta...
bool create_node_verify_resp_device_role(IoFrame &f, const uint8_t *own, const uint8_t *dst)
Build an address response (0x37) — device side, used only by the key-extraction responder.
bool create_private2_read(IoFrame &f, const uint8_t *own, const uint8_t *dst, uint8_t modifier, bool long_form, bool low_power)
Build a CMD_PRIVATE2 (0x0C) request in either of the two field-observed shapes.
bool verify_challenge_resp(const IoFrame &origin, const uint8_t challenge[HMAC_SIZE], const uint8_t mac[HMAC_SIZE], const uint8_t *key)
Check a device's 0x3D for origin, in constant time (crypto::verify_hmac()).
bool create_discover_resp(IoFrame &f, const uint8_t *own, const uint8_t *dst, DeviceType type, uint8_t subtype, uint8_t manufacturer_id)
Build a discovery response (0x29) — device side, used only by the key-extraction responder.
bool create_limitation_status_read(IoFrame &f, const uint8_t *own, const uint8_t *dst, LimitationType type, bool low_power)
Build the limitation read (see the header for the evidence and the read-only guarantee).
CoverCommand
Named device commands for cover-type actuators.
@ FAVORITE
Move to stored favorite/"My" position.
@ STOP
Stop movement immediately.
@ VENT
Move to ventilation position (window-type devices).
bool create_1w_remove_controller(IoFrame &f, const uint8_t src[NODE_ID_SIZE], DeviceType target_type, uint16_t sequence, const uint8_t controller_key[AES_KEY_SIZE])
Build a 1W remove-controller frame (CMD 0x39).
static bool create_challenge_req_framed(IoFrame &f, const uint8_t *dst, const uint8_t *src, const uint8_t challenge[HMAC_SIZE], bool start, bool low_power)
Build a challenge request (0x3C) with caller-chosen framing bits.
bool recover_system_key_from_transfer(const uint8_t transfer_payload[AES_KEY_SIZE], const uint8_t challenge[HMAC_SIZE], uint8_t out_key[AES_KEY_SIZE])
Recover the system key from a CMD_KEY_TRANSFER payload.
bool create_execute_heating_level(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power, uint8_t level)
Build a heating-level execute frame (0x00). See proto_commands.h.
bool create_discover_confirm(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power, bool ack)
Build a discovery-confirm request (0x2C) — controller side, sent directly to a freshly-discovered dev...
bool create_get_info1(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power)
Build a CMD_GET_INFO1 (0x54) request. No payload. See proto_commands.h for the evidence note.
DeviceType
IO-Homecontrol node types.
@ UNKNOWN
Unknown/unspecified device.
bool create_execute_position_and_tilt(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power, uint8_t position, uint8_t tilt_percent)
Build a combined position-and-tilt execute command (0x00) — setClosureAndOrientation.
bool create_1w_execute_command(IoFrame &f, const uint8_t src[NODE_ID_SIZE], DeviceType target_type, CoverCommand cmd, uint16_t sequence, const uint8_t controller_key[AES_KEY_SIZE], uint8_t acei, bool broadcast_all)
Build a 1W named-command execute frame (CMD 0x00) targeting a device class.
void init_frame(IoFrame &f, bool is_2w, bool start, bool end, bool low_power)
Initialize an IoFrame header (ctrl0/ctrl1) with flags.
bool create_key_confirm(IoFrame &f, const uint8_t *own, const uint8_t *dst)
Build a key-confirm frame (0x33) — device side, used only by the key-extraction responder.
bool create_execute_tilt(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power, uint8_t tilt_percent)
Build a tilt execute command (0x00) for devices that support slat angle control.
bool create_set_config1(IoFrame &f, const uint8_t *own, const uint8_t *dst)
Build a set-config command (0x6F) to tell the device to automatically send status updates when contro...
bool create_execute_heating_main_parameter(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power, uint16_t main_parameter)
Build a temperature / alias execute frame (0x00), main parameter only. See proto_commands....
bool create_challenge_resp(IoFrame &f, const uint8_t *dst, const uint8_t *src, const uint8_t challenge[HMAC_SIZE], const IoFrame &origin, const uint8_t *key)
Build a challenge response (0x3D) proving we know the system key.
void set_dst(IoFrame &f, const uint8_t id[NODE_ID_SIZE])
Set destination node ID.
bool create_key_init(IoFrame &f, const uint8_t *own, const uint8_t *dst)
Build a key-init request (0x31) to start the pairing key exchange with a discovered device.
bool create_execute_position(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power, uint8_t position, bool silent)
Build a position execute command (0x00) to move a device to a numeric position.
bool create_general_info3(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power)
Build a CMD_GET_GENERAL_INFO3 (0x58) request.
bool create_execute_heating_with_fp16(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power, uint16_t main_parameter, uint16_t fp16)
Build a temperature / alias execute frame (0x00) carrying functional parameter 16....
bool create_status_mp_fp_read(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power)
Build the KLF-style status read of the main parameter plus FP1-FP7 (see the header for the evidence).
bool create_get_info2(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power)
Build a CMD_GET_INFO2 (0x56) request. No payload. See proto_commands.h for the evidence note.
bool create_execute_command(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power, CoverCommand cmd, bool silent)
Build a named-command execute frame (0x00) for STOP, FAVORITE, or VENT.
static bool build_execute_with_second_bitmap_fp(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power, uint16_t main_parameter, uint8_t second_bitmap, uint16_t value)
Build a temperature / alias execute frame (0x00) carrying one functional parameter of the second bitm...
bool challenge_resp_hmac(const IoFrame &origin, const uint8_t challenge[HMAC_SIZE], const uint8_t *key, uint8_t hmac[HMAC_SIZE])
The 0x3D MAC for origin.
bool recover_key_from_pull(const IoFrame &request, const uint8_t transfer_payload[AES_KEY_SIZE], uint8_t out_key[AES_KEY_SIZE])
Recover a device's key from its answer to our 0x38.
bool create_discover_confirm_ack(IoFrame &f, const uint8_t *own, const uint8_t *dst)
Build a discovery-confirm acknowledgement (0x2D) — device side, used only by the key-extraction respo...
bool create_challenge_req_device_role(IoFrame &f, const uint8_t *dst, const uint8_t *src, const uint8_t challenge[HMAC_SIZE])
Build a device-role challenge request (0x3C) — device side, used only by the key-extraction responder...
static bool create_challenge_resp_framed(IoFrame &f, const uint8_t *dst, const uint8_t *src, const uint8_t challenge[HMAC_SIZE], const IoFrame &origin, const uint8_t *key, bool end, bool low_power)
Build a challenge response (0x3D) with caller-chosen framing bits.
bool create_discover(IoFrame &f, const uint8_t *own)
Build a discovery broadcast (0x28).
bool create_private_function(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power, uint8_t function_id, uint8_t sub_index)
Build a CMD_PRIVATE (0x03) request for an arbitrary function ID.
bool create_discovery_request(IoFrame &f, const uint8_t *own, uint8_t command, const uint8_t *dst, bool low_power, bool ack_capable, bool payload_enabled, uint8_t payload, const uint8_t *system_key)
Build a configurable discovery request command (0x28, 0x2A, or 0x2E).
bool create_get_status_tilt(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power)
Build a tilt-aware get-status request (0x03) that returns the extended 16-byte tilt payload.
bool create_get_status_extended(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power, uint8_t selector, uint8_t block, uint8_t function_id)
Build an extended CMD_PRIVATE (0x03) request with a selector/block pair — the shape real hubs use for...
LimitationType
On-air selector byte (request data[0]) choosing which resulting limit to read.
bool create_launch_key_transfer(IoFrame &f, const uint8_t *own, const uint8_t *dst, const uint8_t challenge[HMAC_SIZE])
Build a key transfer request (0x38). See proto_commands.h for the frame shape.
bool create_execute_heating_with_mode_flags(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power, uint16_t main_parameter, uint16_t mode_flags)
Build a temperature / alias execute frame (0x00) carrying functional parameter 9. See proto_commands....
bool create_challenge_resp_device_role(IoFrame &f, const uint8_t *dst, const uint8_t *src, const uint8_t challenge[HMAC_SIZE], const IoFrame &origin, const uint8_t *key)
Build a device-role challenge response (0x3D) — device side, used only by the key-extraction responde...
bool create_1w_execute_position(IoFrame &f, const uint8_t src[NODE_ID_SIZE], DeviceType target_type, uint8_t position, uint16_t sequence, const uint8_t controller_key[AES_KEY_SIZE], uint8_t acei, bool broadcast_all)
Build a 1W position execute frame (CMD 0x00) targeting a device class.
bool create_challenge_req(IoFrame &f, const uint8_t *dst, const uint8_t *src, const uint8_t challenge[HMAC_SIZE])
Build a challenge request (0x3C) using a caller-supplied challenge.
bool create_execute_main_parameter(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power, uint8_t position, bool force_open_priority)
Build a main-parameter-only position execute frame (0x00). See proto_commands.h.
bool create_status_update_resp(IoFrame &f, const uint8_t *own, const uint8_t *dst)
Build a status-update acknowledgment (0x72).
bool create_1w_add_controller(IoFrame &f, const uint8_t src[NODE_ID_SIZE], DeviceType target_type, uint8_t manufacturer, uint16_t sequence, const uint8_t controller_key[AES_KEY_SIZE], bool with_mac)
Build a 1W add-controller frame (CMD 0x30).
static bool create_controller_start_frame(IoFrame &f, const uint8_t *own, const uint8_t *dst, uint8_t cmd, bool low_power, bool ack)
Build a bare controller→device start frame: no payload, START set, END clear, LOW_POWER and ACK as th...
bool create_key_transfer(IoFrame &f, IoFrame &old_frame, const uint8_t *dst, const uint8_t *src, const uint8_t key[AES_KEY_SIZE], const uint8_t challenge[HMAC_SIZE])
Build a key-transfer frame (0x32) containing the system key encrypted with the transfer key.
void set_src(IoFrame &f, const uint8_t id[NODE_ID_SIZE])
Set source node ID.
Command builders for the IO‑Homecontrol protocol.
IO-Homecontrol command IDs, result codes and protocol enumerations.
Cryptographic helpers for the IO‑Homecontrol protocol.
Heating profiles: what a climate card offers for each kind of heater, and how.
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 mac[HMAC_SIZE]
Out-of-length authenticator trailer; meaningful only when has_mac.
bool has_mac
True if this frame carries the mac trailer (see struct doc).
uint8_t data_len
Actual length of data.
uint8_t ctrl1
Control byte 1: low power, beacon, etc.