Home IO Control
ESPHome add-on for IO-Homecontrol devices
Loading...
Searching...
No Matches
proto_constants.h
Go to the documentation of this file.
1#pragma once
2
3/// @file proto_constants.h
4/// @brief IO-Homecontrol command IDs, result codes and protocol enumerations.
5/// @ingroup hioc_protocol
6///
7/// Command bytes, CMD_ERROR_RESP result codes, wire position/status flags,
8/// cryptographic constants, and the manufacturer/originator/ACEI/discovery
9/// lookups. These describe *what* travels on the wire, independent of the
10/// frame container (proto_frame.h) and the device model (proto_device_model.h).
11
12#include "proto_sizes.h"
13
14#include <cstdint>
15
16namespace esphome {
17namespace home_io_control {
18
19// ============================================================================
20// Command IDs
21// ============================================================================
22
23// Normal operation commands
24static constexpr uint8_t CMD_EXECUTE = 0x00; ///< Set position/open/close/stop — requires authentication
25static constexpr uint8_t CMD_ACTIVATE_MODE = 0x01; ///< Activate device mode (scene, ventilation) — requires auth
26static constexpr uint8_t CMD_PRIVATE = 0x03; ///< Get device status — no authentication needed
27static constexpr uint8_t CMD_PRIVATE_RESP = 0x04; ///< Response to 0x00 and 0x03 (contains position data)
28static constexpr uint8_t CMD_PRIVATE2 =
29 0x0C; ///< Content otherwise undecoded by the wire parser. Its request payload matches
30 ///< CMD_EXECUTE's POS_FAVORITE/POS_VENT_MODIFIER stored-position selector with the
31 ///< execution prefix stripped, so it reads like a stored-position readback rather
32 ///< than a live telemetry poll — see the somfy_rs100_* / somfy_oximo40_* captures and
33 ///< multi_somfy_probe_private2_{long_form,short_form}.yaml for real request/response
34 ///< pairs. Not handled by any dispatch path in this codebase.
35static constexpr uint8_t CMD_PRIVATE2_RESP = 0x0D; ///< Response to CMD_PRIVATE2. See CMD_PRIVATE2's comment.
36
37// Priority-level and private register commands
38static constexpr uint8_t CMD_PRIORITY_LEVEL_REQ =
39 0x19; ///< Priority-level (lock) query: a one-byte priority level in, the device's lock state
40 ///< for that level out (CMD_PRIORITY_LEVEL_RESP). io-homecontrol arbitrates control by
41 ///< priority level (0-7), and protective functions such as wind protection hold an
42 ///< actuator by locking it at a level. The only capture is
43 ///< tests/corpus/captures/exchange/somfy_awning_exchange_priority_level_sx1276.yaml
44 ///< (issue #27): a real Somfy TaHoma Switch sends 0x02 and then 0x04 to a Sunea io
45 ///< screen it owns, ~212 ms apart. That level-by-level walk fits a lock query and fits
46 ///< it better than the "inject sensor value" reading this opcode was once filed under.
47 ///< Still a working interpretation: no reply has been captured, so the payload
48 ///< semantics are unconfirmed. No builder or dispatch path exists in this codebase.
49static constexpr uint8_t CMD_PRIORITY_LEVEL_RESP = 0x1A; ///< Reply to CMD_PRIORITY_LEVEL_REQ. Never
50 ///< observed on the wire.
51
52// Device identification
53static constexpr uint8_t CMD_IDENTIFY = 0x1E; ///< Device physical identification / jog — requires authentication
54
55static constexpr uint8_t CMD_WRITE_PRIVATE = 0x20; ///< Write private register (climate/heating devices)
56static constexpr uint8_t CMD_WRITE_PRIVATE_ACK = 0x21; ///< Acknowledgment to CMD_WRITE_PRIVATE
57
58// Discovery and pairing commands
59static constexpr uint8_t CMD_DISCOVER_REQ = 0x28; ///< Broadcast discovery request
60static constexpr uint8_t CMD_DISCOVER_RESP = 0x29; ///< Device responds with its ID and type
61static constexpr uint8_t CMD_DISCOVER_SPE_REQ =
62 0x2A; ///< Broadcast roll-call answered by every device that already holds this controller's
63 ///< system key, regardless of device type. A device that holds no key yet — one in
64 ///< learning mode, mid-pairing — has nothing to authenticate the request against and
65 ///< stays silent, so this enumerates already-managed devices and cannot discover new
66 ///< ones. Do not offer it as a pairing-discovery command: it can only ever add replies
67 ///< from devices already paired, never help reach an unpaired one.
68 ///< The 12-byte payload authenticates itself in a single frame — 6 random challenge
69 ///< bytes followed by a 6-byte HMAC over the command byte alone — instead of the usual
70 ///< 0x3C/0x3D round trip, which is what lets it be broadcast;
71 ///< create_discovery_request() (proto_commands.cpp) builds it to match. Real captured
72 ///< bytes: tests/corpus/captures/pairing/velux_kux100_pairing_full.yaml (request shape, HMAC
73 ///< recomputed under that installation's key before the capture was re-keyed) and
74 ///< tests/corpus/captures/discovery/somfy_awning_discovery_spe_paired_rollcall.yaml plus
75 ///< tests/corpus/captures/discovery/somfy_izymo_dimmer_discovery_spe_paired_rollcall.yaml (two awnings
76 ///< and a dimmer answering one broadcast). No dispatch path consumes the reply yet;
77 ///< classify_pairing_discovery_response() accepts only 0x29 and must not be extended to
78 ///< accept 0x2B, because a roll-call reply from an already-paired device is not a
79 ///< newly-discovered one and must never enter the pairing flow. See
80 ///< CMD_DISCOVER_SPE_RESP.
81static constexpr uint8_t CMD_DISCOVER_SPE_RESP =
82 0x2B; ///< Roll-call reply to CMD_DISCOVER_SPE_REQ, sent only by devices that already hold the
83 ///< requesting controller's system key. The payload is the same DISCOVERY_RESP_FULL_SIZE
84 ///< layout as a CMD_DISCOVER_RESP (0x29), so every DISCOVERY_RESP_*_OFFSET constant
85 ///< below applies unchanged — packed type/subtype at data[0..1], backbone address at
86 ///< DISCOVERY_RESP_BACKBONE_OFFSET, manufacturer at DISCOVERY_RESP_MANUFACTURER_OFFSET,
87 ///< Multi Information Byte at DISCOVERY_RESP_FLAGS_OFFSET, timestamp at
88 ///< DISCOVERY_RESP_TIMESTAMP_OFFSET — and PairingEngine::parse_device_from_discovery()
89 ///< decodes a 0x2B correctly with no special-casing. Real captured replies (two awnings
90 ///< and a dimmer) are in tests/corpus/captures/discovery/somfy_awning_discovery_spe_paired_rollcall.yaml
91 ///< and tests/corpus/captures/discovery/somfy_izymo_dimmer_discovery_spe_paired_rollcall.yaml. The
92 ///< timestamp is the field that advances between
93 ///< successive replies from one device; it is not a response to the request's random
94 ///< challenge, since that HMAC covers only the constant command byte. Treat a reply as
95 ///< self-description, not proof of identity: nothing in it is bound to the request, so
96 ///< report it, never act on it. No dispatch path consumes a 0x2B yet.
97static constexpr uint8_t CMD_DISCOVER_CONFIRM = 0x2C; ///< Confirm discovery to device
98static constexpr uint8_t CMD_DISCOVER_CONFIRM_ACK = 0x2D; ///< Device acknowledges confirmation
99static constexpr uint8_t CMD_DISCOVER_ALT_REQ =
100 0x2E; ///< Alternate discovery. Broadcast (to 0x00003F) draws no response at all on every
101 ///< device this project has real evidence for — a Somfy Izymo dimmer
102 ///< (tests/corpus/captures/discovery/somfy_izymo_dimmer_discovery_alt_no_response.yaml) and a Velux
103 ///< KLR200/KUX100 pair
104 ///< (tests/corpus/captures/discovery/velux_kux100_discovery_alt_broadcast_no_response.yaml) both went
105 ///< unanswered; the older "response is 0x29" guess never had real evidence and appears to have been wrong.
106 ///< Directly *addressed* to a known device instead of broadcast, it does draw a
107 ///< response, but a 0x3C/0x3D challenge-response followed by CMD_DISCOVER_ALT_RESP
108 ///< (0x2F), not 0x29 — see
109 ///< tests/corpus/captures/discovery/velux_kux100_discovery_alt_addressed_challenge_response.yaml.
110static constexpr uint8_t CMD_DISCOVER_ALT_RESP =
111 0x2F; ///< Reply to an addressed (non-broadcast) CMD_DISCOVER_ALT_REQ, following a
112 ///< 0x3C/0x3D challenge-response. See CMD_DISCOVER_ALT_REQ's comment and
113 ///< tests/corpus/captures/discovery/velux_kux100_discovery_alt_addressed_challenge_response.yaml
114 ///< — the only capture this project has of it. Not otherwise used anywhere in this
115 ///< codebase (no dispatch logic added).
116static constexpr uint8_t CMD_ONEWAY_ADD_CONTROLLER =
117 0x30; ///< 1W "add controller" — a 1W device broadcasts this while its key-copy gesture is
118 ///< active, handing its network's wrapped system key to whichever controller is
119 ///< listening. Its 20-byte declared payload (enc_key[16] + man_id[1] + data[1] +
120 ///< sequence[2]) plus a genuine 6-byte MAC does not fit inside CTRL0's 5-bit length
121 ///< field together (29 + 6 = 35, unrepresentable in 5 bits), so the MAC rides after
122 ///< the declared length instead, still under the CRC — see IoFrame::has_mac and
123 ///< frame_carries_mac_trailer() (proto_frame.h). Reference:
124 ///< tests/corpus/captures/enrollment/reference_1w_enrollment_add_controller_kat.yaml.
125static constexpr uint8_t CMD_ONEWAY_REMOVE = 0x39; ///< 1W "remove controller" (un-pair a 1W remote from a device);
126 ///< same payload shape as 0x2E.
127
128// Key exchange commands (used during pairing)
129static constexpr uint8_t CMD_KEY_INIT = 0x31; ///< Initiate key transfer to device
130static constexpr uint8_t CMD_KEY_TRANSFER = 0x32; ///< Send encrypted system key to device
131static constexpr uint8_t CMD_KEY_CONFIRM = 0x33; ///< Device confirms key was received
132
133// Node verification and device-initiated key exchange
134static constexpr uint8_t CMD_NODE_VERIFY_REQ =
135 0x36; ///< Node verification request: a controller checks that a node belongs to its system.
136 ///< The node answers with its address (CMD_NODE_VERIFY_RESP) and the controller then
137 ///< challenges that answer, so only a node holding the system key passes. It is what
138 ///< closes a key transfer (tests/corpus/captures/pairing/velux_kux100_pairing_full.yaml,
139 ///< a Velux KLR200) and what a controller sends to nodes it already knows, with no
140 ///< pairing in progress (a Velux KIG300 probing a Somfy dimmer in
141 ///< tests/corpus/captures/probe/velux_kig300_probe_capability_burst.yaml; a KLR300
142 ///< checking each node after a roll-call in
143 ///< tests/corpus/captures/discovery/velux_klr300_discovery_rollcall_node_verification.yaml).
144 ///< Answered by the key-extraction responder's create_node_verify_resp_device_role()
145 ///< (handle_node_verify_req_() in key_extraction_responder.cpp).
146static constexpr uint8_t CMD_NODE_VERIFY_RESP =
147 0x37; ///< Node verification response: the device returns its own 3-byte backbone address,
148 ///< byte-identical to the one it reported at data[2..4]
149 ///< (DISCOVERY_RESP_BACKBONE_OFFSET) of its CMD_DISCOVER_RESP earlier in the same
150 ///< session — an independent confirmation of that offset. In
151 ///< tests/corpus/captures/pairing/velux_kux100_pairing_full.yaml a Velux KLR200 closes
152 ///< pairing with 0x36 and then challenges the 0x37 it gets back (see
153 ///< CMD_CHALLENGE_REQ). Sent by create_node_verify_resp_device_role()
154 ///< (proto_commands.h/.cpp), the key-extraction responder's answer to CMD_NODE_VERIFY_REQ.
155static constexpr uint8_t CMD_LAUNCH_KEY_TRANSFER =
156 0x38; ///< Device-initiated ("pull") key transfer request: documented elsewhere as a command
157 ///< ID plus a 6-byte challenge, nothing more — never observed in our corpus or in any
158 ///< field log, and not sent or handled anywhere in this codebase — the constant is
159 ///< used only to construct a hypothetical device-side IV in
160 ///< tests/proto/proto_crypto_test.cpp, exercising the crypto primitive, not a dispatch path.
161
162// Authentication commands (challenge-response for secured commands)
163static constexpr uint8_t CMD_CHALLENGE_REQ =
164 0x3C; ///< 6-byte random challenge. Usually a device challenging a controller's command, but
165 ///< the protocol is symmetric and controllers challenge devices too: in
166 ///< tests/corpus/captures/pairing/velux_kux100_pairing_full.yaml a KLR200 issues 0x3C against
167 ///< the device's own CMD_NODE_VERIFY_RESP. The key-extraction responder now answers exactly
168 ///< that inbound direction (KeyExtractionResponder::handle_node_verify_challenge_() in
169 ///< key_extraction_responder.cpp), so both directions are implemented, not just the outbound
170 ///< one.
171static constexpr uint8_t CMD_CHALLENGE_RESP =
172 0x3D; ///< HMAC proof answering a 0x3C. Whoever is challenged authenticates *its own*
173 ///< preceding frame: the transcript is [cmd, data...] of the challenged party's last
174 ///< frame (create_challenge_resp()), never the challenger's. That holds in both
175 ///< directions — the device-side 0x3D in velux_kux100_pairing_full.yaml (over its own 0x37) was
176 ///< recomputed under that installation's recovered key and confirmed before the
177 ///< capture was re-keyed, so it is measured, not assumed by symmetry.
178
179// File/blob management block (0x48-0x4B)
180static constexpr uint8_t CMD_UNKNOWN4A_REQ =
181 0x4A; ///< Reads chunk `<handle><chunk u16>` of a buffer opened by a 0x46/0x47 exchange; seen
182 ///< on air between two controllers in a VELUX KLR 200 copy session. What it does to an
183 ///< actuator is unknown, and the surrounding 0x46-0x4B block includes write commands.
184 ///< This constant exists solely so a received 0x4A frame renders by name in the log
185 ///< instead of as UNKNOWN_CMD; it must never be sent, and no builder for it exists
186 ///< anywhere in this codebase — see CMD_ONEWAY_ADD_CONTROLLER for the same "named but
187 ///< never sent" precedent, and docs/adr/ for the standing decision not to add one.
188static constexpr uint8_t CMD_UNKNOWN4A_RESP =
189 0x4B; ///< Echoes `<handle><chunk>` followed by up to 18 bytes of data. Observed on the wire
190 ///< (tests/corpus/captures/probe/velux_kig300_probe_capability_burst.yaml) answering an
191 ///< ON_OFF_SWITCH-type device's traffic; the `01 00 00` payload there has the shape of a
192 ///< close echo. Not sent or handled anywhere in this codebase.
193
194// Device info commands
195static constexpr uint8_t CMD_GET_NAME = 0x50; ///< Request device name
196static constexpr uint8_t CMD_GET_NAME_RESP = 0x51; ///< Device name response
197static constexpr uint8_t CMD_SET_NAME = 0x52; ///< Set device name (authenticated)
198static constexpr uint8_t CMD_SET_NAME_RESP = 0x53; ///< Device-name write response
199static constexpr uint8_t CMD_GET_INFO1 =
200 0x54; ///< Request device general info 1. Sent by the `get_info1` diagnostic probe
201 ///< (docs/diagnostic-probes.md, ADR 0024); field-observed on air from a real hub.
202static constexpr uint8_t CMD_GET_INFO1_RESP = 0x55; ///< Device general info 1 response. Still never captured on our
203 ///< wire or in any field log, and nothing decodes it — accepted by
204 ///< the soft-PHY only so a probe reply is not dropped as an
205 ///< unknown command.
206static constexpr uint8_t CMD_GET_INFO2 = 0x56; ///< Request device type/model info. The only thing that sends
207 ///< it is the `get_info2` diagnostic probe; the corpus has no
208 ///< fixture for the request itself -- every 0x57 capture on
209 ///< hand is a reply to someone else's request. Closing this
210 ///< needs a fresh device-add on owned hardware, ingested with
211 ///< `--rekey` (tests/corpus/README.md).
212static constexpr uint8_t CMD_GET_INFO2_RESP = 0x57; ///< Device type/model response
213static constexpr uint8_t CMD_GET_GENERAL_INFO3 =
214 0x58; ///< Observed on the wire (tests/corpus/captures/probe/velux_kig300_probe_capability_burst.yaml)
215 ///< with no payload. Content undecoded. Not sent or handled anywhere in this codebase.
216static constexpr uint8_t CMD_GET_GENERAL_INFO3_RESP =
217 0x59; ///< Never captured on our own wire. This constant exists so a received 0x59 frame
218 ///< renders by name instead of as UNKNOWN_CMD. Not sent or handled anywhere in this
219 ///< codebase.
220
221// Configuration and status update commands
222static constexpr uint8_t CMD_SET_CONFIG1 = 0x6F; ///< Configure device to auto-send status updates
223static constexpr uint8_t CMD_SET_CONFIG1_RESP =
224 0x70; ///< Config response, otherwise undocumented. Never observed in our corpus or in any
225 ///< field log, and not sent or handled anywhere in this codebase beyond the generic
226 ///< "is this a known command byte" check in radio_soft_phy.cpp.
227static constexpr uint8_t CMD_STATUS_UPDATE = 0x71; ///< Device-initiated status update (needs auth)
228static constexpr uint8_t CMD_STATUS_UPDATE_RESP = 0x72; ///< Acknowledge status update
229
230static constexpr uint8_t CMD_SEND_RAW_MESSAGE =
231 0xF0; ///< Named "Send Raw Message" / "Find Hardware" — two candidate names, neither settled.
232 ///< Never observed in our corpus or in any field log, and not sent or handled
233 ///< anywhere in this codebase.
234static constexpr uint8_t CMD_READ_GROUPS =
235 0xF1; ///< Named "Actuator: Read Groups" / "ActuatorAnyConfigIsLocal" (uncertain) / "Service
236 ///< ACK" — three candidate names, one itself flagged uncertain. Never observed in our
237 ///< corpus or in any field log, and not sent or handled anywhere in this codebase.
238static constexpr uint8_t CMD_REBOOT =
239 0xF2; ///< Named "Reboot" / "Service Status" — two candidate names, one of them
240 ///< destructive-sounding, on no field evidence at all. Never observed in our corpus or
241 ///< in any field log, and not sent or handled anywhere in this codebase; treat the
242 ///< "reboot" reading with particular caution — it is a guess.
243static constexpr uint8_t CMD_SERVICE_STATUS_ACK = 0xF3; ///< No description available at all for this opcode, not even
244 ///< a hedge. Never observed in our corpus or in any field log,
245 ///< and not sent or handled anywhere in this codebase.
246
247static constexpr uint8_t CMD_ERROR_RESP = 0xFE; ///< Error response to any command
248
249// Command-result / limitation codes carried in CMD_ERROR_RESP DATA[0].
250static constexpr uint8_t RESULT_UNKNOWN_STATUS_REPLY = 0x00; ///< Device returned an unknown status reply.
251static constexpr uint8_t RESULT_COMMAND_COMPLETED_OK = 0x01; ///< No errors detected.
252static constexpr uint8_t RESULT_NO_CONTACT = 0x02; ///< No communication to node.
253static constexpr uint8_t RESULT_MANUALLY_OPERATED = 0x03; ///< Manually operated by a user.
254static constexpr uint8_t RESULT_BLOCKED = 0x04; ///< Node blocked by an object.
255static constexpr uint8_t RESULT_WRONG_SYSTEMKEY = 0x05; ///< Node contains the wrong system key.
256static constexpr uint8_t RESULT_PRIORITY_LEVEL_LOCKED = 0x06; ///< Node is locked on this priority level.
257static constexpr uint8_t RESULT_REACHED_WRONG_POSITION = 0x07; ///< Node stopped in another position than expected.
258static constexpr uint8_t RESULT_ERROR_DURING_EXECUTION = 0x08; ///< Generic execution failure.
259static constexpr uint8_t RESULT_NO_EXECUTION = 0x09; ///< Node did not move.
260static constexpr uint8_t RESULT_CALIBRATING = 0x0A; ///< Node is calibrating.
261static constexpr uint8_t RESULT_POWER_CONSUMPTION_TOO_HIGH = 0x0B; ///< Node power consumption is too high.
262static constexpr uint8_t RESULT_POWER_CONSUMPTION_TOO_LOW = 0x0C; ///< Node power consumption is too low.
263static constexpr uint8_t RESULT_LOCK_POSITION_OPEN = 0x0D; ///< Lock command failed because the door is open.
264static constexpr uint8_t RESULT_MOTION_TIME_TOO_LONG = 0x0E; ///< Target was not reached in time.
265static constexpr uint8_t RESULT_THERMAL_PROTECTION = 0x0F; ///< Node entered thermal protection mode.
266static constexpr uint8_t RESULT_PRODUCT_NOT_OPERATIONAL = 0x10; ///< Node is not currently operational.
267static constexpr uint8_t RESULT_FILTER_MAINTENANCE_NEEDED = 0x11; ///< Filter needs maintenance.
268static constexpr uint8_t RESULT_BATTERY_LEVEL = 0x12; ///< Battery level is low.
269static constexpr uint8_t RESULT_TARGET_MODIFIED = 0x13; ///< Node modified the requested target value.
270static constexpr uint8_t RESULT_MODE_NOT_IMPLEMENTED = 0x14; ///< Mode is not supported by the node.
271static constexpr uint8_t RESULT_COMMAND_INCOMPATIBLE_TO_MOVEMENT = 0x15; ///< Command cannot move the node that way.
272static constexpr uint8_t RESULT_USER_ACTION = 0x16; ///< User action overrode the command.
273static constexpr uint8_t RESULT_DEAD_BOLT_ERROR = 0x17; ///< Dead bolt error.
274static constexpr uint8_t RESULT_AUTOMATIC_CYCLE_ENGAGED = 0x18; ///< Node entered automatic cycle mode.
275static constexpr uint8_t RESULT_WRONG_LOAD_CONNECTED = 0x19; ///< Wrong load connected to node.
276static constexpr uint8_t RESULT_COLOUR_NOT_REACHABLE = 0x1A; ///< Requested colour not reachable.
277static constexpr uint8_t RESULT_TARGET_NOT_REACHABLE = 0x1B; ///< Requested target not reachable.
278static constexpr uint8_t RESULT_BAD_INDEX_RECEIVED = 0x1C; ///< Invalid index received.
279static constexpr uint8_t RESULT_COMMAND_OVERRULED = 0x1D; ///< Command was overruled by a newer command.
280static constexpr uint8_t RESULT_NODE_WAITING_FOR_POWER = 0x1E; ///< Node is waiting for power.
281static constexpr uint8_t RESULT_NODE_LOCKED = 0x20; ///< Node is locked.
282static constexpr uint8_t RESULT_WRONG_POSITION = 0x21; ///< Node reports wrong position.
283static constexpr uint8_t RESULT_LIMITS_NOT_SET = 0x22; ///< Device limits are not set.
284static constexpr uint8_t RESULT_IP_NOT_SET = 0x23; ///< Intermediate position is not set.
285static constexpr uint8_t RESULT_OUT_OF_RANGE = 0x24; ///< Requested value is out of range.
286static constexpr uint8_t RESULT_PRIORITY_LOCKED_NON_EXEC =
287 0x38; ///< Priority locked, command not executed (ACEI priority too low).
288static constexpr uint8_t RESULT_INVALID_FUNCTION_INDEX =
289 0x58; ///< CMD_PRIVATE-family function ID / sub-index / selector-block outside the range the
290 ///< device implements. Seen cross-vendor (Somfy Sunea awning + dimmer, Velux window)
291 ///< when a diagnostic probe walks past the last supported index; not in any reference
292 ///< error table. Distinct from RESULT_BAD_INDEX_RECEIVED (0x1C).
293static constexpr uint8_t RESULT_INFORMATION_CODE = 0xDF; ///< Information-only code with unknown semantics.
294static constexpr uint8_t RESULT_PARAMETER_LIMITED = 0xE0; ///< Parameter limited by an unknown device.
295static constexpr uint8_t RESULT_LIMITATION_BY_LOCAL_USER = 0xE1; ///< Parameter limited by local button.
296static constexpr uint8_t RESULT_LIMITATION_BY_USER = 0xE2; ///< Parameter limited by a remote control.
297static constexpr uint8_t RESULT_LIMITATION_BY_RAIN = 0xE3; ///< Parameter limited by a rain sensor.
298static constexpr uint8_t RESULT_LIMITATION_BY_TIMER = 0xE4; ///< Parameter limited by a timer.
299static constexpr uint8_t RESULT_LIMITATION_BY_SCD = 0xE5; ///< Parameter limited by a security actuator.
300static constexpr uint8_t RESULT_LIMITATION_BY_UPS = 0xE6; ///< Parameter limited by a power supply.
301static constexpr uint8_t RESULT_LIMITATION_BY_UNKNOWN_DEVICE = 0xE7; ///< Parameter limited by an unknown device.
302static constexpr uint8_t RESULT_LIMITATION_BY_SAAC = 0xEA; ///< Parameter limited by a standalone automatic controller.
303static constexpr uint8_t RESULT_LIMITATION_BY_WIND = 0xEB; ///< Parameter limited by a wind sensor.
304static constexpr uint8_t RESULT_LIMITATION_BY_MYSELF = 0xEC; ///< Parameter limited by the node itself.
305static constexpr uint8_t RESULT_LIMITATION_BY_AUTOMATIC_CYCLE = 0xED; ///< Parameter limited by an automatic cycle.
306static constexpr uint8_t RESULT_LIMITATION_BY_EMERGENCY = 0xEE; ///< Parameter limited by an emergency.
307
308// ============================================================================
309// Position and Status Wire Constants
310// ============================================================================
311
312/// Position values in the IO protocol.
313/// Normal positions are 0-100 (0=fully open, 100=fully closed).
314/// Special values above 100 are control commands encoded as the "main" parameter
315/// byte in CMD_EXECUTE payloads. These are internal wire constants — callers should
316/// prefer CoverCommand for type-safe command dispatch.
317static constexpr uint8_t POS_STOP = 0xD2; ///< Wire value: stop movement.
318static constexpr uint8_t POS_UNKNOWN = 0xD4; ///< Wire value: position unknown / keep current.
319static constexpr uint8_t POS_FAVORITE = 0xD8; ///< Wire value: move to favorite/"My" position.
320
321/// Offset of the main position/command byte in a CMD_EXECUTE payload — after the originator (first
322/// byte) and the ACEI (second byte). Holds a 0-100 position scale value or one of the POS_* codes.
323static constexpr uint8_t EXECUTE_MAIN_BYTE_OFFSET = 2;
324
325/// @brief Wire value for the secured target position command.
326///
327/// Moves the actuator to its pre-programmed secured/safety position from the
328/// Execution Parameter Buffer. Typically sent by environmental sensors (wind, rain)
329/// to retract an awning or close a shutter to a wind-safe state.
330static constexpr uint8_t POS_SECURED_TARGET = 0xD1;
331
332/// @brief Wire value for the default position command.
333///
334/// Moves the actuator to its factory or user-configured default position.
335static constexpr uint8_t POS_DEFAULT = 0xD3;
336
337/// @brief Ambiguous wire value used only for passive 1W-traffic intent decoding
338/// (decode_1w_main_intent() / oneway_intent_to_target() in proto_codecs.cpp).
339///
340/// 0x64 (100) is simultaneously the ordinary doubled-position wire value for 50% and a value
341/// some physical 1W remotes send for their "force open" button — the protocol has no dedicated
342/// override code, so the two are indistinguishable on the wire. For passively decoding someone
343/// else's remote traffic, "FORCE_OPEN" is the more useful diagnostic label (physical remotes
344/// rarely send a numeric 50%). This is NOT used by any outbound builder in this codebase:
345/// real-hardware testing confirmed that sending main=0x64 as an outbound 2W
346/// CMD_EXECUTE command makes a real device move to 50% open, not bypass anything — see
347/// create_force_open() in proto_commands.cpp for the actual (ACEI-priority-based) force-open
348/// implementation.
349static constexpr uint8_t POS_FORCE_OPEN = 0x64;
350
351/// @brief Modifier byte for the ventilation command.
352///
353/// Both favorite and ventilation use POS_FAVORITE (0xD8) as the main parameter byte,
354/// but ventilation sets the secondary byte (main[1]) to 0x03 while favorite leaves it 0x00.
355static constexpr uint8_t POS_VENT_MODIFIER = 0x03;
356
357/// @brief Scale factor between a 0-100 percent position and its CMD_EXECUTE main-byte wire value.
358///
359/// Percent 0-100 maps to wire 0-200 (wire = percent * POSITION_WIRE_SCALE), leaving 201-255 free
360/// for the POS_* special codes above. Builders multiply by this to encode a position; decoders
361/// divide by it to recover one — both directions belong on this one constant so they cannot
362/// silently drift apart into two different bare "2"s.
363static constexpr uint8_t POSITION_WIRE_SCALE = 2;
364/// @brief Highest doubled-position wire value: 100% * POSITION_WIRE_SCALE. A main byte at or
365/// below this is an ordinary position; above it, one of the POS_* special codes.
366static constexpr uint8_t POSITION_WIRE_MAX = 200;
367
368/// Status byte flags in CMD_PRIVATE_RESP and CMD_STATUS_UPDATE.
369static constexpr uint8_t STATUS_STOPPED = 0x01; ///< Byte 0 bit 0: device is not moving
370static constexpr uint8_t STATUS_EXPECTED = 0x80; ///< Byte 1 bit 7: device will send auto status update
371static constexpr uint8_t STATUS_TILT_SELECTOR =
372 0x20; ///< Extended status payload marker for tilt-capable devices: FPI1 with bit 5 set = functional parameter
373 ///< FP3 (FPI1 bit 7 = FP1 ... bit 0 = FP8). The actuator echoes FPI1/FPI2 in its ack (VELUX KLF 200 API
374 ///< v3.18 §10.1.1.5). The 0x80 form of the extended 0x03 request selects FP1.
375
376/// @brief CMD_PRIVATE (0x03) function ID for a position-status request — data[0] of the payload.
377///
378/// The only function ID this codebase has ever captured on its own wire: it is what
379/// create_get_status() freezes create_private_function() at, and the default `function_id` of
380/// create_get_status_extended(). Lives here rather than in proto_commands.cpp because that
381/// default argument is spelled in proto_commands.h.
382static constexpr uint8_t PRIVATE_GET_POSITION_STATUS = 0x03;
383
384// ============================================================================
385// Cryptographic Constants
386// ============================================================================
387
388/// The transfer key is a hardcoded key used ONLY during pairing to obfuscate
389/// the system key during over-the-air transfer. It is NOT the system key.
390/// This is the same across all IO-Homecontrol devices worldwide.
391static constexpr uint8_t TRANSFER_KEY[AES_KEY_SIZE] = {0x34, 0xC3, 0x46, 0x6E, 0xD8, 0x8F, 0x4E, 0x8E,
392 0x16, 0xAA, 0x47, 0x39, 0x49, 0x88, 0x43, 0x73};
393static constexpr uint16_t CRC_POLYNOMIAL_REVERSED = 0x8408; ///< Reversed CRC-CCITT polynomial used by IO-homecontrol
394static constexpr uint16_t CRC_LSB_MASK = 0x0001; ///< Least-significant-bit mask for reflected CRC update
395
396/// Broadcast address for device discovery (0x00003B).
397/// Used as destination in CMD_DISCOVER_REQ frames to trigger all pairable devices to respond.
398static constexpr uint8_t BROADCAST_DISCOVER[NODE_ID_SIZE] = {0x00, 0x00, 0x3B};
399
400/// Alternate discovery / 1W broadcast address (0x00003F).
401/// Used as destination for CMD_DISCOVER_ALT_REQ (0x2E) alternate discovery, and the address
402/// on which devices in 1W-triggered pairing mode listen. Distinct from the 2W discovery
403/// broadcast BROADCAST_DISCOVER (0x00003B).
404static constexpr uint8_t BROADCAST_DISCOVER_ALT[NODE_ID_SIZE] = {0x00, 0x00, 0x3F};
405
406// ============================================================================
407// Command Name Lookup
408// ============================================================================
409
410/// @brief Get a human-readable name for any IO-Homecontrol command ID.
411///
412/// Returns a short uppercase identifier suitable for log lines (e.g., "EXECUTE",
413/// "DISCOVER_REQ", "CHALLENGE_RESP"). Unknown commands return "UNKNOWN_CMD".
414/// @param cmd Command byte from the frame header.
415/// @return Null-terminated string.
416const char *command_name(uint8_t cmd);
417
418// ============================================================================
419// Manufacturer ID Lookup
420// ============================================================================
421
422/// @brief Maximum manufacturer ID with a known name in the lookup table.
423static constexpr uint8_t MANUFACTURER_ID_MAX = 12;
424
425/// @brief IO-Homecontrol manufacturer ID constants.
426///
427/// These 1-based identifiers are assigned by the IO-Homecontrol alliance and
428/// appear in the discovery response payload at DISCOVERY_RESP_MANUFACTURER_OFFSET.
429/// @{
430static constexpr uint8_t MANUFACTURER_VELUX = 1; ///< VELUX (roof windows, skylights).
431static constexpr uint8_t MANUFACTURER_SOMFY = 2; ///< Somfy (shutters, awnings, blinds).
432static constexpr uint8_t MANUFACTURER_HONEYWELL = 3; ///< Honeywell.
433static constexpr uint8_t MANUFACTURER_HORMANN = 4; ///< Hörmann (garage doors, gates).
434static constexpr uint8_t MANUFACTURER_ASSA_ABLOY = 5; ///< ASSA ABLOY (locks, access).
435static constexpr uint8_t MANUFACTURER_NIKO = 6; ///< Niko (switches, home automation).
436static constexpr uint8_t MANUFACTURER_WINDOW_MASTER = 7; ///< WINDOW MASTER (ventilation).
437static constexpr uint8_t MANUFACTURER_RENSON = 8; ///< Renson (ventilation, sun protection).
438static constexpr uint8_t MANUFACTURER_CIAT = 9; ///< CIAT (HVAC).
439static constexpr uint8_t MANUFACTURER_SECUYOU = 10; ///< Secuyou (security).
440static constexpr uint8_t MANUFACTURER_OVERKIZ = 11; ///< OVERKIZ (Somfy connectivity platform).
441static constexpr uint8_t MANUFACTURER_ATLANTIC_GROUP = 12; ///< Atlantic Group (heating, hot water).
442/// @}
443
444/// @brief Get a human-readable manufacturer name from the protocol manufacturer byte.
445///
446/// The manufacturer ID is a 1-based index assigned by the IO-Homecontrol alliance.
447/// IDs outside the known range return "unknown". When an unknown ID appears at runtime,
448/// the pairing flow logs a warning suggesting the user file a GitHub issue.
449/// @warning **Display-only — do not use this for YAML.** Four of the twelve names do not
450/// round-trip through `.strip().lower()` to their `manufacturer:` YAML token
451/// (`MANUFACTURER_OPTIONS`, `hub_validators.py`): `"Hörmann"` has an umlaut the YAML token
452/// (`hormann`) drops, and `"ASSA ABLOY"`/`"WINDOW MASTER"`/`"Atlantic Group"` use a space
453/// where the YAML token uses `_`. There is currently no YAML-token accessor for
454/// manufacturers — see `yaml_device_type_name()` (proto_device_model.h) for the pattern this
455/// would follow if one is ever added.
456/// @param id Manufacturer ID byte (1–12 for known manufacturers).
457/// @return Null-terminated lowercase string such as "unknown", or mixed-case name like "Somfy".
458const char *manufacturer_name(uint8_t id);
459
460// ============================================================================
461// Command Originator Codes
462// ============================================================================
463
464/// @brief Command originator codes indicating what or who triggered a command.
465///
466/// The originator byte is the first byte of the CMD_EXECUTE payload. It tells
467/// the actuator (and any eavesdropping controller) who initiated the movement.
468/// This is useful for understanding device-initiated status updates.
469///
470/// ORIGINATOR_WIND_SENSOR and ORIGINATOR_RAIN_SENSOR are both field-confirmed on real hardware —
471/// a single combined wind/rain protection station (issue #27, community capture) broadcasting
472/// both values minutes apart:
473/// tests/corpus/captures/oneway/wind_sensor_oneway_favorite_wind_originator_sx1276.yaml and
474/// tests/corpus/captures/oneway/wind_sensor_oneway_favorite_rain_originator_sx1276.yaml. The
475/// latter is this project's first real-hardware confirmation of ORIGINATOR_RAIN_SENSOR at all —
476/// every prior wind/rain-station capture used ORIGINATOR_WIND_SENSOR only.
477/// @{
478static constexpr uint8_t ORIGINATOR_LOCAL_USER = 0x00; ///< User pressed a button on the actuator.
479static constexpr uint8_t ORIGINATOR_USER_REMOTE = 0x01; ///< User sent command from a remote control.
480static constexpr uint8_t ORIGINATOR_RAIN_SENSOR = 0x02; ///< Rain sensor triggered the movement.
481static constexpr uint8_t ORIGINATOR_TIMER = 0x03; ///< Timer or schedule triggered the movement.
482static constexpr uint8_t ORIGINATOR_SECURITY = 0x04; ///< Security controlling device (SCD) action.
483static constexpr uint8_t ORIGINATOR_UPS = 0x05; ///< Uninterruptible power supply action.
484static constexpr uint8_t ORIGINATOR_SMART_CONTROLLER = 0x06; ///< Smart function controller.
485static constexpr uint8_t ORIGINATOR_LIFESTYLE = 0x07; ///< Lifestyle scenario controller.
486static constexpr uint8_t ORIGINATOR_SAAC = 0x08; ///< Stand-alone automatic controller (SAAC).
487static constexpr uint8_t ORIGINATOR_WIND_SENSOR = 0x09; ///< Wind sensor triggered the movement.
488static constexpr uint8_t ORIGINATOR_LOAD_SHEDDING = 0x0B; ///< Load-shedding manager.
489static constexpr uint8_t ORIGINATOR_LOCAL_LIGHT = 0x0C; ///< Local light sensor.
490static constexpr uint8_t ORIGINATOR_ENVIRONMENT = 0x0D; ///< Unspecified environment sensor.
491static constexpr uint8_t ORIGINATOR_MYSELF = 0x10; ///< Actuator decided to move by itself.
492static constexpr uint8_t ORIGINATOR_AUTOMATIC_CYCLE = 0xFE; ///< Automatic cycle / external access.
493static constexpr uint8_t ORIGINATOR_EMERGENCY = 0xFF; ///< Emergency command (never disabled).
494/// @}
495
496/// @brief Get a human-readable name for a command originator byte.
497///
498/// @param originator Originator code from the first data byte of CMD_EXECUTE.
499/// @return Null-terminated string such as "rain_sensor" or "user_remote".
500const char *originator_name(uint8_t originator);
501
502// ============================================================================
503// ACEI (Application Command Execution Interface)
504// ============================================================================
505
506/// @brief ACEI byte bit-field definitions.
507///
508/// The ACEI byte is the second byte of the CMD_EXECUTE payload. It encodes the
509/// priority level and service class of the command, controlling which commands
510/// can override others. Devices reject commands with lower priority than their
511/// current locked level (resulting in RESULT_PRIORITY_LEVEL_LOCKED).
512/// @{
513static constexpr uint8_t ACEI_VALID_BIT = 0x01; ///< Bit 0: command validity flag.
514static constexpr uint8_t ACEI_EXTENDED_MASK = 0x06; ///< Bits [2:1]: extended field.
515static constexpr uint8_t ACEI_EXTENDED_SHIFT = 1; ///< Shift for extended field extraction.
516static constexpr uint8_t ACEI_SERVICE_MASK = 0x18; ///< Bits [4:3]: service type.
517static constexpr uint8_t ACEI_SERVICE_SHIFT = 3; ///< Shift for service field extraction.
518static constexpr uint8_t ACEI_LEVEL_MASK = 0xE0; ///< Bits [7:5]: priority level (0–7).
519static constexpr uint8_t ACEI_LEVEL_SHIFT = 5; ///< Shift for priority level extraction.
520/// @}
521
522/// @brief ACEI priority level values (0–7).
523///
524/// These values are extracted from the ACEI byte via (acei & ACEI_LEVEL_MASK) >> ACEI_LEVEL_SHIFT.
525/// @{
526static constexpr uint8_t ACEI_LEVEL_PROTECTION_HUMAN = 0; ///< Personal safety (highest, overrides all).
527static constexpr uint8_t ACEI_LEVEL_PROTECTION_SENSOR = 1; ///< Goods/environment protection via sensors.
528static constexpr uint8_t ACEI_LEVEL_USER_HIGH = 2; ///< High-priority user controller.
529static constexpr uint8_t ACEI_LEVEL_USER_DEFAULT = 3; ///< Default remote controller priority.
530static constexpr uint8_t ACEI_LEVEL_COMFORT_1 = 4; ///< Comfort automation level 1.
531static constexpr uint8_t ACEI_LEVEL_COMFORT_2 = 5; ///< Comfort automation level 2.
532static constexpr uint8_t ACEI_LEVEL_AUTO_SAAC = 6; ///< Stand-alone automatic controller.
533static constexpr uint8_t ACEI_LEVEL_AUTO_DEFAULT = 7; ///< Default automatic level (lowest).
534/// @}
535
536/// @brief Get a human-readable name for an ACEI priority level (0–7).
537///
538/// Priority levels form a hierarchy: level 0 (human protection) is highest
539/// and overrides all others. Level 3 is the default for remote controllers.
540/// @param level Priority level value (0–7).
541/// @return Null-terminated string such as "user_default" or "protection_sensor".
542const char *acei_level_name(uint8_t level);
543
544/// @brief ACEI byte for a 1W CMD_EXECUTE frame — the Somfy-shaped default.
545///
546/// Level 2 (user_high), extended-info bit set. The 2W EXECUTE_ACEI (proto_commands.cpp) pins
547/// level 3 to a real captured 2W hub; this level-2 value is what the published 1W reference
548/// vector (tests/corpus/captures/oneway/reference_1w_oneway_execute_iv_vector.yaml) and every
549/// Somfy 1W remote frame in the corpus (7+ frames across 3 nodes: 9D6085, 485B37, 7B8240) carry.
550/// Lives here (not proto_commands.cpp) so oneway_controller.h's resolve_oneway_wire_profile() can
551/// name it without the protocol layer depending on the controller layer.
552/// Composition: (ACEI_LEVEL_USER_HIGH << 5) | (1 << 1) | 1 = 0x43.
553static constexpr uint8_t ONEWAY_EXECUTE_ACEI =
555
556/// @brief ACEI byte for a 1W CMD_EXECUTE frame from a VELUX KLI-class remote.
557///
558/// Level 3 (user_default), extended-info bits clear. This is also iown-homecontrol's generic
559/// `ACEI_DEFAULT`. **Confidence: n=1** — one corpus frame
560/// (tests/corpus/captures/oneway/velux_kli313_oneway_stop.yaml) plus samr037/iohc-flipper's
561/// README, which names cmd 0x00 payload[1] the "vendor byte" (`0x43` Somfy / `0x61` Velux).
562/// The `unidentified_1w_remote_*` frames also carry 0x61 but their manufacturer is unknown, so
563/// they can't corroborate the VELUX attribution. `execute_acei:` on the identity is the escape
564/// hatch. Composition: (ACEI_LEVEL_USER_DEFAULT << 5) | ACEI_VALID_BIT = 0x61.
566
567// ============================================================================
568// Discovery Response Extended Fields
569// ============================================================================
570
571/// @brief Byte offsets within CMD_DISCOVER_RESP (0x29) payload data.
572///
573/// The full discovery response carries up to 9 bytes of device metadata:
574/// bytes 0–1 hold the packed device type/subtype (already parsed by
575/// decode_packed_device_type()), and bytes 2–8 hold additional fields.
576/// @{
577static constexpr uint8_t DISCOVERY_RESP_BACKBONE_OFFSET = 2; ///< Backbone address starts at data[2] (3 bytes);
578 ///< cross-confirmed by CMD_NODE_VERIFY_RESP (0x37),
579 ///< which returns the same 3 bytes for the same
580 ///< device (see that constant's comment).
581static constexpr uint8_t DISCOVERY_RESP_MANUFACTURER_OFFSET = 5; ///< Manufacturer ID at data[5].
582static constexpr uint8_t DISCOVERY_RESP_FLAGS_OFFSET = 6; ///< Flags byte at data[6].
583static constexpr uint8_t DISCOVERY_RESP_TIMESTAMP_OFFSET = 7; ///< Timestamp starts at data[7] (2 bytes).
584static constexpr uint8_t DISCOVERY_RESP_FULL_SIZE = 9; ///< Full discovery response payload size.
585/// @}
586
587// === Multi Information Byte (Discovery Response data[6]) ===
588// The flags byte in the discovery response encodes device capabilities and timing
589// characteristics that help a controller tune its interaction with the actuator.
590
591/// @brief Bit masks and shifts for the Multi Information Byte fields.
592/// @{
593static constexpr uint8_t DISCOVERY_FLAGS_ATT_MASK = 0xC0; ///< Bits [7:6]: actuator turnaround time class.
594static constexpr uint8_t DISCOVERY_FLAGS_ATT_SHIFT = 6; ///< Shift for ATT field extraction.
595static constexpr uint8_t DISCOVERY_FLAGS_SYNC_CTRL_GRP = 0x20; ///< Bit 5: supports sync control group.
596static constexpr uint8_t DISCOVERY_FLAGS_RF_SUPPORT =
597 0x08; ///< Bit 3: 1 = the node has its own RF, 0 = it sits on a wired backbone only (VELUX KLF 200 API
598 ///< v3.18 §7.4.1.2.4-7, Table 52).
599static constexpr uint8_t DISCOVERY_FLAGS_IO_MEMBERSHIP =
600 0x04; ///< Bit 2: io-homecontrol membership; always 1 (same table).
601static constexpr uint8_t DISCOVERY_FLAGS_POWER_SAVE_MASK = 0x03; ///< Bits [1:0]: power save mode.
602/// @}
603
604/// @brief Actuator Turnaround Time (ATT) class values.
605///
606/// The time within which each node must respond after receiving a command (VELUX KLF 200 API
607/// v3.18 §7.4.1.2.4-7, Table 51: "Actuator Turnaround time"). The unit is milliseconds.
608/// Extracted from the Multi Information Byte via
609/// `(flags & DISCOVERY_FLAGS_ATT_MASK) >> DISCOVERY_FLAGS_ATT_SHIFT`.
610/// @{
611static constexpr uint8_t ATT_CLASS_5MS = 0; ///< Response within 5 ms.
612static constexpr uint8_t ATT_CLASS_10MS = 1; ///< Response within 10 ms.
613static constexpr uint8_t ATT_CLASS_20MS = 2; ///< Response within 20 ms.
614static constexpr uint8_t ATT_CLASS_40MS = 3; ///< Response within 40 ms.
615/// @}
616
617/// @brief Power save mode values from the Multi Information Byte.
618///
619/// Extracted from `flags & DISCOVERY_FLAGS_POWER_SAVE_MASK`.
620/// Devices in low-power mode require long preamble (1024 bytes) to wake their receiver.
621/// @{
622static constexpr uint8_t POWER_SAVE_ALWAYS_ALIVE = 0; ///< Device is always listening — short preamble works.
623static constexpr uint8_t POWER_SAVE_LOW_POWER = 1; ///< Device sleeps — needs long preamble to wake.
624/// @}
625
626/// @brief Extract the ATT class field from a discovery response's Multi Information Byte.
627/// @param flags Multi Information Byte (data[DISCOVERY_RESP_FLAGS_OFFSET]).
628/// @return ATT class value (0–3); pass to att_class_name() for a human-readable string.
629inline uint8_t discovery_att_class(uint8_t flags) {
631}
632
633/// @brief Extract the power save mode field from a discovery response's Multi Information Byte.
634/// @param flags Multi Information Byte (data[DISCOVERY_RESP_FLAGS_OFFSET]).
635/// @return Power save mode value (0–1); pass to power_save_mode_name() for a human-readable string.
636inline uint8_t discovery_power_save_mode(uint8_t flags) { return flags & DISCOVERY_FLAGS_POWER_SAVE_MASK; }
637
638/// @brief Get a human-readable turnaround time string for an ATT class value.
639/// @param att_class ATT class (0–3) extracted from the Multi Information Byte.
640/// @return Null-terminated string such as "5ms", "10ms", "20ms", or "40ms".
641const char *att_class_name(uint8_t att_class);
642
643/// @brief Get a human-readable power save mode name.
644/// @param mode Power save value (0–1) extracted from the Multi Information Byte.
645/// @return Null-terminated string such as "always_alive" or "low_power".
646const char *power_save_mode_name(uint8_t mode);
647
648// ============================================================================
649// Command Result Codes
650// ============================================================================
651
652/// @brief Return a stable symbolic name for a CMD_ERROR_RESP result code.
653/// @param result Result byte from CMD_ERROR_RESP data[0].
654/// @return Uppercase symbolic name, or "UNKNOWN_RESULT_CODE" when unmapped.
655const char *command_result_name(uint8_t result);
656/// @brief Return a human-readable explanation for a CMD_ERROR_RESP result code.
657/// @param result Result byte from CMD_ERROR_RESP data[0].
658/// @return Short description suitable for warn-level logs.
659const char *command_result_description(uint8_t result);
660/// @brief Check whether a result code represents an environmental or control limitation.
661/// @param result Result byte from CMD_ERROR_RESP data[0].
662/// @return true when the response reports a limitation rather than a generic execution error.
663bool is_limitation_result(uint8_t result);
664
665} // namespace home_io_control
666} // namespace esphome
static constexpr uint8_t ATT_CLASS_10MS
Response within 10 ms.
static constexpr uint8_t ACEI_LEVEL_USER_HIGH
High-priority user controller.
const char * manufacturer_name(uint8_t id)
Get a human-readable manufacturer name from the protocol manufacturer byte.
static constexpr uint8_t RESULT_OUT_OF_RANGE
Requested value is out of range.
static constexpr uint8_t MANUFACTURER_HONEYWELL
Honeywell.
static constexpr uint8_t RESULT_CALIBRATING
Node is calibrating.
static constexpr uint8_t ACEI_LEVEL_COMFORT_1
Comfort automation level 1.
uint8_t discovery_power_save_mode(uint8_t flags)
Extract the power save mode field from a discovery response's Multi Information Byte.
static constexpr uint8_t CMD_DISCOVER_REQ
Broadcast discovery request.
static constexpr uint8_t CMD_SET_CONFIG1
Configure device to auto-send status updates.
static constexpr uint8_t RESULT_COLOUR_NOT_REACHABLE
Requested colour not reachable.
static constexpr uint8_t RESULT_LIMITATION_BY_AUTOMATIC_CYCLE
Parameter limited by an automatic cycle.
static constexpr uint8_t ORIGINATOR_UPS
Uninterruptible power supply action.
static constexpr uint8_t ORIGINATOR_LOCAL_LIGHT
Local light sensor.
static constexpr uint8_t CMD_KEY_TRANSFER
Send encrypted system key to device.
static constexpr uint8_t RESULT_TARGET_MODIFIED
Node modified the requested target value.
static constexpr uint8_t RESULT_REACHED_WRONG_POSITION
Node stopped in another position than expected.
static constexpr uint8_t RESULT_LIMITATION_BY_MYSELF
Parameter limited by the node itself.
static constexpr uint8_t CMD_SERVICE_STATUS_ACK
No description available at all for this opcode, not even a hedge.
static constexpr uint8_t TRANSFER_KEY[AES_KEY_SIZE]
The transfer key is a hardcoded key used ONLY during pairing to obfuscate the system key during over-...
static constexpr uint8_t CMD_ERROR_RESP
Error response to any command.
static constexpr uint8_t CMD_GET_NAME_RESP
Device name response.
static constexpr uint8_t RESULT_AUTOMATIC_CYCLE_ENGAGED
Node entered automatic cycle mode.
static constexpr uint8_t CMD_DISCOVER_CONFIRM_ACK
Device acknowledges confirmation.
static constexpr uint8_t CMD_ACTIVATE_MODE
Activate device mode (scene, ventilation) — requires auth.
static constexpr uint8_t MANUFACTURER_CIAT
CIAT (HVAC).
static constexpr uint8_t RESULT_MODE_NOT_IMPLEMENTED
Mode is not supported by the node.
static constexpr uint8_t ACEI_LEVEL_USER_DEFAULT
Default remote controller priority.
static constexpr uint8_t RESULT_COMMAND_OVERRULED
Command was overruled by a newer command.
const char * att_class_name(uint8_t att_class)
Get a human-readable turnaround time string for an ATT class value.
static constexpr uint8_t RESULT_INVALID_FUNCTION_INDEX
CMD_PRIVATE-family function ID / sub-index / selector-block outside the range the device implements.
static constexpr uint8_t MANUFACTURER_VELUX
IO-Homecontrol manufacturer ID constants.
static constexpr uint8_t DISCOVERY_RESP_MANUFACTURER_OFFSET
Manufacturer ID at data[5].
static constexpr uint8_t POS_UNKNOWN
Wire value: position unknown / keep current.
static constexpr uint8_t RESULT_BAD_INDEX_RECEIVED
Invalid index received.
static constexpr uint8_t RESULT_MANUALLY_OPERATED
Manually operated by a user.
static constexpr uint8_t POS_FORCE_OPEN
Ambiguous wire value used only for passive 1W-traffic intent decoding (decode_1w_main_intent() / onew...
static constexpr uint8_t CMD_STATUS_UPDATE
Device-initiated status update (needs auth).
static constexpr uint8_t ACEI_LEVEL_AUTO_SAAC
Stand-alone automatic controller.
const char * power_save_mode_name(uint8_t mode)
Get a human-readable power save mode name.
static constexpr uint8_t CMD_NODE_VERIFY_REQ
Node verification request: a controller checks that a node belongs to its system.
static constexpr uint8_t CMD_UNKNOWN4A_REQ
Reads chunk <handle><chunk u16> of a buffer opened by a 0x46/0x47 exchange; seen on air between two c...
static constexpr uint8_t DISCOVERY_FLAGS_RF_SUPPORT
Bit 3: 1 = the node has its own RF, 0 = it sits on a wired backbone only (VELUX KLF 200 API v3....
static constexpr uint8_t ACEI_LEVEL_PROTECTION_SENSOR
Goods/environment protection via sensors.
static constexpr uint8_t RESULT_LIMITATION_BY_UNKNOWN_DEVICE
Parameter limited by an unknown device.
static constexpr uint8_t CMD_DISCOVER_ALT_REQ
Alternate discovery.
static constexpr uint8_t POSITION_WIRE_MAX
Highest doubled-position wire value: 100% * POSITION_WIRE_SCALE.
static constexpr uint8_t RESULT_LOCK_POSITION_OPEN
Lock command failed because the door is open.
static constexpr uint8_t CMD_PRIVATE2_RESP
Response to CMD_PRIVATE2. See CMD_PRIVATE2's comment.
static constexpr uint8_t RESULT_WRONG_SYSTEMKEY
Node contains the wrong system key.
static constexpr uint8_t POSITION_WIRE_SCALE
Scale factor between a 0-100 percent position and its CMD_EXECUTE main-byte wire value.
static constexpr uint8_t CMD_GET_NAME
Request device name.
static constexpr uint8_t MANUFACTURER_RENSON
Renson (ventilation, sun protection).
static constexpr uint8_t POWER_SAVE_ALWAYS_ALIVE
Power save mode values from the Multi Information Byte.
const char * command_name(uint8_t cmd)
Get a human-readable name for any IO-Homecontrol command ID.
static constexpr uint8_t ORIGINATOR_LOAD_SHEDDING
Load-shedding manager.
static constexpr uint8_t ORIGINATOR_LIFESTYLE
Lifestyle scenario controller.
static constexpr uint8_t ATT_CLASS_20MS
Response within 20 ms.
static constexpr uint8_t MANUFACTURER_NIKO
Niko (switches, home automation).
static constexpr uint8_t CMD_DISCOVER_SPE_RESP
Roll-call reply to CMD_DISCOVER_SPE_REQ, sent only by devices that already hold the requesting contro...
static constexpr uint8_t ACEI_LEVEL_MASK
Bits [7:5]: priority level (0–7).
static constexpr uint8_t RESULT_LIMITATION_BY_EMERGENCY
Parameter limited by an emergency.
static constexpr uint8_t CMD_DISCOVER_ALT_RESP
Reply to an addressed (non-broadcast) CMD_DISCOVER_ALT_REQ, following a 0x3C/0x3D challenge-response.
static constexpr uint8_t RESULT_NODE_WAITING_FOR_POWER
Node is waiting for power.
static constexpr uint8_t ACEI_EXTENDED_SHIFT
Shift for extended field extraction.
static constexpr uint8_t RESULT_BATTERY_LEVEL
Battery level is low.
static constexpr uint8_t CMD_WRITE_PRIVATE
Write private register (climate/heating devices).
static constexpr uint8_t RESULT_MOTION_TIME_TOO_LONG
Target was not reached in time.
static constexpr uint8_t RESULT_THERMAL_PROTECTION
Node entered thermal protection mode.
static constexpr uint8_t DISCOVERY_RESP_BACKBONE_OFFSET
Byte offsets within CMD_DISCOVER_RESP (0x29) payload data.
static constexpr uint8_t RESULT_POWER_CONSUMPTION_TOO_HIGH
Node power consumption is too high.
static constexpr uint8_t ORIGINATOR_MYSELF
Actuator decided to move by itself.
static constexpr uint8_t RESULT_ERROR_DURING_EXECUTION
Generic execution failure.
static constexpr uint8_t CMD_KEY_CONFIRM
Device confirms key was received.
static constexpr uint8_t CMD_WRITE_PRIVATE_ACK
Acknowledgment to CMD_WRITE_PRIVATE.
static constexpr uint8_t ACEI_LEVEL_PROTECTION_HUMAN
ACEI priority level values (0–7).
static constexpr uint8_t RESULT_PARAMETER_LIMITED
Parameter limited by an unknown device.
static constexpr uint8_t CMD_KEY_INIT
Initiate key transfer to device.
static constexpr uint8_t RESULT_LIMITATION_BY_RAIN
Parameter limited by a rain sensor.
static constexpr uint8_t RESULT_USER_ACTION
User action overrode the command.
static constexpr uint8_t CMD_GET_GENERAL_INFO3_RESP
Never captured on our own wire.
const char * command_result_description(uint8_t result)
Return a human-readable explanation for a CMD_ERROR_RESP result code.
static constexpr uint8_t POS_VENT_MODIFIER
Modifier byte for the ventilation command.
static constexpr uint8_t CMD_GET_INFO1_RESP
Device general info 1 response.
static constexpr uint8_t ACEI_LEVEL_AUTO_DEFAULT
Default automatic level (lowest).
static constexpr uint8_t POS_DEFAULT
Wire value for the default position command.
static constexpr uint8_t MANUFACTURER_OVERKIZ
OVERKIZ (Somfy connectivity platform).
static constexpr uint8_t CMD_ONEWAY_ADD_CONTROLLER
1W "add controller" — a 1W device broadcasts this while its key-copy gesture is active,...
const char * command_result_name(uint8_t result)
Return a stable symbolic name for a CMD_ERROR_RESP result code.
static constexpr uint8_t DISCOVERY_FLAGS_ATT_MASK
Bit masks and shifts for the Multi Information Byte fields.
static constexpr uint8_t CMD_SET_NAME_RESP
Device-name write response.
static constexpr uint8_t RESULT_DEAD_BOLT_ERROR
Dead bolt error.
static constexpr uint8_t RESULT_NO_EXECUTION
Node did not move.
static constexpr uint16_t CRC_LSB_MASK
Least-significant-bit mask for reflected CRC update.
static constexpr uint8_t ACEI_LEVEL_COMFORT_2
Comfort automation level 2.
static constexpr uint8_t DISCOVERY_RESP_FLAGS_OFFSET
Flags byte at data[6].
static constexpr uint8_t ORIGINATOR_SECURITY
Security controlling device (SCD) action.
static constexpr uint8_t ORIGINATOR_EMERGENCY
Emergency command (never disabled).
static constexpr uint8_t CMD_PRIORITY_LEVEL_REQ
Priority-level (lock) query: a one-byte priority level in, the device's lock state for that level out...
static constexpr uint8_t ACEI_SERVICE_SHIFT
Shift for service field extraction.
static constexpr uint8_t MANUFACTURER_ID_MAX
Maximum manufacturer ID with a known name in the lookup table.
static constexpr uint8_t RESULT_LIMITATION_BY_WIND
Parameter limited by a wind sensor.
static constexpr uint8_t RESULT_LIMITATION_BY_UPS
Parameter limited by a power supply.
static constexpr uint8_t CMD_DISCOVER_SPE_REQ
Broadcast roll-call answered by every device that already holds this controller's system key,...
static constexpr uint8_t CMD_NODE_VERIFY_RESP
Node verification response: the device returns its own 3-byte backbone address, byte-identical to the...
static constexpr uint8_t CMD_EXECUTE
Set position/open/close/stop — requires authentication.
static constexpr uint8_t CMD_PRIVATE_RESP
Response to 0x00 and 0x03 (contains position data).
static constexpr uint8_t RESULT_LIMITATION_BY_USER
Parameter limited by a remote control.
static constexpr uint8_t ONEWAY_EXECUTE_ACEI_VELUX
ACEI byte for a 1W CMD_EXECUTE frame from a VELUX KLI-class remote.
static constexpr uint8_t RESULT_COMMAND_COMPLETED_OK
No errors detected.
static constexpr uint8_t ORIGINATOR_WIND_SENSOR
Wind sensor triggered the movement.
static constexpr uint8_t MANUFACTURER_ATLANTIC_GROUP
Atlantic Group (heating, hot water).
static constexpr uint8_t CMD_CHALLENGE_REQ
6-byte random challenge.
static constexpr uint8_t CMD_STATUS_UPDATE_RESP
Acknowledge status update.
static constexpr uint8_t RESULT_WRONG_POSITION
Node reports wrong position.
static constexpr uint8_t RESULT_COMMAND_INCOMPATIBLE_TO_MOVEMENT
Command cannot move the node that way.
static constexpr uint8_t ACEI_SERVICE_MASK
Bits [4:3]: service type.
static constexpr uint8_t ACEI_EXTENDED_MASK
Bits [2:1]: extended field.
static constexpr uint8_t EXECUTE_MAIN_BYTE_OFFSET
Offset of the main position/command byte in a CMD_EXECUTE payload — after the originator (first byte)...
static constexpr uint8_t RESULT_PRODUCT_NOT_OPERATIONAL
Node is not currently operational.
const char * acei_level_name(uint8_t level)
Get a human-readable name for an ACEI priority level (0–7).
static constexpr uint8_t CMD_SET_NAME
Set device name (authenticated).
static constexpr uint8_t CMD_SET_CONFIG1_RESP
Config response, otherwise undocumented.
static constexpr uint8_t CMD_LAUNCH_KEY_TRANSFER
Device-initiated ("pull") key transfer request: documented elsewhere as a command ID plus a 6-byte ch...
static constexpr uint8_t DISCOVERY_FLAGS_SYNC_CTRL_GRP
Bit 5: supports sync control group.
static constexpr uint8_t CMD_DISCOVER_CONFIRM
Confirm discovery to device.
static constexpr uint8_t ACEI_LEVEL_SHIFT
Shift for priority level extraction.
static constexpr uint8_t CMD_ONEWAY_REMOVE
1W "remove controller" (un-pair a 1W remote from a device); same payload shape as 0x2E.
static constexpr uint8_t POS_FAVORITE
Wire value: move to favorite/"My" position.
static constexpr uint8_t STATUS_EXPECTED
Byte 1 bit 7: device will send auto status update.
static constexpr uint8_t CMD_SEND_RAW_MESSAGE
Named "Send Raw Message" / "Find Hardware" — two candidate names, neither settled.
static constexpr uint8_t BROADCAST_DISCOVER[NODE_ID_SIZE]
Broadcast address for device discovery (0x00003B).
static constexpr uint8_t CMD_DISCOVER_RESP
Device responds with its ID and type.
static constexpr uint8_t RESULT_POWER_CONSUMPTION_TOO_LOW
Node power consumption is too low.
static constexpr uint8_t RESULT_FILTER_MAINTENANCE_NEEDED
Filter needs maintenance.
static constexpr uint8_t DISCOVERY_RESP_FULL_SIZE
Full discovery response payload size.
static constexpr uint8_t MANUFACTURER_ASSA_ABLOY
ASSA ABLOY (locks, access).
static constexpr uint8_t DISCOVERY_RESP_TIMESTAMP_OFFSET
Timestamp starts at data[7] (2 bytes).
static constexpr uint8_t RESULT_WRONG_LOAD_CONNECTED
Wrong load connected to node.
static constexpr uint8_t STATUS_TILT_SELECTOR
Extended status payload marker for tilt-capable devices: FPI1 with bit 5 set = functional parameter F...
static constexpr uint8_t MANUFACTURER_HORMANN
Hörmann (garage doors, gates).
static constexpr uint8_t CMD_READ_GROUPS
Named "Actuator: Read Groups" / "ActuatorAnyConfigIsLocal" (uncertain) / "Service ACK" — three...
static constexpr uint8_t BROADCAST_DISCOVER_ALT[NODE_ID_SIZE]
Alternate discovery / 1W broadcast address (0x00003F).
uint8_t discovery_att_class(uint8_t flags)
Extract the ATT class field from a discovery response's Multi Information Byte.
static constexpr uint8_t DISCOVERY_FLAGS_ATT_SHIFT
Shift for ATT field extraction.
static constexpr uint8_t ATT_CLASS_5MS
Actuator Turnaround Time (ATT) class values.
static constexpr uint8_t POWER_SAVE_LOW_POWER
Device sleeps — needs long preamble to wake.
static constexpr uint8_t DISCOVERY_FLAGS_POWER_SAVE_MASK
Bits [1:0]: power save mode.
static constexpr uint8_t CMD_GET_GENERAL_INFO3
Observed on the wire (tests/corpus/captures/probe/velux_kig300_probe_capability_burst....
static constexpr uint8_t PRIVATE_GET_POSITION_STATUS
CMD_PRIVATE (0x03) function ID for a position-status request — data[0] of the payload.
static constexpr uint8_t CMD_PRIORITY_LEVEL_RESP
Reply to CMD_PRIORITY_LEVEL_REQ.
static constexpr uint8_t ORIGINATOR_RAIN_SENSOR
Rain sensor triggered the movement.
static constexpr uint8_t CMD_IDENTIFY
Device physical identification / jog — requires authentication.
static constexpr uint8_t RESULT_LIMITATION_BY_SAAC
Parameter limited by a standalone automatic controller.
static constexpr uint8_t CMD_PRIVATE
Get device status — no authentication needed.
static constexpr uint8_t DISCOVERY_FLAGS_IO_MEMBERSHIP
Bit 2: io-homecontrol membership; always 1 (same table).
static constexpr uint8_t MANUFACTURER_WINDOW_MASTER
WINDOW MASTER (ventilation).
static constexpr uint8_t RESULT_LIMITATION_BY_TIMER
Parameter limited by a timer.
static constexpr uint8_t CMD_PRIVATE2
Content otherwise undecoded by the wire parser.
static constexpr uint8_t MANUFACTURER_SECUYOU
Secuyou (security).
static constexpr uint8_t CMD_GET_INFO2_RESP
Device type/model response.
static constexpr uint8_t RESULT_PRIORITY_LEVEL_LOCKED
Node is locked on this priority level.
static constexpr uint8_t ORIGINATOR_SAAC
Stand-alone automatic controller (SAAC).
static constexpr uint8_t POS_STOP
Position values in the IO protocol.
static constexpr uint8_t ORIGINATOR_USER_REMOTE
User sent command from a remote control.
static constexpr uint8_t ONEWAY_EXECUTE_ACEI
ACEI byte for a 1W CMD_EXECUTE frame — the Somfy-shaped default.
static constexpr uint8_t CMD_CHALLENGE_RESP
HMAC proof answering a 0x3C.
bool is_limitation_result(uint8_t result)
Check whether a result code represents an environmental or control limitation.
static constexpr uint8_t CMD_GET_INFO2
Request device type/model info.
const char * originator_name(uint8_t originator)
Get a human-readable name for a command originator byte.
static constexpr uint8_t RESULT_UNKNOWN_STATUS_REPLY
Device returned an unknown status reply.
static constexpr uint8_t CMD_GET_INFO1
Request device general info 1.
static constexpr uint8_t MANUFACTURER_SOMFY
Somfy (shutters, awnings, blinds).
static constexpr uint8_t ORIGINATOR_ENVIRONMENT
Unspecified environment sensor.
static constexpr uint8_t RESULT_LIMITS_NOT_SET
Device limits are not set.
static constexpr uint8_t CMD_REBOOT
Named "Reboot" / "Service Status" — two candidate names, one of them destructive-sounding,...
static constexpr uint8_t RESULT_NODE_LOCKED
Node is locked.
static constexpr uint8_t RESULT_PRIORITY_LOCKED_NON_EXEC
Priority locked, command not executed (ACEI priority too low).
static constexpr uint8_t ORIGINATOR_TIMER
Timer or schedule triggered the movement.
static constexpr uint16_t CRC_POLYNOMIAL_REVERSED
Reversed CRC-CCITT polynomial used by IO-homecontrol.
static constexpr uint8_t RESULT_NO_CONTACT
No communication to node.
static constexpr uint8_t ORIGINATOR_SMART_CONTROLLER
Smart function controller.
static constexpr uint8_t ATT_CLASS_40MS
Response within 40 ms.
static constexpr uint8_t STATUS_STOPPED
Status byte flags in CMD_PRIVATE_RESP and CMD_STATUS_UPDATE.
static constexpr uint8_t RESULT_BLOCKED
Node blocked by an object.
static constexpr uint8_t RESULT_INFORMATION_CODE
Information-only code with unknown semantics.
static constexpr uint8_t ORIGINATOR_AUTOMATIC_CYCLE
Automatic cycle / external access.
static constexpr uint8_t RESULT_LIMITATION_BY_LOCAL_USER
Parameter limited by local button.
static constexpr uint8_t RESULT_TARGET_NOT_REACHABLE
Requested target not reachable.
static constexpr uint8_t ACEI_VALID_BIT
ACEI byte bit-field definitions.
static constexpr uint8_t RESULT_IP_NOT_SET
Intermediate position is not set.
static constexpr uint8_t POS_SECURED_TARGET
Wire value for the secured target position command.
static constexpr uint8_t CMD_UNKNOWN4A_RESP
Echoes <handle><chunk> followed by up to 18 bytes of data.
static constexpr uint8_t ORIGINATOR_LOCAL_USER
Command originator codes indicating what or who triggered a command.
static constexpr uint8_t RESULT_LIMITATION_BY_SCD
Parameter limited by a security actuator.
Fundamental IO-Homecontrol frame and crypto size constants.