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