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