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// Data transport block (0x46-0x4B)
195static constexpr uint8_t CMD_TRANSFER_OPEN =
196 0x46; ///< Opens a read of one object: nine payload bytes `<originator> <class> 00 <id hi> <id lo>
197 ///< 00 00 00 00`. A Somfy Izymo answered it unauthenticated and challenged the block reads. Sent only by the
198 ///< `read_object` diagnostic probe (ADR 0062) and only for the objects in TRANSFER_OBJECT_*;
199 ///< create_transfer_open() refuses every other id. The sibling 0x48 opens a write and has no builder
200 ///< anywhere in this codebase.
201static constexpr uint8_t CMD_TRANSFER_OPEN_RESP =
202 0x47; ///< Answer to CMD_TRANSFER_OPEN: `<handle> <status> <length u16 BE>`. Status 0 = accepted,
203 ///< 1 = adapted, anything else an error; length 0 means there is nothing to read.
204static constexpr uint8_t CMD_UNKNOWN4A_REQ =
205 0x4A; ///< Reads chunk `<handle><chunk u16>` of a buffer opened by CMD_TRANSFER_OPEN, counting down
206 ///< from ceil(length/18) to 1, then `<handle> 00 00` closes it. Seen on air between two VELUX
207 ///< KLR 200 controllers in a copy session. Sent only by the `read_object` probe, through
208 ///< create_transfer_read(), whose payload is exactly these three bytes: a 0x4A that carries
209 ///< data is the write direction of the block and cannot be built here (ADR 0062).
210static constexpr uint8_t CMD_UNKNOWN4A_RESP =
211 0x4B; ///< Echoes `<handle><chunk>` followed by up to 18 bytes of data (none in the close echo). Also
212 ///< seen answering an ON_OFF_SWITCH-type device's traffic
213 ///< (tests/corpus/captures/probe/velux_kig300_probe_capability_burst.yaml), where the `01 00 00`
214 ///< payload has the shape of a close echo.
215
216/// @name Objects the `read_object` probe can open (CMD_TRANSFER_OPEN)
217/// Object class 0 ids. Allowlisted: create_transfer_open() refuses every other id.
218/// @{
219/// The actuator capacity record a Somfy controller reads after pairing (protocol version 4 or higher).
220/// \todo Byte layout unknown: it names the events the device can report, the sensor types it
221/// carries and its radio channels. Read to learn it.
222static constexpr uint16_t TRANSFER_OBJECT_ACTUATOR_CAPACITY = 0x8100;
223/// The actuator state record read beside the capacity record. \todo Byte layout unknown.
224static constexpr uint16_t TRANSFER_OBJECT_ACTUATOR_STATE = 0x8103;
225/// A 16-byte name record: on a Somfy Izymo dimmer `00` followed by the ASCII text "MODULE DIMMER" and
226/// zero padding, read behind an authenticated block read. Whether it always equals the CMD_GET_NAME text
227/// is not established. It is the object that shows the open/read/close flow works on a device before the
228/// two records above are read.
229static constexpr uint16_t TRANSFER_OBJECT_NAME = 0x8003;
230/// Two further class-0 records beside the name record. A Somfy Izymo dimmer returned the same bytes
231/// from them as from CMD_GET_INFO1 (0x8001, 14 bytes) and CMD_GET_INFO2 (0x8002, 16 bytes), so they
232/// are the object-transport form of those two answers.
233/// @{
234static constexpr uint16_t TRANSFER_OBJECT_GENERAL_INFO1 = 0x8001;
235static constexpr uint16_t TRANSFER_OBJECT_GENERAL_INFO2 = 0x8002;
236/// @}
237/// @}
238
239/// @name Data transport payload layout (CMD_TRANSFER_OPEN .. CMD_UNKNOWN4A_RESP)
240/// @{
241static constexpr uint8_t TRANSFER_OPEN_DATA_SIZE = 9; ///< CMD_TRANSFER_OPEN payload bytes.
242static constexpr uint8_t TRANSFER_OBJECT_CLASS_DEVICE = 0; ///< Object class of the TRANSFER_OBJECT_* ids.
243/// Object class of the Somfy manufacturer objects in TRANSFER_READABLE_OBJECTS.
244/// \todo No device has been captured answering an open of this class yet.
245static constexpr uint8_t TRANSFER_OBJECT_CLASS_SOMFY = 2;
246static constexpr uint8_t TRANSFER_OPEN_RESP_DATA_SIZE = 4; ///< CMD_TRANSFER_OPEN_RESP payload bytes.
247static constexpr uint8_t TRANSFER_READ_REQ_DATA_SIZE = 3; ///< CMD_UNKNOWN4A_REQ payload bytes: handle + chunk.
248static constexpr uint8_t TRANSFER_BLOCK_SIZE = 18; ///< Data bytes per CMD_UNKNOWN4A_RESP; 18 fills a 32-byte frame.
249static constexpr uint8_t TRANSFER_STATUS_ACCEPTED = 0; ///< CMD_TRANSFER_OPEN_RESP status: request accepted.
250static constexpr uint8_t TRANSFER_STATUS_ADAPTED = 1; ///< CMD_TRANSFER_OPEN_RESP status: accepted with changes.
251/// CMD_TRANSFER_OPEN_RESP status 7 with length 0: nothing to send for this object. An RS100 Solar io
252/// answered its object 0x6105 this way twice, with no other transfer open, so it reads as an empty
253/// object rather than a refusal (issue #156).
254static constexpr uint8_t TRANSFER_STATUS_NOTHING_TO_READ = 7;
255static constexpr uint16_t TRANSFER_CHUNK_CLOSE = 0; ///< Chunk number of the closing CMD_UNKNOWN4A_REQ.
256/// @}
257
258/// @brief One object a CMD_TRANSFER_OPEN can name: its class byte and its 16-bit id.
260 uint8_t object_class{TRANSFER_OBJECT_CLASS_DEVICE}; ///< TRANSFER_OBJECT_CLASS_DEVICE or TRANSFER_OBJECT_CLASS_SOMFY.
261 uint16_t id{0}; ///< Object id, sent big-endian.
262};
263
264/// @brief Every object the `read_object` probe can open, in probe-index order (ADR 0062).
265///
266/// create_transfer_open() refuses any class/id pair not in this table, so the table is the allowlist.
267/// Indexes 0 to 4 are the five named objects above. Indexes 5 to 9 are the other class-0 ids a Somfy
268/// RS100 Solar io lists in its own capacity record (issue #156); they have not been read yet. Indexes
269/// 10 to 29 are Somfy manufacturer objects to read on the same motor; a device that does not keep an
270/// object refuses the open (status 3, length 0) and nothing more is sent. Every entry is a plain read
271/// of a whole object. Appending keeps the existing indexes stable; never reorder.
272/// \todo The layouts of entries 5 to 29 are unknown: read them to learn what they hold.
274 // 0-4: the named class-0 objects.
280 // 5-9: further class-0 ids from the RS100 Solar's capacity record.
286 // 10-29: Somfy manufacturer objects.
307};
308/// Number of entries in TRANSFER_READABLE_OBJECTS.
309static constexpr uint8_t TRANSFER_READABLE_OBJECT_COUNT =
310 sizeof(TRANSFER_READABLE_OBJECTS) / sizeof(TRANSFER_READABLE_OBJECTS[0]);
311
312// Device info commands
313static constexpr uint8_t CMD_GET_NAME = 0x50; ///< Request device name
314static constexpr uint8_t CMD_GET_NAME_RESP = 0x51; ///< Device name response
315static constexpr uint8_t CMD_SET_NAME = 0x52; ///< Set device name (authenticated)
316static constexpr uint8_t CMD_SET_NAME_RESP = 0x53; ///< Device-name write response
317static constexpr uint8_t CMD_GET_INFO1 =
318 0x54; ///< Request device general info 1. Sent by the `get_info1` diagnostic probe
319 ///< (docs/diagnostic-probes.md, ADR 0024); field-observed on air from a real hub.
320static constexpr uint8_t CMD_GET_INFO1_RESP =
321 0x55; ///< Device general info 1 response. One capture in the corpus
322 ///< (tests/corpus/captures/probe/velux_window_probe_metadata_replies.yaml);
323 ///< nothing decodes it. Accepted by the soft-PHY so a probe reply is
324 ///< not dropped as an unknown command.
325static constexpr uint8_t CMD_GET_INFO2 = 0x56; ///< Request device type/model info. The only thing that sends
326 ///< it is the `get_info2` diagnostic probe; the corpus has no
327 ///< fixture for the request itself -- every 0x57 capture on
328 ///< hand is a reply to someone else's request. Closing this
329 ///< needs a fresh device-add on owned hardware, ingested with
330 ///< `--rekey` (tests/corpus/README.md).
331static constexpr uint8_t CMD_GET_INFO2_RESP = 0x57; ///< Device type/model response
332static constexpr uint8_t CMD_GET_GENERAL_INFO3 =
333 0x58; ///< Observed on the wire (tests/corpus/captures/probe/velux_kig300_probe_capability_burst.yaml)
334 ///< with no payload. Content undecoded. Not sent or handled anywhere in this codebase.
335static constexpr uint8_t CMD_GET_GENERAL_INFO3_RESP =
336 0x59; ///< Never captured on our own wire. This constant exists so a received 0x59 frame
337 ///< renders by name instead of as UNKNOWN_CMD. Not sent or handled anywhere in this
338 ///< codebase.
339
340// Configuration and status update commands
341static constexpr uint8_t CMD_SET_CONFIG1 = 0x6F; ///< Configure device to auto-send status updates
342static constexpr uint8_t CMD_SET_CONFIG1_RESP =
343 0x70; ///< Config response; meaning undecoded, never captured. Never applied to device state:
344 ///< the device-event recorder (IOHomeControlComponent::record_device_event_()) logs it raw.
345 ///< Logged under the name SET_CONFIG1_RESP whatever it carries.
346static constexpr uint8_t CMD_SUBSCRIBE_EVENT =
347 0x73; ///< Subscribe a device to one event: payload `<action> <event hi> <event lo>`, action
348 ///< EVENT_SUBSCRIPTION_SUBSCRIBE or EVENT_SUBSCRIPTION_UNSUBSCRIBE. Sent by the
349 ///< `subscribe_event` / `unsubscribe_event` diagnostic probes (docs/diagnostic-probes.md,
350 ///< ADR 0060) and by the opt-in sun level and battery level polls (ADR 0064), which read
351 ///< the answer. An RS100 Solar io answered a subscription to EVENT_LOCAL_SENSOR with a
352 ///< CMD_STATUS_UPDATE carrying that event
353 ///< (tests/corpus/captures/probe/somfy_rs100_probe_subscribe_event_sx1262.yaml).
354/// Device status update, sent by the device on its own (authenticated, acknowledged with
355/// CMD_STATUS_UPDATE_RESP) or as the answer to a CMD_SUBSCRIBE_EVENT. The payload is a flags byte,
356/// a status byte (battery in STATUS_BATTERY_MASK), a big-endian event id, the length of the event
357/// data, then the event data: see decode_status_update_event(). Position updates carry
358/// EVENT_STATUS_CHANGE.
359static constexpr uint8_t CMD_STATUS_UPDATE = 0x71;
360static constexpr uint8_t CMD_STATUS_UPDATE_RESP = 0x72; ///< Acknowledge status update
361
362/// @name Subscription action (first CMD_SUBSCRIBE_EVENT payload byte)
363/// @{
364static constexpr uint8_t EVENT_SUBSCRIPTION_SUBSCRIBE = 0x01; ///< Ask the device to report the event.
365/// Ask the device to stop reporting the event. \todo A guess from the frame's shape: no device has
366/// been seen to act on it. Kept only to test whether a subscription can be undone.
367static constexpr uint8_t EVENT_SUBSCRIPTION_UNSUBSCRIBE = 0x00;
368/// @}
369/// CMD_SUBSCRIBE_EVENT payload size: the action byte and the big-endian event id.
370static constexpr uint8_t SUBSCRIBE_EVENT_DATA_SIZE = 3;
371
372/// The event a position-bearing CMD_STATUS_UPDATE carries (every captured one, e.g.
373/// tests/corpus/captures/statuspoll/somfy_rs100_statuspoll_kig300_sx1276.yaml). The same two bytes
374/// follow the first byte of the CMD_SET_CONFIG1 payload the hub sends at the end of pairing.
375/// A CMD_STATUS_UPDATE carrying any other event is a device event report, never a position update.
376static constexpr uint16_t EVENT_STATUS_CHANGE = 0x100A;
377
378/// @name Subscribable device events (CMD_SUBSCRIBE_EVENT payload)
379/// The only two event ids the `subscribe_event` probe can send; create_subscribe_event() refuses every other id.
380/// @{
381/// A battery-powered device reports its charge level. An RS100 Solar io answered the subscription
382/// with this event and one data byte, 100, while its status byte read full
383/// (tests/corpus/captures/probe/somfy_rs100_probe_subscribe_event_battery_sx1262.yaml); see
384/// decode_battery_level_event().
385/// \todo Percent is a reading of that one value: no level below 100 has been captured.
386static constexpr uint16_t EVENT_BATTERY_LEVEL_CHANGE = 0x2001;
387/// A device reports a sensor built into it (sun, window, ...). An RS100 Solar io accepted the
388/// subscription and answered with this event: first data byte LOCAL_SENSOR_TYPE_SUN, then a byte,
389/// a big-endian 16-bit value and a byte; the value is 0 at night and rises with sun on the panel
390/// (tests/corpus/captures/probe/somfy_rs100_probe_subscribe_event_sx1262.yaml and
391/// somfy_rs100_probe_subscribe_event_daylight_sx1262.yaml); see decode_sun_level_event().
392/// \todo The value's unit and scale are unknown; the surrounding two bytes are undecoded.
393static constexpr uint16_t EVENT_LOCAL_SENSOR = 0x2005;
394/// @}
395
396/// @name Event data of the subscribable events
397/// Layouts captured from one RS100 Solar io (issue #156); a report of any other shape is not decoded.
398/// @{
399static constexpr uint8_t LOCAL_SENSOR_EVENT_DATA_LEN = 5; ///< EVENT_LOCAL_SENSOR data: `06 xx <hi> <lo> yy`.
400static constexpr uint8_t LOCAL_SENSOR_TYPE_SUN = 0x06; ///< First EVENT_LOCAL_SENSOR data byte on every capture.
401static constexpr uint8_t LOCAL_SENSOR_VALUE_OFFSET = 2; ///< Offset of the big-endian value inside the event data.
402static constexpr uint8_t BATTERY_LEVEL_EVENT_DATA_LEN = 1; ///< EVENT_BATTERY_LEVEL_CHANGE data: one level byte.
403static constexpr uint8_t BATTERY_LEVEL_MAX_PERCENT = 100; ///< Highest level read as a percentage.
404/// @}
405
406static constexpr uint8_t CMD_SEND_RAW_MESSAGE =
407 0xF0; ///< Named "Send Raw Message" / "Find Hardware" — two candidate names, neither settled.
408 ///< Never observed in our corpus or in any field log, and not sent or handled
409 ///< anywhere in this codebase.
410static constexpr uint8_t CMD_READ_GROUPS =
411 0xF1; ///< Named "Actuator: Read Groups" / "ActuatorAnyConfigIsLocal" (uncertain) / "Service
412 ///< ACK" — three candidate names, one itself flagged uncertain. Never observed in our
413 ///< corpus or in any field log, and not sent or handled anywhere in this codebase.
414static constexpr uint8_t CMD_REBOOT =
415 0xF2; ///< Named "Reboot" / "Service Status" — two candidate names, one of them
416 ///< destructive-sounding, on no field evidence at all. Never observed in our corpus or
417 ///< in any field log, and not sent or handled anywhere in this codebase; treat the
418 ///< "reboot" reading with particular caution — it is a guess.
419static constexpr uint8_t CMD_SERVICE_STATUS_ACK = 0xF3; ///< No description available at all for this opcode, not even
420 ///< a hedge. Never observed in our corpus or in any field log,
421 ///< and not sent or handled anywhere in this codebase.
422
423static constexpr uint8_t CMD_ERROR_RESP = 0xFE; ///< Error response to any command
424
425// === CMD_ERROR_RESP result byte ===
426//
427// DATA[0] of a CMD_ERROR_RESP packs a result code into bits [7:3] and an execution state into
428// bits [2:0]: byte = (code << 3) | state. Our captures fit this layout: every corpus 0xFE byte
429// but one has state 0, a device answers a command it does not implement with 0x08 = code 1
430// "command unknown" (tests/corpus/captures/probe/somfy_awning_probe_general_info3_lr1121.yaml,
431// velux_window_probe_metadata_replies.yaml, velux_kig300_probe_capability_burst.yaml), and a
432// priority lock comes back as 0x38 = code 7 (tests/corpus/captures/exchange/
433// tilt_cover_exchange_priority_locked_error.yaml). The RESULT_* values below that are multiples
434// of 8 (RESULT_COMMAND_UNKNOWN .. RESULT_ALREADY_CONFIGURED) are those codes already shifted into
435// place. The run-status values with state bits set (0x01-0x24) and the limitation values
436// (0xDF-0xEE) stay as a fallback: one Somfy capture does answer with 0x05
437// (tests/corpus/captures/identify/somfy_awning_identify_wrong_systemkey_sx1276.yaml), so not
438// every device uses the packed form.
439static constexpr uint8_t ERROR_RESP_CODE_SHIFT = 3; ///< Result code sits in bits [7:3] of the 0xFE byte.
440static constexpr uint8_t ERROR_RESP_STATE_MASK = 0x07; ///< Execution state sits in bits [2:0] of the 0xFE byte.
441static constexpr uint8_t ERROR_RESP_CODE_MAX = 0x11; ///< Highest named result code (17, "already configured").
442
443// Command-result / limitation codes carried in CMD_ERROR_RESP DATA[0].
444static constexpr uint8_t RESULT_UNKNOWN_STATUS_REPLY = 0x00; ///< Device returned an unknown status reply.
445static constexpr uint8_t RESULT_COMMAND_COMPLETED_OK = 0x01; ///< No errors detected.
446static constexpr uint8_t RESULT_NO_CONTACT = 0x02; ///< No communication to node.
447static constexpr uint8_t RESULT_MANUALLY_OPERATED = 0x03; ///< Manually operated by a user.
448static constexpr uint8_t RESULT_BLOCKED = 0x04; ///< Node blocked by an object.
449static constexpr uint8_t RESULT_WRONG_SYSTEMKEY = 0x05; ///< Node contains the wrong system key.
450static constexpr uint8_t RESULT_PRIORITY_LEVEL_LOCKED = 0x06; ///< Node is locked on this priority level.
451static constexpr uint8_t RESULT_REACHED_WRONG_POSITION = 0x07; ///< Node stopped in another position than expected.
452static constexpr uint8_t RESULT_NO_EXECUTION = 0x09; ///< Node did not move.
453static constexpr uint8_t RESULT_CALIBRATING = 0x0A; ///< Node is calibrating.
454static constexpr uint8_t RESULT_POWER_CONSUMPTION_TOO_HIGH = 0x0B; ///< Node power consumption is too high.
455static constexpr uint8_t RESULT_POWER_CONSUMPTION_TOO_LOW = 0x0C; ///< Node power consumption is too low.
456static constexpr uint8_t RESULT_LOCK_POSITION_OPEN = 0x0D; ///< Lock command failed because the door is open.
457static constexpr uint8_t RESULT_MOTION_TIME_TOO_LONG = 0x0E; ///< Target was not reached in time.
458static constexpr uint8_t RESULT_THERMAL_PROTECTION = 0x0F; ///< Node entered thermal protection mode.
459static constexpr uint8_t RESULT_FILTER_MAINTENANCE_NEEDED = 0x11; ///< Filter needs maintenance.
460static constexpr uint8_t RESULT_BATTERY_LEVEL = 0x12; ///< Battery level is low.
461static constexpr uint8_t RESULT_TARGET_MODIFIED = 0x13; ///< Node modified the requested target value.
462static constexpr uint8_t RESULT_MODE_NOT_IMPLEMENTED = 0x14; ///< Mode is not supported by the node.
463static constexpr uint8_t RESULT_COMMAND_INCOMPATIBLE_TO_MOVEMENT = 0x15; ///< Command cannot move the node that way.
464static constexpr uint8_t RESULT_USER_ACTION = 0x16; ///< User action overrode the command.
465static constexpr uint8_t RESULT_DEAD_BOLT_ERROR = 0x17; ///< Dead bolt error.
466static constexpr uint8_t RESULT_WRONG_LOAD_CONNECTED = 0x19; ///< Wrong load connected to node.
467static constexpr uint8_t RESULT_COLOUR_NOT_REACHABLE = 0x1A; ///< Requested colour not reachable.
468static constexpr uint8_t RESULT_TARGET_NOT_REACHABLE = 0x1B; ///< Requested target not reachable.
469static constexpr uint8_t RESULT_BAD_INDEX_RECEIVED = 0x1C; ///< Invalid index received.
470static constexpr uint8_t RESULT_COMMAND_OVERRULED = 0x1D; ///< Command was overruled by a newer command.
471static constexpr uint8_t RESULT_NODE_WAITING_FOR_POWER = 0x1E; ///< Node is waiting for power.
472static constexpr uint8_t RESULT_WRONG_POSITION = 0x21; ///< Node reports wrong position.
473static constexpr uint8_t RESULT_LIMITS_NOT_SET = 0x22; ///< Device limits are not set.
474static constexpr uint8_t RESULT_IP_NOT_SET = 0x23; ///< Intermediate position is not set.
475static constexpr uint8_t RESULT_OUT_OF_RANGE = 0x24; ///< Requested value is out of range.
476// Packed result codes (code << 3, state 0 = "non executing"); see the block comment above.
477static constexpr uint8_t RESULT_COMMAND_UNKNOWN = 0x08; ///< Code 1: the device does not offer this command in its
478 ///< current state or class.
479static constexpr uint8_t RESULT_MODE_OR_STATE_UNKNOWN = 0x10; ///< Code 2: mode or state number unknown.
480static constexpr uint8_t RESULT_BAD_MAC = 0x18; ///< Code 3: challenge answer (MAC) did not verify.
481static constexpr uint8_t RESULT_SETUP_REQUIRED = 0x20; ///< Code 4: the device needs setting up first.
482static constexpr uint8_t RESULT_PARAMETER_UNKNOWN = 0x28; ///< Code 5: parameter, alias, access method or scenario
483 ///< unknown. The Somfy Izymo answers our SetConfig1
484 ///< (0x6F) with it during pairing.
485static constexpr uint8_t RESULT_ORIGINATOR_DISABLED = 0x30; ///< Code 6: command originator disabled.
486static constexpr uint8_t RESULT_PRIORITY_LOCKED = 0x38; ///< Code 7: priority locked; the command's priority
487 ///< level is too low to run.
488static constexpr uint8_t RESULT_PRIORITY_SERVICE_NOT_SUPPORTED = 0x40; ///< Code 8: priority service not supported.
489static constexpr uint8_t RESULT_PARAMETER_LIMITATION_ADJUSTMENT = 0x48; ///< Code 9: parameter limitation adjustment.
491 0x50; ///< Code 10: parameter incoherence adjustment.
492static constexpr uint8_t RESULT_TOTAL_INCOHERENCE = 0x58; ///< Code 11: the request does not fit what the device
493 ///< implements. Seen cross-vendor (Somfy Sunea awning +
494 ///< dimmer, Velux window) when a diagnostic probe walks
495 ///< past the last supported function index or block.
496static constexpr uint8_t RESULT_SERVICE_FULL = 0x60; ///< Code 12: service full.
497static constexpr uint8_t RESULT_ALIAS_INCOHERENCE = 0x68; ///< Code 13: alias incoherence.
498static constexpr uint8_t RESULT_SESSION_INTERRUPTED = 0x70; ///< Code 14: session interrupted.
499static constexpr uint8_t RESULT_NOT_IMPLEMENTED = 0x78; ///< Code 15: not implemented.
500static constexpr uint8_t RESULT_COMMAND_REJECTED = 0x80; ///< Code 16: command rejected. A Somfy Sunilus and a
501 ///< Somfy J406io answered a key transfer (0x32) with it.
502static constexpr uint8_t RESULT_ALREADY_CONFIGURED = 0x88; ///< Code 17: already configured.
503static constexpr uint8_t RESULT_INFORMATION_CODE = 0xDF; ///< Information-only code with unknown semantics.
504static constexpr uint8_t RESULT_PARAMETER_LIMITED = 0xE0; ///< Parameter limited by an unknown device.
505static constexpr uint8_t RESULT_LIMITATION_BY_LOCAL_USER = 0xE1; ///< Parameter limited by local button.
506static constexpr uint8_t RESULT_LIMITATION_BY_USER = 0xE2; ///< Parameter limited by a remote control.
507static constexpr uint8_t RESULT_LIMITATION_BY_RAIN = 0xE3; ///< Parameter limited by a rain sensor.
508static constexpr uint8_t RESULT_LIMITATION_BY_TIMER = 0xE4; ///< Parameter limited by a timer.
509static constexpr uint8_t RESULT_LIMITATION_BY_SCD = 0xE5; ///< Parameter limited by a security actuator.
510static constexpr uint8_t RESULT_LIMITATION_BY_UPS = 0xE6; ///< Parameter limited by a power supply.
511static constexpr uint8_t RESULT_LIMITATION_BY_UNKNOWN_DEVICE = 0xE7; ///< Parameter limited by an unknown device.
512static constexpr uint8_t RESULT_LIMITATION_BY_SAAC = 0xEA; ///< Parameter limited by a standalone automatic controller.
513static constexpr uint8_t RESULT_LIMITATION_BY_WIND = 0xEB; ///< Parameter limited by a wind sensor.
514static constexpr uint8_t RESULT_LIMITATION_BY_MYSELF = 0xEC; ///< Parameter limited by the node itself.
515static constexpr uint8_t RESULT_LIMITATION_BY_AUTOMATIC_CYCLE = 0xED; ///< Parameter limited by an automatic cycle.
516static constexpr uint8_t RESULT_LIMITATION_BY_EMERGENCY = 0xEE; ///< Parameter limited by an emergency.
517
518// ============================================================================
519// Position and Status Wire Constants
520// ============================================================================
521
522/// Position values in the IO protocol.
523/// Normal positions are 0-100 (0=fully open, 100=fully closed).
524/// Special values above 100 are control commands encoded as the "main" parameter
525/// byte in CMD_EXECUTE payloads. These are internal wire constants — callers should
526/// prefer CoverCommand for type-safe command dispatch.
527static constexpr uint8_t POS_STOP = 0xD2; ///< Wire value: stop movement.
528static constexpr uint8_t POS_UNKNOWN = 0xD4; ///< Wire value: position unknown / keep current.
529static constexpr uint8_t POS_FAVORITE = 0xD8; ///< Wire value: move to favorite/"My" position.
530/// 16-bit main parameter `D400` (POS_UNKNOWN in the high byte): keep the current value. Sent with a
531/// functional parameter that carries the actual change (a heat pump's off, a water heater's operating mode).
532static constexpr uint16_t MAIN_PARAMETER_KEEP = static_cast<uint16_t>(POS_UNKNOWN) << BITS_PER_BYTE;
533
534/// Offset of the main position/command byte in a CMD_EXECUTE payload — after the originator (first
535/// byte) and the ACEI (second byte). Holds a 0-100 position scale value or one of the POS_* codes.
536static constexpr uint8_t EXECUTE_MAIN_BYTE_OFFSET = 2;
537
538/// @brief Wire value for the secured target position command.
539///
540/// Moves the actuator to its pre-programmed secured/safety position from the
541/// Execution Parameter Buffer. Typically sent by environmental sensors (wind, rain)
542/// to retract an awning or close a shutter to a wind-safe state.
543static constexpr uint8_t POS_SECURED_TARGET = 0xD1;
544
545/// @brief Wire value for the default position command.
546///
547/// Moves the actuator to its factory or user-configured default position.
548static constexpr uint8_t POS_DEFAULT = 0xD3;
549
550/// @brief Ambiguous wire value used only for passive 1W-traffic intent decoding
551/// (decode_1w_main_intent() / oneway_intent_to_target() in proto_codecs.cpp).
552///
553/// 0x64 (100) is simultaneously the ordinary doubled-position wire value for 50% and a value
554/// some physical 1W remotes send for their "force open" button — the protocol has no dedicated
555/// override code, so the two are indistinguishable on the wire. For passively decoding someone
556/// else's remote traffic, "FORCE_OPEN" is the more useful diagnostic label (physical remotes
557/// rarely send a numeric 50%). This is NOT used by any outbound builder in this codebase:
558/// real-hardware testing confirmed that sending main=0x64 as an outbound 2W
559/// CMD_EXECUTE command makes a real device move to 50% open, not bypass anything — see
560/// create_force_open() in proto_commands.cpp for the actual (ACEI-priority-based) force-open
561/// implementation.
562static constexpr uint8_t POS_FORCE_OPEN = 0x64;
563
564/// @brief Modifier byte for the ventilation command.
565///
566/// Both favorite and ventilation use POS_FAVORITE (0xD8) as the main parameter byte,
567/// but ventilation sets the secondary byte (main[1]) to 0x03 while favorite leaves it 0x00.
568static constexpr uint8_t POS_VENT_MODIFIER = 0x03;
569
570/// @brief Modifier byte for the pedestrian-position command (POS_FAVORITE main byte, as for ventilation).
571/// Sent by the Pedestrian Position button on gate openers.
572/// \todo Confirm on a real gate opener that main=0xD8, modifier=0x07 opens it to a pedestrian position
573/// (experimental).
574static constexpr uint8_t POS_PEDESTRIAN_MODIFIER = 0x07;
575
576/// @brief Modifier byte for the partial-position command (POS_FAVORITE main byte, as for ventilation).
577/// Sent by the Partial Position button on garage openers, which run their stored partial position
578/// (issue #163).
579static constexpr uint8_t POS_PARTIAL_MODIFIER = 0x09;
580
581/// @brief Scale factor between a 0-100 percent position and its CMD_EXECUTE main-byte wire value.
582///
583/// Percent 0-100 maps to wire 0-200 (wire = percent * POSITION_WIRE_SCALE), leaving 201-255 free
584/// for the POS_* special codes above. Builders multiply by this to encode a position; decoders
585/// divide by it to recover one — both directions belong on this one constant so they cannot
586/// silently drift apart into two different bare "2"s.
587static constexpr uint8_t POSITION_WIRE_SCALE = 2;
588/// @brief Highest doubled-position wire value: 100% * POSITION_WIRE_SCALE. A main byte at or
589/// below this is an ordinary position; above it, one of the POS_* special codes.
590static constexpr uint8_t POSITION_WIRE_MAX = 200;
591
592/// Status byte flags in CMD_PRIVATE_RESP and CMD_STATUS_UPDATE.
593static constexpr uint8_t STATUS_STOPPED = 0x01; ///< Byte 0 bit 0: device is not moving
594static constexpr uint8_t STATUS_EXPECTED = 0x80; ///< Byte 1 bit 7: device will send auto status update
595/// Byte 1 bits 5-6: the device's coarse battery level (BatteryState). Every solar Somfy actuator
596/// captured reports 3 here and every mains one 0 (tests/corpus/captures/statuspoll/
597/// somfy_rs100_statuspoll_kig300_sx1276.yaml, somfy_oximo40_statuspoll_sx1262.yaml,
598/// somfy_awning_statuspoll_success_sx1262.yaml).
599static constexpr uint8_t STATUS_BATTERY_MASK = 0x60;
600static constexpr uint8_t STATUS_BATTERY_SHIFT = 5; ///< Right shift that brings STATUS_BATTERY_MASK down to 0-3.
601static constexpr uint8_t STATUS_TILT_SELECTOR =
602 0x20; ///< Extended status payload marker for tilt-capable devices: FPI1 with bit 5 set = functional parameter
603 ///< FP3 (FPI1 bit 7 = FP1 ... bit 0 = FP8). The actuator echoes FPI1/FPI2 in its ack (VELUX KLF 200 API
604 ///< v3.18 §10.1.1.5). The 0x80 form of the extended 0x03 request selects FP1.
605
606/// @brief CMD_PRIVATE (0x03) function ID for a position-status request — data[0] of the payload.
607///
608/// The only function ID this codebase has ever captured on its own wire: it is what
609/// create_get_status() freezes create_private_function() at, and the default `function_id` of
610/// create_get_status_extended(). Lives here rather than in proto_commands.cpp because that
611/// default argument is spelled in proto_commands.h.
612static constexpr uint8_t PRIVATE_GET_POSITION_STATUS = 0x03;
613
614/// @name CMD_PRIVATE status read of the main parameter plus functional parameters (MP + FP1-FP7)
615/// The payload create_status_mp_fp_read() sends is a replay of the status read a VELUX KLF 200
616/// gateway sends to its devices (KLF 200 API specification v3.18 §10.3.1.4: status type 1 =
617/// current position, FPI1 selecting FP1-FP7). The bytes are on-air evidence, not a decoded
618/// request: the reply layout is not interpreted by this codebase.
619/// @{
620static constexpr uint8_t PRIVATE_STATUS_MP_FP = 0x01; ///< CMD_PRIVATE function ID at data[0].
621static constexpr uint8_t STATUS_MP_FP_FPI1_MASK = 0xFE; ///< FPI1: FP1..FP7 requested (bit 7 = FP1 ... bit 1 = FP7).
622static constexpr uint8_t STATUS_MP_FP_PARAM_TYPE = 0x01; ///< Status type per requested parameter: current value.
623static constexpr uint8_t STATUS_MP_FP_PARAM_COUNT =
624 7; ///< Largest selection seen on air (FP1-FP7), kept as a conservative cap across both bitmaps.
625static constexpr uint8_t STATUS_MP_FP_FPI2 =
626 0x00; ///< FPI2: no FP9-FP16 requested (bit 7 = FP9 ... bit 0 = FP16 otherwise).
627/// @}
628
629/// @brief Bit of functional parameter `fp` (1-16) in its bitmap: FPI1 holds FP1-FP8 and FPI2 FP9-FP16,
630/// each with the lowest-numbered parameter in bit 7. One rule for EXECUTE and status bitmaps alike.
631/// @param fp Functional parameter number, 1-16.
632/// @return The single bit of `fp` in its bitmap.
633constexpr uint8_t fp_bitmap_bit(uint8_t fp) {
634 constexpr unsigned first_parameter_bit = 1U << (BITS_PER_BYTE - 1U);
635 return static_cast<uint8_t>(first_parameter_bit >> ((fp - 1U) % BITS_PER_BYTE));
636}
637
638/// @name Limitation read (CMD_LIMITATION_STATUS_REQ / CMD_LIMITATION_STATUS_RESP)
639/// The layout is a hypothesis built from frames a VELUX KLF 200 was heard sending (issue #98) and
640/// the KLF 200 API specification v3.18, section 10.5 (get limitation: parameter id and limitation
641/// type, 0 = resulting minimum, 1 = resulting maximum). Confirm it against a reply captured under
642/// an active limitation before building on it.
643/// @{
644
645/// On-air selector byte (request data[0]) choosing which resulting limit to read. Exactly two
646/// values exist on purpose: the same opcode probably also carries the limitation write of section
647/// 10.5.2, so no other selector may be constructible. The minimum/maximum assignment rests on
648/// request order only (the minimum read always came first, with 0x80).
649enum class LimitationType : uint8_t {
650 MINIMUM = 0x80, ///< Resulting minimum limit; rain protection raises it (a floor on the position value).
651 MAXIMUM = 0xC0, ///< Resulting maximum limit.
652};
653
654static constexpr uint8_t LIMITATION_PARAM_MP = 0x00; ///< Parameter id of the main parameter.
655static constexpr uint8_t LIMITATION_PARAM_FP_FIRST = 1; ///< Parameter id of FP1.
656static constexpr uint8_t LIMITATION_PARAM_FP_LAST = 16; ///< Parameter id of FP16.
657static constexpr uint8_t LIMITATION_REQ_DATA_SIZE = 3; ///< Request data: selector, parameter id, 0x00.
658static constexpr uint8_t LIMITATION_RESP_DATA_SIZE = 5; ///< Reply data: parameter id, value (2), originator, time.
659
660/// Remaining-time codes of a limitation reply's last byte (KLF 200 API specification v3.18, table
661/// 200): codes 0-252 count `(n + 1) * 30` seconds, 253 means no time limit, and 254/255 are the
662/// timer values the set command uses to delete an entry (never a duration).
663static constexpr uint8_t LIMITATION_TIME_MAX_COUNTED = 252; ///< Highest code that encodes a duration.
664static constexpr uint8_t LIMITATION_TIME_UNLIMITED = 253; ///< Limitation without a time limit.
665static constexpr uint32_t LIMITATION_TIME_STEP_S = 30; ///< Seconds per time-code step.
666/// @}
667
668// ============================================================================
669// Cryptographic Constants
670// ============================================================================
671
672/// The transfer key is a hardcoded key used ONLY during pairing to obfuscate
673/// the system key during over-the-air transfer. It is NOT the system key.
674/// This is the same across all IO-Homecontrol devices worldwide.
675static constexpr uint8_t TRANSFER_KEY[AES_KEY_SIZE] = {0x34, 0xC3, 0x46, 0x6E, 0xD8, 0x8F, 0x4E, 0x8E,
676 0x16, 0xAA, 0x47, 0x39, 0x49, 0x88, 0x43, 0x73};
677static constexpr uint16_t CRC_POLYNOMIAL_REVERSED = 0x8408; ///< Reversed CRC-CCITT polynomial used by IO-homecontrol
678static constexpr uint16_t CRC_LSB_MASK = 0x0001; ///< Least-significant-bit mask for reflected CRC update
679
680/// Broadcast address for device discovery (0x00003B).
681/// Used as destination in CMD_DISCOVER_REQ frames to trigger all pairable devices to respond.
682static constexpr uint8_t BROADCAST_DISCOVER[NODE_ID_SIZE] = {0x00, 0x00, 0x3B};
683
684/// Alternate discovery / 1W broadcast address (0x00003F).
685/// Used as destination for CMD_DISCOVER_ALT_REQ (0x2E) alternate discovery, and the address
686/// on which devices in 1W-triggered pairing mode listen. Distinct from the 2W discovery
687/// broadcast BROADCAST_DISCOVER (0x00003B).
688static constexpr uint8_t BROADCAST_DISCOVER_ALT[NODE_ID_SIZE] = {0x00, 0x00, 0x3F};
689
690// ============================================================================
691// Command Name Lookup
692// ============================================================================
693
694/// @brief Get a human-readable name for any IO-Homecontrol command ID.
695///
696/// Returns a short uppercase identifier suitable for log lines (e.g., "EXECUTE",
697/// "DISCOVER_REQ", "CHALLENGE_RESP"). Unknown commands return "UNKNOWN_CMD".
698/// @param cmd Command byte from the frame header.
699/// @return Null-terminated string.
700const char *command_name(uint8_t cmd);
701
702// ============================================================================
703// Manufacturer ID Lookup
704// ============================================================================
705
706/// @brief Maximum manufacturer ID with a known name in the lookup table.
707static constexpr uint8_t MANUFACTURER_ID_MAX = 12;
708
709/// @brief IO-Homecontrol manufacturer ID constants.
710///
711/// These 1-based identifiers are assigned by the IO-Homecontrol alliance and
712/// appear in the discovery response payload at DISCOVERY_RESP_MANUFACTURER_OFFSET.
713/// @{
714static constexpr uint8_t MANUFACTURER_VELUX = 1; ///< VELUX (roof windows, skylights).
715static constexpr uint8_t MANUFACTURER_SOMFY = 2; ///< Somfy (shutters, awnings, blinds).
716static constexpr uint8_t MANUFACTURER_HONEYWELL = 3; ///< Honeywell.
717static constexpr uint8_t MANUFACTURER_HORMANN = 4; ///< Hörmann (garage doors, gates).
718static constexpr uint8_t MANUFACTURER_ASSA_ABLOY = 5; ///< ASSA ABLOY (locks, access).
719static constexpr uint8_t MANUFACTURER_NIKO = 6; ///< Niko (switches, home automation).
720static constexpr uint8_t MANUFACTURER_WINDOW_MASTER = 7; ///< WINDOW MASTER (ventilation).
721static constexpr uint8_t MANUFACTURER_RENSON = 8; ///< Renson (ventilation, sun protection).
722static constexpr uint8_t MANUFACTURER_CIAT = 9; ///< CIAT (HVAC).
723static constexpr uint8_t MANUFACTURER_SECUYOU = 10; ///< Secuyou (security).
724static constexpr uint8_t MANUFACTURER_OVERKIZ = 11; ///< OVERKIZ (Somfy connectivity platform).
725static constexpr uint8_t MANUFACTURER_ATLANTIC_GROUP = 12; ///< Atlantic Group (heating, hot water).
726/// @}
727
728/// @brief Get a human-readable manufacturer name from the protocol manufacturer byte.
729///
730/// The manufacturer ID is a 1-based index assigned by the IO-Homecontrol alliance.
731/// IDs outside the known range return "unknown". When an unknown ID appears at runtime,
732/// the pairing flow logs a warning suggesting the user file a GitHub issue.
733/// @warning **Display-only — do not use this for YAML.** Four of the twelve names do not
734/// round-trip through `.strip().lower()` to their `manufacturer:` YAML token
735/// (`MANUFACTURER_OPTIONS`, `hub_validators.py`): `"Hörmann"` has an umlaut the YAML token
736/// (`hormann`) drops, and `"ASSA ABLOY"`/`"WINDOW MASTER"`/`"Atlantic Group"` use a space
737/// where the YAML token uses `_`. There is currently no YAML-token accessor for
738/// manufacturers — see `yaml_device_type_name()` (proto_device_model.h) for the pattern this
739/// would follow if one is ever added.
740/// @param id Manufacturer ID byte (1–12 for known manufacturers).
741/// @return Null-terminated lowercase string such as "unknown", or mixed-case name like "Somfy".
742const char *manufacturer_name(uint8_t id);
743
744// ============================================================================
745// Command Originator Codes
746// ============================================================================
747
748/// @brief Command originator codes indicating what or who triggered a command.
749///
750/// The originator byte is the first byte of the CMD_EXECUTE payload. It tells
751/// the actuator (and any eavesdropping controller) who initiated the movement.
752/// This is useful for understanding device-initiated status updates.
753///
754/// ORIGINATOR_WIND_SENSOR and ORIGINATOR_RAIN_SENSOR are both field-confirmed on real hardware —
755/// a single combined wind/rain protection station (issue #27, community capture) broadcasting
756/// both values minutes apart:
757/// tests/corpus/captures/oneway/wind_sensor_oneway_favorite_wind_originator_sx1276.yaml and
758/// tests/corpus/captures/oneway/wind_sensor_oneway_favorite_rain_originator_sx1276.yaml. The
759/// latter is this project's first real-hardware confirmation of ORIGINATOR_RAIN_SENSOR at all —
760/// every prior wind/rain-station capture used ORIGINATOR_WIND_SENSOR only.
761/// @{
762static constexpr uint8_t ORIGINATOR_LOCAL_USER = 0x00; ///< User pressed a button on the actuator.
763static constexpr uint8_t ORIGINATOR_USER_REMOTE = 0x01; ///< User sent command from a remote control.
764static constexpr uint8_t ORIGINATOR_RAIN_SENSOR = 0x02; ///< Rain sensor triggered the movement.
765static constexpr uint8_t ORIGINATOR_TIMER = 0x03; ///< Timer or schedule triggered the movement.
766static constexpr uint8_t ORIGINATOR_SECURITY = 0x04; ///< Security controlling device (SCD) action.
767static constexpr uint8_t ORIGINATOR_UPS = 0x05; ///< Uninterruptible power supply action.
768static constexpr uint8_t ORIGINATOR_SMART_CONTROLLER = 0x06; ///< Smart function controller.
769static constexpr uint8_t ORIGINATOR_LIFESTYLE = 0x07; ///< Lifestyle scenario controller.
770static constexpr uint8_t ORIGINATOR_SAAC = 0x08; ///< Stand-alone automatic controller (SAAC).
771static constexpr uint8_t ORIGINATOR_WIND_SENSOR = 0x09; ///< Wind sensor triggered the movement.
772static constexpr uint8_t ORIGINATOR_LOAD_SHEDDING = 0x0B; ///< Load-shedding manager.
773static constexpr uint8_t ORIGINATOR_LOCAL_LIGHT = 0x0C; ///< Local light sensor.
774static constexpr uint8_t ORIGINATOR_ENVIRONMENT = 0x0D; ///< Unspecified environment sensor.
775static constexpr uint8_t ORIGINATOR_MYSELF = 0x10; ///< Actuator decided to move by itself.
776static constexpr uint8_t ORIGINATOR_AUTOMATIC_CYCLE = 0xFE; ///< Automatic cycle / external access.
777static constexpr uint8_t ORIGINATOR_EMERGENCY = 0xFF; ///< Emergency command (never disabled).
778/// @}
779
780/// @brief Get a human-readable name for a command originator byte.
781///
782/// @param originator Originator code from the first data byte of CMD_EXECUTE.
783/// @return Null-terminated string such as "rain_sensor" or "user_remote".
784const char *originator_name(uint8_t originator);
785
786// ============================================================================
787// ACEI (Application Command Execution Interface)
788// ============================================================================
789
790/// @brief ACEI byte bit-field definitions.
791///
792/// The ACEI byte is the second byte of the CMD_EXECUTE payload. It encodes the
793/// priority level and service class of the command, controlling which commands
794/// can override others. Devices reject commands with lower priority than their
795/// current locked level (resulting in RESULT_PRIORITY_LEVEL_LOCKED).
796/// @{
797static constexpr uint8_t ACEI_VALID_BIT = 0x01; ///< Bit 0: command validity flag.
798static constexpr uint8_t ACEI_EXTENDED_MASK = 0x06; ///< Bits [2:1]: extended field.
799static constexpr uint8_t ACEI_EXTENDED_SHIFT = 1; ///< Shift for extended field extraction.
800static constexpr uint8_t ACEI_SERVICE_MASK = 0x18; ///< Bits [4:3]: service type.
801static constexpr uint8_t ACEI_SERVICE_SHIFT = 3; ///< Shift for service field extraction.
802static constexpr uint8_t ACEI_LEVEL_MASK = 0xE0; ///< Bits [7:5]: priority level (0–7).
803static constexpr uint8_t ACEI_LEVEL_SHIFT = 5; ///< Shift for priority level extraction.
804/// @}
805
806/// @brief ACEI priority level values (0–7).
807///
808/// These values are extracted from the ACEI byte via (acei & ACEI_LEVEL_MASK) >> ACEI_LEVEL_SHIFT.
809/// @{
810static constexpr uint8_t ACEI_LEVEL_PROTECTION_HUMAN = 0; ///< Personal safety (highest, overrides all).
811static constexpr uint8_t ACEI_LEVEL_PROTECTION_SENSOR = 1; ///< Goods/environment protection via sensors.
812static constexpr uint8_t ACEI_LEVEL_USER_HIGH = 2; ///< High-priority user controller.
813static constexpr uint8_t ACEI_LEVEL_USER_DEFAULT = 3; ///< Default remote controller priority.
814static constexpr uint8_t ACEI_LEVEL_COMFORT_1 = 4; ///< Comfort automation level 1.
815static constexpr uint8_t ACEI_LEVEL_COMFORT_2 = 5; ///< Comfort automation level 2.
816static constexpr uint8_t ACEI_LEVEL_AUTO_SAAC = 6; ///< Stand-alone automatic controller.
817static constexpr uint8_t ACEI_LEVEL_AUTO_DEFAULT = 7; ///< Default automatic level (lowest).
818/// @}
819
820/// @brief Get a human-readable name for an ACEI priority level (0–7).
821///
822/// Priority levels form a hierarchy: level 0 (human protection) is highest
823/// and overrides all others. Level 3 is the default for remote controllers.
824/// @param level Priority level value (0–7).
825/// @return Null-terminated string such as "user_default" or "protection_sensor".
826const char *acei_level_name(uint8_t level);
827
828/// @brief ACEI byte for a 1W CMD_EXECUTE frame — the Somfy-shaped default.
829///
830/// Level 2 (user_high), extended-info bit set. The 2W EXECUTE_ACEI (proto_commands.cpp) pins
831/// level 3 to a real captured 2W hub; this level-2 value is what the published 1W reference
832/// vector (tests/corpus/captures/oneway/reference_1w_oneway_execute_iv_vector.yaml) and every
833/// Somfy 1W remote frame in the corpus (7+ frames across several nodes) carry.
834/// Lives here (not proto_commands.cpp) so oneway_controller.h's resolve_oneway_wire_profile() can
835/// name it without the protocol layer depending on the controller layer.
836/// Composition: (ACEI_LEVEL_USER_HIGH << 5) | (1 << 1) | 1 = 0x43.
837static constexpr uint8_t ONEWAY_EXECUTE_ACEI =
839
840/// @brief ACEI byte for a 1W CMD_EXECUTE frame from a VELUX KLI-class remote.
841///
842/// Level 3 (user_default), extended-info bits clear. **Confidence: n=1** — one corpus frame
843/// (tests/corpus/captures/oneway/velux_kli313_oneway_stop.yaml).
844/// An unidentified remote's frames also carry 0x61
845/// (tests/corpus/captures/oneway/unidentified_1w_remote_oneway_execute_open_and_stop_acei_61_sx1276.yaml),
846/// but its manufacturer is unknown, so they can't corroborate the VELUX attribution.
847/// `execute_acei:` on the identity is the escape hatch.
848/// Composition: (ACEI_LEVEL_USER_DEFAULT << 5) | ACEI_VALID_BIT = 0x61.
850
851// ============================================================================
852// Discovery Response Extended Fields
853// ============================================================================
854
855/// @brief Byte offsets within CMD_DISCOVER_RESP (0x29) payload data.
856///
857/// The full discovery response carries up to 9 bytes of device metadata:
858/// bytes 0–1 hold the packed device type/subtype (already parsed by
859/// decode_packed_device_type()), and bytes 2–8 hold additional fields.
860/// @{
861static constexpr uint8_t DISCOVERY_RESP_BACKBONE_OFFSET = 2; ///< Backbone address starts at data[2] (3 bytes);
862 ///< cross-confirmed by CMD_NODE_VERIFY_RESP (0x37),
863 ///< which returns the same 3 bytes for the same
864 ///< device (see that constant's comment).
865static constexpr uint8_t DISCOVERY_RESP_MANUFACTURER_OFFSET = 5; ///< Manufacturer ID at data[5].
866static constexpr uint8_t DISCOVERY_RESP_FLAGS_OFFSET = 6; ///< Flags byte at data[6].
867static constexpr uint8_t DISCOVERY_RESP_TIMESTAMP_OFFSET = 7; ///< Timestamp starts at data[7] (2 bytes).
868static constexpr uint8_t DISCOVERY_RESP_FULL_SIZE = 9; ///< Full discovery response payload size.
869/// @}
870
871// === Multi Information Byte (Discovery Response data[6]) ===
872// The flags byte in the discovery response encodes device capabilities and timing
873// characteristics that help a controller tune its interaction with the actuator.
874
875/// @brief Bit masks and shifts for the Multi Information Byte fields.
876/// @{
877static constexpr uint8_t DISCOVERY_FLAGS_ATT_MASK = 0xC0; ///< Bits [7:6]: actuator turnaround time class.
878static constexpr uint8_t DISCOVERY_FLAGS_ATT_SHIFT = 6; ///< Shift for ATT field extraction.
879static constexpr uint8_t DISCOVERY_FLAGS_SYNC_CTRL_GRP = 0x20; ///< Bit 5: supports sync control group.
880static constexpr uint8_t DISCOVERY_FLAGS_RF_SUPPORT =
881 0x08; ///< Bit 3: 1 = the node has its own RF, 0 = it sits on a wired backbone only (VELUX KLF 200 API
882 ///< v3.18 §7.4.1.2.4-7, Table 52).
883static constexpr uint8_t DISCOVERY_FLAGS_IO_MEMBERSHIP =
884 0x04; ///< Bit 2: io-homecontrol membership; always 1 (same table).
885static constexpr uint8_t DISCOVERY_FLAGS_POWER_SAVE_MASK = 0x03; ///< Bits [1:0]: power save mode.
886/// @}
887
888/// @brief Actuator Turnaround Time (ATT) class values.
889///
890/// The time within which each node must respond after receiving a command (VELUX KLF 200 API
891/// v3.18 §7.4.1.2.4-7, Table 51: "Actuator Turnaround time"). The unit is milliseconds.
892/// Extracted from the Multi Information Byte via
893/// `(flags & DISCOVERY_FLAGS_ATT_MASK) >> DISCOVERY_FLAGS_ATT_SHIFT`.
894/// @{
895static constexpr uint8_t ATT_CLASS_5MS = 0; ///< Response within 5 ms.
896static constexpr uint8_t ATT_CLASS_10MS = 1; ///< Response within 10 ms.
897static constexpr uint8_t ATT_CLASS_20MS = 2; ///< Response within 20 ms.
898static constexpr uint8_t ATT_CLASS_40MS = 3; ///< Response within 40 ms.
899/// @}
900
901/// Turnaround time of ATT_CLASS_5MS, the shortest class; att_class_ms() doubles it per class.
902static constexpr uint32_t ATT_CLASS_5MS_TURNAROUND_MS = 5;
903
904/// @brief Power save mode values from the Multi Information Byte.
905///
906/// Extracted from `flags & DISCOVERY_FLAGS_POWER_SAVE_MASK`.
907/// Devices in low-power mode require long preamble (1024 bytes) to wake their receiver.
908/// @{
909static constexpr uint8_t POWER_SAVE_ALWAYS_ALIVE = 0; ///< Device is always listening — short preamble works.
910static constexpr uint8_t POWER_SAVE_LOW_POWER = 1; ///< Device sleeps — needs long preamble to wake.
911/// @}
912
913/// Default Multi Information Byte for our device-role CMD_DISCOVER_RESP (0x29): the turnaround-time
914/// class and power-save mode the key-extraction responder advertises to a foreign hub. The
915/// `key_extraction_multi_info` tuning option replaces it.
916///
917/// 0xDD = 1101_1101: bits [7:6] ATT_CLASS_40MS, bit [3] DISCOVERY_FLAGS_RF_SUPPORT, bit [2]
918/// DISCOVERY_FLAGS_IO_MEMBERSHIP (always 1), bit [4] with no named constant (not in the VELUX KLF 200
919/// API table), power-save bits [1:0] POWER_SAVE_LOW_POWER. All but the power-save bits copy the byte
920/// a real Somfy Izymo dimmer sends, 0xDC
921/// (tests/corpus/captures/pairing/somfy_izymo_dimmer_pairing_full_sx1276.yaml): copying a real
922/// device's whole byte is safer than guessing at bits with no documented meaning.
923///
924/// ATT_CLASS_40MS is the honest class for this responder: it scans channels on the ESPHome loop
925/// cadence and is deaf while it transmits, so it cannot promise a 5 ms turnaround. Low power for the
926/// same reason: a controller wakes a low-power device with a long preamble, which the scanning
927/// responder catches reliably. A VELUX KLR 200, a KLF 200 and a Somfy TaHoma have handed over their
928/// key to this byte.
929///
930/// A controller that reads the power-save bit may skip a sleeping device: a Freebox Delta confirmed
931/// only answers with the Izymo's always-alive byte 0xDC (issue #215). Such a hub needs the tuning
932/// option set to 0xDC.
933static constexpr uint8_t KEY_EXTRACTION_DISCOVER_RESP_FLAGS = 0xDD;
934
935/// Default packed device type word (type in bits [15:6], subtype in bits [5:0]) the key-extraction
936/// responder advertises in its 0x29 to an open discovery: 0x0080, roller shutter subtype 0, the same
937/// word a real Somfy RS100 io sends
938/// (tests/corpus/captures/pairing/somfy_rs100_pairing_key_transfer_timeout.yaml). The
939/// `key_extraction_type_word` tuning option replaces it; a typed discovery is always answered with
940/// the type it asks for.
941static constexpr uint16_t KEY_EXTRACTION_DISCOVER_RESP_TYPE_WORD = 0x0080;
942
943/// @brief Extract the ATT class field from a discovery response's Multi Information Byte.
944/// @param flags Multi Information Byte (data[DISCOVERY_RESP_FLAGS_OFFSET]).
945/// @return ATT class value (0–3); pass to att_class_name() for a human-readable string.
946constexpr uint8_t discovery_att_class(uint8_t flags) {
948}
949
950/// @brief Turnaround time of an ATT class in milliseconds: 5, 10, 20 or 40 (each class doubles it).
951/// @param att_class ATT class (0–3), e.g. from discovery_att_class().
952/// @return The class's turnaround time in milliseconds.
953constexpr uint32_t att_class_ms(uint8_t att_class) {
954 return static_cast<uint32_t>(ATT_CLASS_5MS_TURNAROUND_MS) << att_class;
955}
956
957/// @brief Extract the power save mode field from a discovery response's Multi Information Byte.
958/// @param flags Multi Information Byte (data[DISCOVERY_RESP_FLAGS_OFFSET]).
959/// @return Power save mode value (0–1); pass to power_save_mode_name() for a human-readable string.
960inline uint8_t discovery_power_save_mode(uint8_t flags) { return flags & DISCOVERY_FLAGS_POWER_SAVE_MASK; }
961
962/// @brief Get a human-readable turnaround time string for an ATT class value.
963/// @param att_class ATT class (0–3) extracted from the Multi Information Byte.
964/// @return Null-terminated string such as "5ms", "10ms", "20ms", or "40ms".
965const char *att_class_name(uint8_t att_class);
966
967/// @brief Get a human-readable power save mode name.
968/// @param mode Power save value (0–1) extracted from the Multi Information Byte.
969/// @return Null-terminated string such as "always_alive" or "low_power".
970const char *power_save_mode_name(uint8_t mode);
971
972// ============================================================================
973// Command Result Codes
974// ============================================================================
975
976/// @brief Result code (bits [7:3]) of a CMD_ERROR_RESP byte.
977/// @param result Result byte from CMD_ERROR_RESP data[0].
978/// @return Code 0..31; codes 1..ERROR_RESP_CODE_MAX are named (see the RESULT_* block).
979constexpr uint8_t error_resp_code(uint8_t result) { return result >> ERROR_RESP_CODE_SHIFT; }
980
981/// @brief Execution state (bits [2:0]) of a CMD_ERROR_RESP byte.
982/// @param result Result byte from CMD_ERROR_RESP data[0].
983/// @return State 0..7; 0 means the command was not executed.
984constexpr uint8_t error_resp_state(uint8_t result) { return result & ERROR_RESP_STATE_MASK; }
985
986/// @brief Return a stable symbolic name for a CMD_ERROR_RESP result code.
987///
988/// A byte with state 0 and a named code reads as a packed result code (RESULT_COMMAND_UNKNOWN ..
989/// RESULT_ALREADY_CONFIGURED); every other byte is looked up in the run-status / limitation table.
990/// @param result Result byte from CMD_ERROR_RESP data[0].
991/// @return Uppercase symbolic name, or "UNKNOWN_RESULT_CODE" when unmapped.
992const char *command_result_name(uint8_t result);
993/// @brief Return a human-readable explanation for a CMD_ERROR_RESP result code.
994/// @param result Result byte from CMD_ERROR_RESP data[0].
995/// @return Short description suitable for warn-level logs.
996const char *command_result_description(uint8_t result);
997/// @brief Check whether a result code describes one exchange rather than the device's state.
998///
999/// SESSION_INTERRUPTED is the one such code: a garage drive answered three status reads with it
1000/// right after a command it did not confirm, and the following exchanges worked normally (issue
1001/// #163). It is logged and returned to an action like any other result, but a lasting view of the
1002/// device (the Active Issue sensor) treats it as no result.
1003/// @param result Result byte from CMD_ERROR_RESP data[0].
1004/// @return true for a result that says nothing about the device once the exchange is over.
1005constexpr bool command_result_is_transient(uint8_t result) { return result == RESULT_SESSION_INTERRUPTED; }
1006/// @brief Check whether a result code represents an environmental or control limitation.
1007/// @param result Result byte from CMD_ERROR_RESP data[0].
1008/// @return true when the response reports a limitation rather than a generic execution error.
1009bool is_limitation_result(uint8_t result);
1010
1011} // namespace home_io_control
1012} // 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 TRANSFER_BLOCK_SIZE
Data bytes per CMD_UNKNOWN4A_RESP; 18 fills a 32-byte frame.
static constexpr uint8_t RESULT_CALIBRATING
Node is calibrating.
static constexpr uint8_t ACEI_LEVEL_COMFORT_1
Comfort automation level 1.
static constexpr uint16_t TRANSFER_OBJECT_ACTUATOR_STATE
The actuator state record read beside the capacity record.
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 EVENT_SUBSCRIPTION_SUBSCRIBE
Ask the device to report the event.
static constexpr uint8_t STATUS_MP_FP_FPI1_MASK
FPI1: FP1..FP7 requested (bit 7 = FP1 ... bit 1 = FP7).
static constexpr uint8_t TRANSFER_OPEN_DATA_SIZE
CMD_TRANSFER_OPEN payload bytes.
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.
constexpr 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 CMD_GET_NAME_RESP
Device name response.
static constexpr uint16_t EVENT_LOCAL_SENSOR
A device reports a sensor built into it (sun, window, ...).
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 uint32_t ATT_CLASS_5MS_TURNAROUND_MS
Turnaround time of ATT_CLASS_5MS, the shortest class; att_class_ms() doubles it per class.
static constexpr uint8_t MANUFACTURER_VELUX
IO-Homecontrol manufacturer ID constants.
static constexpr uint8_t LOCAL_SENSOR_TYPE_SUN
First EVENT_LOCAL_SENSOR data byte on every capture.
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 status update, sent by the device on its own (authenticated, acknowledged with CMD_STATUS_UPDA...
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 uint16_t EVENT_STATUS_CHANGE
The event a position-bearing CMD_STATUS_UPDATE carries (every captured one, e.g.
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 CMD_TRANSFER_OPEN, counting down from ceil(leng...
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 TRANSFER_OBJECT_CLASS_SOMFY
Object class of the Somfy manufacturer objects in TRANSFER_READABLE_OBJECTS.
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 SUBSCRIBE_EVENT_DATA_SIZE
CMD_SUBSCRIBE_EVENT payload size: the action byte and the big-endian event id.
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 POS_PEDESTRIAN_MODIFIER
Modifier byte for the pedestrian-position command (POS_FAVORITE main byte, as for ventilation).
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 uint16_t TRANSFER_OBJECT_GENERAL_INFO1
Two further class-0 records beside the name record.
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 TRANSFER_STATUS_ADAPTED
CMD_TRANSFER_OPEN_RESP status: accepted with changes.
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 uint16_t KEY_EXTRACTION_DISCOVER_RESP_TYPE_WORD
Default packed device type word (type in bits [15:6], subtype in bits [5:0]) the key-extraction respo...
static constexpr uint8_t CMD_TRANSFER_OPEN_RESP
Answer to CMD_TRANSFER_OPEN: <handle> <status> <length u16 BE>.
static constexpr uint8_t TRANSFER_STATUS_ACCEPTED
CMD_TRANSFER_OPEN_RESP status: request accepted.
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 uint16_t TRANSFER_OBJECT_GENERAL_INFO2
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 TRANSFER_OBJECT_CLASS_DEVICE
Object class of the TRANSFER_OBJECT_* ids.
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 EVENT_SUBSCRIPTION_UNSUBSCRIBE
Ask the device to stop reporting the event.
static constexpr TransferObject TRANSFER_READABLE_OBJECTS[]
Every object the read_object probe can open, in probe-index order (ADR 0062).
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 LOCAL_SENSOR_VALUE_OFFSET
Offset of the big-endian value inside the event data.
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 BATTERY_LEVEL_EVENT_DATA_LEN
EVENT_BATTERY_LEVEL_CHANGE data: one level byte.
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...
constexpr uint32_t att_class_ms(uint8_t att_class)
Turnaround time of an ATT class in milliseconds: 5, 10, 20 or 40 (each class doubles it).
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 uint16_t MAIN_PARAMETER_KEEP
16-bit main parameter D400 (POS_UNKNOWN in the high byte): keep the current value.
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.
constexpr uint8_t fp_bitmap_bit(uint8_t fp)
Bit of functional parameter fp (1-16) in its bitmap: FPI1 holds FP1-FP8 and FPI2 FP9-FP16,...
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 TRANSFER_OPEN_RESP_DATA_SIZE
CMD_TRANSFER_OPEN_RESP payload bytes.
constexpr bool command_result_is_transient(uint8_t result)
Check whether a result code describes one exchange rather than the device's state.
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; meaning undecoded, never captured.
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 BATTERY_LEVEL_MAX_PERCENT
Highest level read as a percentage.
static constexpr uint8_t ACEI_LEVEL_SHIFT
Shift for priority level extraction.
static constexpr uint8_t CMD_TRANSFER_OPEN
Opens a read of one object: nine payload bytes <originator> <class> 00 <id hi> <id lo> < 00 00 00 00.
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 (bit 7 = FP9 ... bit 0 = FP16 otherwise).
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 uint16_t TRANSFER_OBJECT_ACTUATOR_CAPACITY
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 POS_PARTIAL_MODIFIER
Modifier byte for the partial-position command (POS_FAVORITE main byte, as for ventilation).
static constexpr uint8_t CMD_READ_GROUPS
Named "Actuator: Read Groups" / "ActuatorAnyConfigIsLocal" (uncertain) / "Service ACK" — three...
static constexpr uint16_t TRANSFER_OBJECT_NAME
A 16-byte name record: on a Somfy Izymo dimmer 00 followed by the ASCII text "MODULE DIMMER" and zero...
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.
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 TRANSFER_READABLE_OBJECT_COUNT
Number of entries in TRANSFER_READABLE_OBJECTS.
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 uint16_t EVENT_BATTERY_LEVEL_CHANGE
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 TRANSFER_READ_REQ_DATA_SIZE
CMD_UNKNOWN4A_REQ payload bytes: handle + chunk.
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 CMD_SUBSCRIBE_EVENT
Subscribe a device to one event: payload <action> <event hi> <event lo>, action EVENT_SUBSCRIPTION_SU...
static constexpr uint8_t STATUS_MP_FP_PARAM_COUNT
Largest selection seen on air (FP1-FP7), kept as a conservative cap across both bitmaps.
static constexpr uint8_t LOCAL_SENSOR_EVENT_DATA_LEN
EVENT_LOCAL_SENSOR data: 06 xx <hi> <lo> yy.
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 KEY_EXTRACTION_DISCOVER_RESP_FLAGS
Default Multi Information Byte for our device-role CMD_DISCOVER_RESP (0x29): the turnaround-time clas...
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 uint16_t TRANSFER_CHUNK_CLOSE
Chunk number of the closing CMD_UNKNOWN4A_REQ.
static constexpr uint8_t TRANSFER_STATUS_NOTHING_TO_READ
CMD_TRANSFER_OPEN_RESP status 7 with length 0: nothing to send for this object.
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 (none in the close echo).
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.
One object a CMD_TRANSFER_OPEN can name: its class byte and its 16-bit id.
uint8_t object_class
TRANSFER_OBJECT_CLASS_DEVICE or TRANSFER_OBJECT_CLASS_SOMFY.