Home IO Control
ESPHome add-on for IO-Homecontrol devices
Loading...
Searching...
No Matches
proto_commands.h
Go to the documentation of this file.
1#pragma once
2
3/// @file proto_commands.h
4/// @brief Command builders for the IO‑Homecontrol protocol.
5/// @ingroup hioc_protocol
6///
7/// This module provides builder functions that populate IoFrame structures for
8/// the various commands used in discovery, pairing, control, and status operations.
9/// All builders follow the same pattern: fill an IoFrame with CTRL0/CTRL1 flags,
10/// addresses, command ID, and optional payload.
11///
12/// Position encoding:
13/// - IO protocol position values: 0 = fully open, 100 = fully closed.
14/// - Special values: POS_STOP (0xD2), POS_FAVORITE (0xD8), POS_UNKNOWN (0xD4).
15/// - The Home Assistant layer maps HA's 1.0=open/0.0=closed to the IO scale via
16/// ha_position = 1.0 - (io_position / 100.0). Some devices (horizontal awnings)
17/// have inverted mapping; see platform_cover.h.
18///
19/// Preamble handling:
20/// - Commands to battery/solar‑powered devices must use LONG_PREAMBLE (1024 bytes)
21/// so the sleeping receiver can detect the frame. Mains‑powered devices use
22/// SHORT_PREAMBLE (8 bytes). See CTRL1_LOW_POWER flag and battery_powered_ field
23/// in IoDevice.
24
25#include "proto_frame.h"
26
27namespace esphome {
28namespace home_io_control {
29
30/// Build an execute command (0x00) to control a device (set position or special).
31/// @param f IoFrame to populate.
32/// @param own Controller's 3‑byte node ID (source address).
33/// @param dst Target device's 3‑byte node ID (destination address).
34/// @param low_power True if target is battery/solar‑powered (uses long preamble).
35/// @param position Desired position: 0–100 (open→closed), or POS_STOP/POS_FAVORITE.
36/// @note For solar/battery devices, low_power must be true to use the 1024‑byte preamble
37/// required for the receiver to wake up. Mains‑powered devices should use false
38/// (8‑byte preamble). See proto_frame.h for LONG_PREAMBLE and SHORT_PREAMBLE.
39/// @return true on success; false if position exceeds limits.
40bool create_execute(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power, uint8_t position);
41
42/// Build a get‑status request (0x03). The device responds with its current position.
43/// @param f IoFrame to populate.
44/// @param own Controller's 3‑byte node ID.
45/// @param dst Target device's 3‑byte node ID.
46/// @return true on success.
47bool create_get_status(IoFrame &f, const uint8_t *own, const uint8_t *dst);
48
49/// Build a get-name request (0x50). The device responds with its stored display name.
50/// @param f IoFrame to populate.
51/// @param own Controller's 3-byte node ID.
52/// @param dst Target device's 3-byte node ID.
53/// @param low_power True if target is battery/solar-powered (uses long preamble).
54/// @return true on success.
55bool create_get_name(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power);
56
57/// Build an authenticated set-name request (0x52) using a fixed zero-padded Latin-1 payload.
58/// @param f IoFrame to populate.
59/// @param own Controller's 3-byte node ID.
60/// @param dst Target device's 3-byte node ID.
61/// @param payload Pre-validated fixed payload produced by encode_device_name_payload().
62/// @return true on success.
63bool create_set_name(IoFrame &f, const uint8_t *own, const uint8_t *dst,
64 const uint8_t payload[DEVICE_NAME_WRITE_PAYLOAD_SIZE]);
65
66/// Build an execute‑tilt command (0x00) for slat angle control.
67/// @param f IoFrame to populate.
68/// @param own Controller node ID.
69/// @param dst Target device node ID.
70/// @param low_power True for long preamble (battery/solar devices).
71/// @param tilt_percent 0 = fully closed, 100 = fully open.
72/// @note This uses the same command (0x00) as position control but with a different
73/// payload format indicating a tilt operation. The receiver infers tilt from
74/// the payload structure. Only devices that advertise tilt support (see
75/// device_supports_tilt in proto_frame.h) will honor this.
76/// @return true on success.
77bool create_execute_tilt(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power, uint8_t tilt_percent);
78
79/// Build a tilt‑aware get‑status request (0x03 with extended payload) that returns
80/// the 16‑byte tilt block in the response.
81/// @param f IoFrame to populate.
82/// @param own Controller node ID.
83/// @param dst Target device node ID.
84/// @return true on success.
85bool create_get_status_tilt(IoFrame &f, const uint8_t *own, const uint8_t *dst);
86
87/// Build a discovery broadcast (0x28). Sent to the broadcast address; only devices
88/// in pairing mode (PROG button pressed) will respond.
89/// @param f IoFrame to populate.
90/// @param own Controller node ID.
91/// @note Destination is BROADCAST_DISCOVER (0x00003B). The device responds with
92/// CMD_DISCOVER_RESP (0x29) containing its node ID and type/subtype. The
93/// controller then switches to point‑to‑point communication for phases 2 and 3.
94/// @return true on success.
95bool create_discover(IoFrame &f, const uint8_t *own);
96
97/// Build a key‑init request (0x31) to start pairing key exchange with a discovered device.
98/// @param f IoFrame to populate.
99/// @param own Controller node ID.
100/// @param dst Discovered device node ID.
101/// @return true on success.
102bool create_key_init(IoFrame &f, const uint8_t *own, const uint8_t *dst);
103
104/// Build a key‑transfer frame (0x32) containing the system key encrypted with the transfer key.
105/// @param f IoFrame to populate.
106/// @param old_frame The key‑init frame (used to derive the encryption IV).
107/// @param dst Target device node ID.
108/// @param src Controller node ID.
109/// @param key The 16‑byte system key to transfer.
110/// @param challenge 6‑byte challenge received from device in its 0x3C response.
111/// @note The system key is obfuscated via the XOR‑AES construction in crypt_key().
112/// The transfer key (hardcoded in proto_frame.h) is the same for all IO‑Homecontrol
113/// devices worldwide; its purpose is to protect the system key in transit during
114/// initial pairing. Once transferred, the device uses the system key for all
115/// subsequent authenticated exchanges.
116/// @return true on success.
117bool create_key_transfer(IoFrame &f, IoFrame &old_frame, const uint8_t *dst, const uint8_t *src,
118 const uint8_t key[AES_KEY_SIZE], const uint8_t challenge[HMAC_SIZE]);
119
120/// Build a challenge request (0x3C) containing 6 random bytes. Used when we need to
121/// authenticate an incoming request from a device.
122/// @param f IoFrame to populate.
123/// @param dst Target device node ID (device we're challenging).
124/// @param src Controller node ID.
125/// @return true on success.
126bool create_challenge_req(IoFrame &f, const uint8_t *dst, const uint8_t *src);
127
128/// Build a challenge response (0x3D) proving we know the system key.
129/// HMAC is computed over [original_command_id + original_data] using the challenge.
130/// @param f IoFrame to populate.
131/// @param dst Target device node ID.
132/// @param src Controller node ID.
133/// @param challenge 6‑byte challenge from the device.
134/// @param origin Original request frame that triggered the challenge.
135/// @param key System key (16 bytes).
136/// @note The HMAC derivation uses the challenge as IV salt; see create_hmac() in
137/// proto_crypto.h for the exact construction. This frame authenticates the
138/// controller to the device for the current exchange.
139/// @return true on success.
140bool create_challenge_resp(IoFrame &f, const uint8_t *dst, const uint8_t *src, const uint8_t challenge[HMAC_SIZE],
141 const IoFrame &origin, const uint8_t *key);
142
143/// Build a status‑update acknowledgment (0x72). Sent after authenticating a device's
144/// status update; broadcast on all 3 channels for reliability.
145/// @param f IoFrame to populate.
146/// @param own Controller node ID.
147/// @param dst Device node ID that sent the update.
148/// @return true on success.
149bool create_status_update_resp(IoFrame &f, const uint8_t *own, const uint8_t *dst);
150
151/// Build a set‑config command (0x6F) telling the device to automatically send
152/// status updates when controlled by any remote.
153/// @param f IoFrame to populate.
154/// @param own Controller node ID.
155/// @param dst Target device node ID.
156/// @note This configures the device to emit CMD_STATUS_UPDATE (0x71) frames whenever
157/// it is controlled by any remote (including the paired controller). This enables
158/// HA to receive unsolicited position updates. The controller must still
159/// authenticate the status update using the inbound auth flow (hub_exchange.h).
160/// @todo Confirm on real hardware which device families actually honor this SetConfig1
161/// payload and emit unsolicited status updates after pairing.
162/// @return true on success.
163bool create_set_config1(IoFrame &f, const uint8_t *own, const uint8_t *dst);
164
165} // namespace home_io_control
166} // namespace esphome
bool create_get_name(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power)
Build a get-name request (0x50).
bool create_get_status(IoFrame &f, const uint8_t *own, const uint8_t *dst)
Build a get-status request (0x03). The device responds with its current position.
bool create_execute(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power, uint8_t position)
Build an execute command (0x00) to control a device.
static constexpr uint8_t HMAC_SIZE
Authentication HMAC is 6 bytes (truncated AES output).
Definition proto_frame.h:83
static constexpr uint8_t DEVICE_NAME_WRITE_PAYLOAD_SIZE
Fixed write payload: 15 visible chars plus trailing null/padding.
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_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.
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_challenge_req(IoFrame &f, const uint8_t *dst, const uint8_t *src)
Build a challenge request (0x3C) containing 6 random bytes.
bool create_discover(IoFrame &f, const uint8_t *own)
Build a discovery broadcast (0x28).
bool create_set_name(IoFrame &f, const uint8_t *own, const uint8_t *dst, const uint8_t payload[DEVICE_NAME_WRITE_PAYLOAD_SIZE])
Build an authenticated set-name request (0x52) using a fixed zero-padded Latin-1 payload.
static constexpr uint8_t AES_KEY_SIZE
AES-128 key size.
Definition proto_frame.h:84
bool create_get_status_tilt(IoFrame &f, const uint8_t *own, const uint8_t *dst)
Build a tilt-aware get-status request (0x03) that returns the extended 16-byte tilt payload.
bool create_status_update_resp(IoFrame &f, const uint8_t *own, const uint8_t *dst)
Build a status-update acknowledgment (0x72).
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.
IO-Homecontrol 2W protocol definitions.
Parsed IO‑Homecontrol frame (CTRL0/1 + addresses + command + data).