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, for a non-inverted
14/// device — but this is per-device, not a universal wire constant: IoDevice::inverted
15/// devices (e.g. horizontal awnings) have it backwards (0 = fully closed, 100 = fully
16/// open). Never hardcode "0 = open" in caller code without checking inversion first.
17/// - Use create_execute_position() for numeric positions (0–100).
18/// - Use create_execute_command() for named commands: CoverCommand::STOP,
19/// CoverCommand::FAVORITE, CoverCommand::VENT.
20/// - Use create_force_open() for CoverCommand::FORCE_OPEN — it takes the target "fully open"
21/// position explicitly rather than assuming 0
22/// - The Home Assistant layer maps HA's 1.0=open/0.0=closed to the IO scale via
23/// ha_position = 1.0 - (io_position / 100.0), or the inverted form for IoDevice::inverted
24/// devices; see platform_cover.h.
25///
26/// Low‑power flag and preamble handling:
27/// - Every frame addressed to a specific device sets CTRL1_LOW_POWER: the codebase does
28/// not track per‑device power class, battery/solar devices need the flag on every frame
29/// sent to them, and the golden‑frame corpus shows real devices accepting it.
30/// - The flag does not select the TX preamble. The exchange engine picks the preamble from
31/// frame position: start frames use LONG_PREAMBLE (1024 bytes) so a sleeping receiver
32/// can wake, follow‑up frames use the driver's response_preamble() (exchange_engine.cpp).
33
34#include "proto_codecs.h"
35#include "proto_device_model.h"
36#include "proto_frame.h"
37
38namespace esphome {
39namespace home_io_control {
40
41/// @brief Build a position execute command (0x00) to move a device to a numeric position.
42///
43/// Encodes a 0–100 position value into the standard 8-byte execute payload with
44/// originator, ACEI, and functional parameter fields. The wire encoding doubles
45/// the position value (0→0x00, 100→0xC8).
46/// @param f IoFrame to populate.
47/// @param own Controller's 3‑byte node ID (source address).
48/// @param dst Target device's 3‑byte node ID (destination address).
49/// @param low_power True if target is battery/solar‑powered (sets CTRL1_LOW_POWER).
50/// @param position Desired position 0–100 (0=fully open, 100=fully closed).
51/// @return true on success; false if position > 100.
52bool create_execute_position(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power, uint8_t position);
53
54/// @brief Build a named-command execute frame (0x00) for STOP, FAVORITE, or VENT.
55///
56/// Each maps to a specific wire encoding in the 6-byte special CMD_EXECUTE payload:
57/// - STOP: main=0xD2, modifier=0x00
58/// - FAVORITE: main=0xD8, modifier=0x00
59/// - VENT: main=0xD8, modifier=0x03
60///
61/// This cleanly separates "move to position X" from "execute named action"
62/// without overloading a single numeric parameter.
63///
64/// FORCE_OPEN is NOT handled here — see create_force_open() instead. Unlike these three, it
65/// needs a device-specific "fully open" wire position (0 or 100 depending on inversion), which
66/// this generic dispatch has no way to supply; passing CoverCommand::FORCE_OPEN returns false.
67/// @param f IoFrame to populate.
68/// @param own Controller's 3‑byte node ID (source address).
69/// @param dst Target device's 3‑byte node ID (destination address).
70/// @param low_power True if target is battery/solar‑powered (sets CTRL1_LOW_POWER).
71/// @param cmd Named command to execute (STOP, FAVORITE, or VENT).
72/// @return true on success; false for invalid/unsupported command.
73bool create_execute_command(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power, CoverCommand cmd);
74
75/// @brief Build a force-open execute frame (0x00): move to the device's wire-scale "fully open"
76/// position at elevated ACEI priority (level 0, protection_human) instead of the usual
77/// user_high level — see EXECUTE_ACEI_FORCE_OPEN in proto_commands.cpp for why priority
78/// elevation, not a special position byte, is the protocol's real mechanism for getting past an
79/// environmental soft lock.
80///
81/// @param f IoFrame to populate.
82/// @param own Controller's 3‑byte node ID (source address).
83/// @param dst Target device's 3‑byte node ID (destination address).
84/// @param low_power True if target is battery/solar‑powered (sets CTRL1_LOW_POWER).
85/// @param open_position The device's wire-scale position value that means "fully open": 0 for
86/// ordinary devices, 100 for IoDevice::inverted ones (e.g. horizontal awnings) — the
87/// caller must resolve this from the target device, this builder does not have access
88/// to device state. Getting this wrong sends an ordinary, harmless-looking position
89/// command to the device's already-resting position instead of moving it anywhere.
90/// @return true on success.
91/// @note The elevated-priority override has not yet been confirmed against a real *active*
92/// environmental lock.
93bool create_force_open(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power, uint8_t open_position);
94
95/// Build a get‑status request (0x03). The device responds with its current position.
96/// @param f IoFrame to populate.
97/// @param own Controller's 3‑byte node ID.
98/// @param dst Target device's 3‑byte node ID.
99/// @return true on success.
100bool create_get_status(IoFrame &f, const uint8_t *own, const uint8_t *dst);
101
102/// Build a get-name request (0x50). The device responds with its stored display name.
103/// @param f IoFrame to populate.
104/// @param own Controller's 3-byte node ID.
105/// @param dst Target device's 3-byte node ID.
106/// @param low_power True if target is battery/solar-powered (sets CTRL1_LOW_POWER).
107/// @return true on success.
108bool create_get_name(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power);
109
110/// Build an authenticated set-name request (0x52) using a fixed zero-padded Latin-1 payload.
111/// @param f IoFrame to populate.
112/// @param own Controller's 3-byte node ID.
113/// @param dst Target device's 3-byte node ID.
114/// @param payload Pre-validated fixed payload produced by encode_device_name_payload().
115/// @return true on success.
116bool create_set_name(IoFrame &f, const uint8_t *own, const uint8_t *dst,
117 const uint8_t payload[DEVICE_NAME_WRITE_PAYLOAD_SIZE]);
118
119/// @brief Build an authenticated device-identify request (0x1E) that makes a device
120/// physically identify itself (brief jog / flash).
121///
122/// @param f IoFrame to populate.
123/// @param own Controller's 3-byte node ID (source address).
124/// @param dst Target device's 3-byte node ID (destination address).
125/// @note The device may reply with CMD_ERROR_RESP instead of a dedicated identify response;
126/// callers should treat that reply as an expected, non-fatal outcome rather than a failure.
127/// @return true on success.
128bool create_identify(IoFrame &f, const uint8_t *own, const uint8_t *dst);
129
130/// Build an execute‑tilt command (0x00) for slat angle control.
131/// @param f IoFrame to populate.
132/// @param own Controller node ID.
133/// @param dst Target device node ID.
134/// @param low_power True if target is battery/solar‑powered (sets CTRL1_LOW_POWER).
135/// @param tilt_percent 0 = fully closed, 100 = fully open.
136/// @note This uses the same command (0x00) as position control but with a different
137/// payload format indicating a tilt operation. The receiver infers tilt from
138/// the payload structure. Only devices that advertise tilt support (see
139/// device_supports_tilt in proto_frame.h) will honor this.
140/// @return true on success.
141bool create_execute_tilt(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power, uint8_t tilt_percent);
142
143/// Build a combined position‑and‑tilt execute command (0x00).
144/// Sets both the cover position and the slat angle atomically in one frame,
145/// corresponding to the protocol's setClosureAndOrientation use case.
146/// @param f IoFrame to populate.
147/// @param own Controller node ID.
148/// @param dst Target device node ID.
149/// @param low_power True if target is battery/solar‑powered (sets CTRL1_LOW_POWER).
150/// @param position Desired position 0–100 (open→closed).
151/// @param tilt_percent 0 = fully closed, 100 = fully open.
152/// @return true on success; false if position exceeds limits.
153bool create_execute_position_and_tilt(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power,
154 uint8_t position, uint8_t tilt_percent);
155
156/// Build a tilt‑aware get‑status request (0x03 with extended payload) that returns
157/// the 16‑byte tilt block in the response.
158/// @param f IoFrame to populate.
159/// @param own Controller node ID.
160/// @param dst Target device node ID.
161/// @return true on success.
162bool create_get_status_tilt(IoFrame &f, const uint8_t *own, const uint8_t *dst);
163
164/// @brief Build a discovery request with configurable command, destination, and payload.
165///
166/// Supports the command codes 0x28 (DISCOVER_REQ), 0x2A (DISCOVER_SPE_REQ), and
167/// 0x2E (DISCOVER_ALT_REQ, alternate discovery). For 0x2A the payload is a 6-byte random nonce
168/// followed by a 6-byte HMAC computed over [cmd + nonce] using the supplied system key.
169///
170/// @param f IoFrame to populate.
171/// @param own Controller's 3-byte node ID.
172/// @param command Discovery command code (0x28, 0x2A, or 0x2E).
173/// @param dst Destination node ID (broadcast or explicit).
174/// @param low_power True to set the LOW_POWER flag in CTRL1.
175/// @param payload_enabled True when the optional payload byte is enabled.
176/// @param payload Optional payload byte (only used when command requires a payload).
177/// @param system_key 16-byte system key; only used for 0x2A HMAC computation.
178/// @return true on success; false for unsupported command or missing key for 0x2A.
179bool create_discovery_request(IoFrame &f, const uint8_t *own, uint8_t command, const uint8_t *dst, bool low_power,
180 bool payload_enabled, uint8_t payload, const uint8_t *system_key);
181
182/// Build a discovery broadcast (0x28). Sent to the broadcast address; only devices
183/// in pairing mode (PROG button pressed) will respond.
184/// @param f IoFrame to populate.
185/// @param own Controller node ID.
186/// @note Destination is BROADCAST_DISCOVER (0x00003B). The device responds with
187/// CMD_DISCOVER_RESP (0x29) containing its node ID and type/subtype. The
188/// controller then switches to point‑to‑point communication for phases 2 and 3.
189/// @return true on success.
190bool create_discover(IoFrame &f, const uint8_t *own);
191
192/// @brief Build a discovery response (0x29) — the device side of discovery, used by the
193/// key-extraction responder (see pairing_responder.h) to emulate an unpaired device.
194///
195/// Almost every builder in this file speaks the *controller* side of the protocol; this one,
196/// create_key_confirm(), create_discover_confirm_ack(), and create_challenge_req_device_role()
197/// below speak the *device* side, needed only for that one reverse-role feature. Device-side
198/// frames never set CTRL1_LOW_POWER: that bit describes the *target* of a controller-originated
199/// frame, and a device's replies are addressed to a mains-powered hub.
200/// Payload layout matches the full 9-byte discovery response format documented at
201/// DISCOVERY_RESP_BACKBONE_OFFSET/_MANUFACTURER_OFFSET/_FLAGS_OFFSET/_TIMESTAMP_OFFSET
202/// (proto_constants.h), cross-checked against a real Somfy actuator's captured 0x29
203/// (tests/corpus/captures/somfy_awning/pairing_lab_discovery_response.yaml): backbone address
204/// equals the device's own node ID, start+end set, low_power clear.
205/// @param f IoFrame to populate.
206/// @param own Our advertised (throwaway) node ID — used as both src and the backbone address.
207/// @param dst Destination node ID (the discovering hub's real node ID, from its 0x28's src).
208/// @param type Device type to advertise.
209/// @param subtype Device subtype to advertise.
210/// @param manufacturer_id Manufacturer ID to advertise (see MANUFACTURER_* in proto_constants.h).
211/// @note The flags byte (turnaround class, power-save) and timestamp are best-effort placeholder
212/// values (0x00) — a real hub may require different values; unverified without hardware.
213/// @return true on success.
214bool create_discover_resp(IoFrame &f, const uint8_t *own, const uint8_t *dst, DeviceType type, uint8_t subtype,
215 uint8_t manufacturer_id);
216
217/// @brief Build a key-confirm frame (0x33) — the device's acknowledgement that it received and
218/// installed the system key, sent after decrypting a CMD_KEY_TRANSFER (0x32).
219///
220/// Device-side counterpart to create_key_transfer(); used only by the key-extraction responder
221/// (see create_discover_resp() above for why this direction exists at all). No payload and END
222/// set, matching real devices' 0x33 in
223/// tests/corpus/captures/somfy_dimmer/pairing_full.yaml, velux_kux100/pairing_full.yaml, and (this
224/// project's own key-extraction responder against a real hub)
225/// tests/corpus/captures/issues/issue_45_velux_kig300_key_extraction_success.yaml —
226/// 0x33 closes the key-exchange sequence that CMD_KEY_INIT (0x31) opened with START.
227/// @param f IoFrame to populate.
228/// @param own Our advertised (throwaway) node ID.
229/// @param dst Destination node ID (the hub that sent the key transfer).
230/// @return true on success.
231bool create_key_confirm(IoFrame &f, const uint8_t *own, const uint8_t *dst);
232
233/// @brief Build a discovery-confirm acknowledgement (0x2D) — the device's answer to a hub's
234/// CMD_DISCOVER_CONFIRM (0x2C), which a hub sends directly to a freshly-discovered device before
235/// it will proceed to the key exchange.
236///
237/// Device-side only, like create_discover_resp()/create_key_confirm() above; this project's own
238/// controller role never sends 0x2C, so there is no counterpart builder for the other direction.
239/// No payload and END set, matching real devices' 0x2D in
240/// tests/corpus/captures/velux_kux100/pairing_full.yaml; for a second independent hub,
241/// tests/corpus/captures/issues/issue_45_somfy_connectivity_kit_key_extraction_stall.yaml, where
242/// an already-paired device answers the same hub the key-extraction responder was talking to; and
243/// for this exact builder exercised against a real hub,
244/// tests/corpus/captures/issues/issue_45_velux_kig300_key_extraction_success.yaml.
245/// @param f IoFrame to populate.
246/// @param own Our advertised (throwaway) node ID.
247/// @param dst Destination node ID (the hub that sent the discovery confirm).
248/// @return true on success.
249bool create_discover_confirm_ack(IoFrame &f, const uint8_t *own, const uint8_t *dst);
250
251/// @brief Recover the system key from an inbound CMD_KEY_TRANSFER (0x32) payload — the decode
252/// counterpart to create_key_transfer()'s encode.
253///
254/// Centralizes the IV-`data` convention in one place: create_key_transfer() derives its IV from
255/// the *preceding* CMD_KEY_INIT (0x31) command byte only (see its own doxygen), so decoding must
256/// use that same single-byte `{CMD_KEY_INIT}` — not the 0x32 frame's own command byte, and not
257/// the discovery frame. crypt_key() is symmetric, so this is the same primitive in reverse.
258/// @param transfer_payload 16-byte CMD_KEY_TRANSFER payload (frame.data).
259/// @param challenge The 6-byte challenge *we* generated and sent in our own CMD_CHALLENGE_REQ
260/// (0x3C) — the far side mixes this into its IV, so decoding requires the exact same bytes.
261/// @param out_key Output: recovered 16-byte system key.
262/// @return true on success (crypt_key() AES failure is the only false case).
263bool recover_system_key_from_transfer(const uint8_t transfer_payload[AES_KEY_SIZE], const uint8_t challenge[HMAC_SIZE],
264 uint8_t out_key[AES_KEY_SIZE]);
265
266/// Build a key‑init request (0x31) to start pairing key exchange with a discovered device.
267/// @param f IoFrame to populate.
268/// @param own Controller node ID.
269/// @param dst Discovered device node ID.
270/// @return true on success.
271bool create_key_init(IoFrame &f, const uint8_t *own, const uint8_t *dst);
272
273/// Build a key‑transfer frame (0x32) containing the system key encrypted with the transfer key.
274/// @param f IoFrame to populate.
275/// @param old_frame The key‑init frame (used to derive the encryption IV).
276/// @param dst Target device node ID.
277/// @param src Controller node ID.
278/// @param key The 16‑byte system key to transfer.
279/// @param challenge 6‑byte challenge received from device in its 0x3C response.
280/// @note The system key is obfuscated via the XOR‑AES construction in crypt_key().
281/// The transfer key (hardcoded in proto_frame.h) is the same for all IO‑Homecontrol
282/// devices worldwide; its purpose is to protect the system key in transit during
283/// initial pairing. Once transferred, the device uses the system key for all
284/// subsequent authenticated exchanges.
285/// @return true on success.
286bool create_key_transfer(IoFrame &f, IoFrame &old_frame, const uint8_t *dst, const uint8_t *src,
287 const uint8_t key[AES_KEY_SIZE], const uint8_t challenge[HMAC_SIZE]);
288
289/// Build a challenge request (0x3C) containing 6 random bytes. Used when we need to
290/// authenticate an incoming request from a device.
291/// @param f IoFrame to populate.
292/// @param dst Target device node ID (device we're challenging).
293/// @param src Controller node ID.
294/// @return true on success.
295bool create_challenge_req(IoFrame &f, const uint8_t *dst, const uint8_t *src);
296
297/// @brief Build a challenge request (0x3C) using a caller-supplied challenge instead of
298/// generating a fresh one internally.
299///
300/// The no-challenge overload above generates its own random bytes and does not expose them,
301/// which is fine for the normal inbound-auth path (the challenge is only ever needed once, to
302/// build this same frame). A caller that needs the *exact* bytes again later — the key-extraction
303/// responder decrypting the corresponding CMD_KEY_TRANSFER (0x32) — generates the challenge itself
304/// and passes it in here, keeping the transmitted 0x3C and the later decrypt on one source of
305/// truth. Note that responder uses create_challenge_req_device_role() below, not this overload.
306/// @param f IoFrame to populate.
307/// @param dst Target device node ID (device we're challenging).
308/// @param src Our own node ID.
309/// @param challenge Caller-supplied 6-byte challenge (e.g. from crypto::generate_challenge()).
310/// @return true on success.
311bool create_challenge_req(IoFrame &f, const uint8_t *dst, const uint8_t *src, const uint8_t challenge[HMAC_SIZE]);
312
313/// @brief Build a challenge request (0x3C) in the *device* direction — used only by the
314/// key-extraction responder to challenge a foreign hub that sent us CMD_KEY_INIT (0x31).
315///
316/// Same command and payload as the controller-role builders above, but framed the way a real
317/// device frames it: START clear and LOW_POWER clear. Both controller-role overloads set both
318/// bits, which is correct for their direction — LOW_POWER describes the *target* of a
319/// controller-originated frame (a device that may be battery/solar powered, see this header's
320/// convention note), and the controller's 0x3C opens its own inbound-auth exchange. Neither holds
321/// for a device answering a hub's key-init: real devices' pairing 0x3C frames in
322/// tests/corpus/captures/somfy_dimmer/pairing_full.yaml (`0E 00 …`) and
323/// velux_kux100/pairing_full.yaml carry neither bit, because the frame is a continuation of the
324/// hub's already-open exchange and is addressed to a mains-powered hub — and this exact builder,
325/// exercised against a real hub, produces the identical `0E 00` shape in
326/// tests/corpus/captures/issues/issue_45_velux_kig300_key_extraction_success.yaml.
327/// @param f IoFrame to populate.
328/// @param dst The foreign hub's node ID (from the inbound 0x31's src).
329/// @param src Our advertised (throwaway) node ID.
330/// @param challenge Caller-supplied 6-byte challenge, retained for the later 0x32 decrypt.
331/// @return true on success.
332bool create_challenge_req_device_role(IoFrame &f, const uint8_t *dst, const uint8_t *src,
333 const uint8_t challenge[HMAC_SIZE]);
334
335/// Build a challenge response (0x3D) proving we know the system key.
336/// HMAC is computed over [original_command_id + original_data] using the challenge.
337/// @param f IoFrame to populate.
338/// @param dst Target device node ID.
339/// @param src Controller node ID.
340/// @param challenge 6‑byte challenge from the device.
341/// @param origin Original request frame that triggered the challenge.
342/// @param key System key (16 bytes).
343/// @note The HMAC derivation uses the challenge as IV salt; see create_hmac() in
344/// proto_crypto.h for the exact construction. This frame authenticates the
345/// controller to the device for the current exchange.
346/// @return true on success.
347bool create_challenge_resp(IoFrame &f, const uint8_t *dst, const uint8_t *src, const uint8_t challenge[HMAC_SIZE],
348 const IoFrame &origin, const uint8_t *key);
349
350/// Build a status‑update acknowledgment (0x72). Sent after authenticating a device's
351/// status update; broadcast on all 3 channels for reliability.
352/// @param f IoFrame to populate.
353/// @param own Controller node ID.
354/// @param dst Device node ID that sent the update.
355/// @return true on success.
356bool create_status_update_resp(IoFrame &f, const uint8_t *own, const uint8_t *dst);
357
358/// Build a set‑config command (0x6F) telling the device to automatically send
359/// status updates when controlled by any remote.
360/// @param f IoFrame to populate.
361/// @param own Controller node ID.
362/// @param dst Target device node ID.
363/// @note This configures the device to emit CMD_STATUS_UPDATE (0x71) frames whenever
364/// it is controlled by any remote (including the paired controller). This enables
365/// HA to receive unsolicited position updates. The controller must still
366/// authenticate the status update using the inbound auth flow (hub_exchange.h).
367/// @todo Confirm on real hardware which device families actually honor this SetConfig1
368/// payload and emit unsolicited status updates after pairing.
369/// @return true on success.
370bool create_set_config1(IoFrame &f, const uint8_t *own, const uint8_t *dst);
371
372} // namespace home_io_control
373} // namespace esphome
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 create_get_name(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power)
Build a get-name request (0x50).
DeviceType
Device type identifiers reported by IO‑Homecontrol products.
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_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.
CoverCommand
Named device commands for cover-type actuators.
bool create_discovery_request(IoFrame &f, const uint8_t *own, uint8_t command, const uint8_t *dst, bool low_power, bool payload_enabled, uint8_t payload, const uint8_t *system_key)
Build a configurable discovery request command (0x28, 0x2A, or 0x2E).
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_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.
static constexpr uint8_t HMAC_SIZE
Authentication HMAC is 6 bytes (truncated AES output).
Definition proto_sizes.h:22
static constexpr uint8_t DEVICE_NAME_WRITE_PAYLOAD_SIZE
Fixed write payload: 15 visible chars plus trailing null/padding.
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_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_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...
bool create_discover(IoFrame &f, const uint8_t *own)
Build a discovery broadcast (0x28).
bool create_identify(IoFrame &f, const uint8_t *own, const uint8_t *dst)
Build an authenticated device-identify request (0x1E).
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.
bool create_execute_position(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power, uint8_t position)
Build a position execute command (0x00) to move a device to a numeric position.
static constexpr uint8_t AES_KEY_SIZE
AES-128 key size.
Definition proto_sizes.h:23
bool create_execute_command(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power, CoverCommand cmd)
Build a named-command execute frame (0x00) for STOP, FAVORITE, or VENT.
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_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_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.
Device-name, address-classification and 1W-frame codecs.
IO-Homecontrol device-type model, capabilities and runtime device state.
IO-Homecontrol 2W frame container: control bytes, IoFrame and (de)serialization.
Parsed IO‑Homecontrol frame (CTRL0/1 + addresses + command + data).
Definition proto_frame.h:71