Home IO Control
ESPHome add-on for IO-Homecontrol devices
Loading...
Searching...
No Matches
proto_frame.h
Go to the documentation of this file.
1#pragma once
2
3/// @file proto_frame.h
4/// @brief IO-Homecontrol 2W protocol definitions.
5/// @ingroup hioc_protocol
6///
7/// IO-Homecontrol is a proprietary wireless protocol used by Somfy, Velux, and other
8/// manufacturers for controlling shutters, awnings, blinds, and similar devices.
9/// "2W" means two-way: the controller sends commands and receives status feedback.
10///
11/// The protocol uses FSK modulation at 868 MHz with frequency hopping across 3 channels.
12/// Communication is encrypted with AES-128 and authenticated with a 6-byte HMAC.
13/// Each installation has a unique 16-byte "system key" shared between controller and devices.
14
15#include <cstdint>
16#include <cstring>
17#include <string>
18
19namespace esphome {
20namespace home_io_control {
21
22// ============================================================================
23// Physical Layer — Radio Parameters
24// ============================================================================
25
26/// The protocol uses 3 frequency channels in the 868 MHz ISM band.
27/// IO-Homecontrol uses 3 channels in the 868 MHz SRD band. In 1W (one-way) mode,
28/// only CH2 is used. In 2W (two-way) mode, the controller hops across all three
29/// channels every ~2.7ms when idle. Commands are sent on CH2; responses may arrive
30/// on any channel within the exchange wait window.
31static constexpr uint32_t FREQ_CH1 = 868250000; ///< Channel 1: 868.25 MHz (2W only)
32static constexpr uint32_t FREQ_CH2 = 868950000; ///< Channel 2: 868.95 MHz (1W and 2W, TX channel)
33static constexpr uint32_t FREQ_CH3 = 869850000; ///< Channel 3: 869.85 MHz (2W only)
34
35/// Preamble is a sequence of 0xAA bytes that precedes every frame.
36/// The first frame in an exchange uses a long preamble (1024 bytes = 8192 bits)
37/// so the receiver has time to detect it while hopping. Subsequent frames in the
38/// same exchange use a short preamble (8 bytes) since both sides are already
39/// on the same channel. Solar-powered devices need the long preamble to wake up.
40static constexpr uint16_t LONG_PREAMBLE = 1024; ///< 1024 bytes for initial/start frames
41static constexpr uint16_t SHORT_PREAMBLE = 8; ///< 8 bytes for response/continuation frames
42/// SX1262-specific preamble for the outbound 0x3D challenge response.
43///
44/// The SX1262 path has to rebuild the IO-homecontrol framing details in software and the
45/// 0x3D auth response is sent immediately after we switch from RX to TX upon receiving the
46/// device's 0x3C challenge. Real-device tuning showed that 64 preamble bytes gives the peer
47/// enough lock-on margin in that tight turn-around window, while leaving the proven SX1276
48/// short-response waveform untouched.
49static constexpr uint16_t SX1262_AUTH_RESPONSE_PREAMBLE = 64;
50
51/// SX1262-specific per-channel dwell while waiting for authenticated exchange responses.
52///
53/// A 50 ms dwell was short enough that the controller could hop away from the request channel
54/// just before the device emitted its post-auth reply. Using 90 ms keeps the SX1262 receiver on
55/// that channel long enough for the observed device turn-around after 0x3D, without inflating the
56/// overall 300/500 ms exchange windows for the rest of the protocol.
57static constexpr int32_t SX1262_EXCHANGE_RESPONSE_WAIT_SLICE_MS = 90;
58
59/// Timing constants for frequency hopping and response waiting.
60static constexpr int32_t HOP_TIME_US = 2700; ///< Time per channel when hopping (2.7ms)
61static constexpr int32_t RESPONSE_CHANNEL_WAIT_MS = 50; ///< Per-channel dwell while waiting for an exchange response
62static constexpr int32_t RESPONSE_WAIT_MS = 500; ///< Wait for response to non-start frame
63static constexpr int32_t RESPONSE_START_WAIT_MS = 300; ///< Wait for response to start frame (longer)
64static constexpr int32_t RESPONSE_AUTH_WAIT_MS =
65 RESPONSE_WAIT_MS; ///< Wait for final response after challenge response
66static constexpr int32_t EXCHANGE_RETRY_DELAY_MS = 250; ///< Gap between retries within one HA command
67static constexpr uint8_t EXCHANGE_RETRY_COUNT = 3; ///< Attempts per command before reporting failure
68
69/// Listen-before-talk (LBT) parameters for ETSI EN 300 220 compliance.
70/// Before transmitting, the radio checks that the channel RSSI is below the
71/// threshold. If the channel is busy, TX is deferred by LBT_RETRY_DELAY_MS
72/// up to LBT_MAX_RETRIES times.
73static constexpr int16_t LBT_RSSI_THRESHOLD_DBM = -90; ///< Channel-free threshold (ETSI: ≤ -90 dBm)
74static constexpr uint8_t LBT_MAX_RETRIES = 5; ///< Max carrier-sense attempts before TX anyway
75static constexpr uint8_t LBT_RETRY_DELAY_MS = 5; ///< Backoff between LBT checks (≥ 5ms per ETSI)
76
77// ============================================================================
78// Frame Constants
79// ============================================================================
80
81static constexpr uint8_t NODE_ID_SIZE = 3; ///< Device/node addresses are 3 bytes (e.g., "123ABC")
82static constexpr uint8_t NODE_ID_STRING_SIZE = NODE_ID_SIZE * 2 + 1; ///< Uppercase hex node ID plus null terminator
83static constexpr uint8_t HMAC_SIZE = 6; ///< Authentication HMAC is 6 bytes (truncated AES output)
84static constexpr uint8_t AES_KEY_SIZE = 16; ///< AES-128 key size
85static constexpr uint8_t AES_BLOCK_SIZE = 16; ///< AES block size
86static constexpr uint8_t IV_SIZE = 16; ///< Initialization vector size for AES
87static constexpr uint8_t IV_PADDING = 0x55; ///< Padding byte used in IV construction
88static constexpr uint8_t BITS_PER_BYTE = 8; ///< Number of bits in one protocol byte
89
90static constexpr uint8_t FRAME_MIN_SIZE = 9; ///< Minimum frame: CTRL0+CTRL1+DST(3)+SRC(3)+CMD(1)
91static constexpr uint8_t FRAME_MAX_SIZE = 32; ///< Maximum frame size (9 header + 23 data)
92static constexpr uint8_t FRAME_MAX_DATA_SIZE = 23; ///< Maximum data bytes after command ID
93
94/// Control byte 0 (CTRL0) bit definitions.
95/// CTRL0 encodes frame flags and the total frame length.
96/// Bits [4:0] = frame_length - 1 (so 0x08 means 9 bytes total).
97/// - START (bit 6): first frame in an exchange; uses long preamble (1024 bytes).
98/// - END (bit 7): last frame in an exchange; set on responses and command completions.
99/// - 1W (bit 5): 1=OneWay protocol (no response expected), 0=TwoWay (response expected).
100/// For 2W operation, the controller sets START on initial command and device replies with END; subsequent frames in an
101/// authenticated exchange also carry END.
102static constexpr uint8_t CTRL0_END = 0x80; ///< Bit 7: last frame in exchange
103static constexpr uint8_t CTRL0_START = 0x40; ///< Bit 6: first frame in exchange (uses long preamble)
104static constexpr uint8_t CTRL0_PROTOCOL_1W = 0x20; ///< Bit 5: 1=OneWay protocol, 0=TwoWay protocol
105static constexpr uint8_t CTRL0_LENGTH_MASK = 0x1F; ///< Bits [4:0]: frame length - 1
106
107/// Control byte 1 (CTRL1) bit definitions.
108/// - LOW_POWER (bit 5): device is battery/solar powered; may sleep and requires long preamble to wake.
109static constexpr uint8_t CTRL1_LOW_POWER = 0x20; ///< Bit 5: low-power device (e.g., solar-powered)
110
111// ============================================================================
112// Command IDs
113// ============================================================================
114
115// Normal operation commands
116static constexpr uint8_t CMD_EXECUTE = 0x00; ///< Set position/open/close/stop — requires authentication
117static constexpr uint8_t CMD_PRIVATE = 0x03; ///< Get device status — no authentication needed
118static constexpr uint8_t CMD_PRIVATE_RESP = 0x04; ///< Response to 0x00 and 0x03 (contains position data)
119
120// Discovery and pairing commands
121static constexpr uint8_t CMD_DISCOVER_REQ = 0x28; ///< Broadcast discovery request
122static constexpr uint8_t CMD_DISCOVER_RESP = 0x29; ///< Device responds with its ID and type
123static constexpr uint8_t CMD_DISCOVER_SPE_REQ = 0x2A; ///< Discover sub-devices (e.g., light on garage door)
124static constexpr uint8_t CMD_DISCOVER_SPE_RESP = 0x2B; ///< Sub-device response
125static constexpr uint8_t CMD_DISCOVER_CONFIRM = 0x2C; ///< Confirm discovery to device
126static constexpr uint8_t CMD_DISCOVER_CONFIRM_ACK = 0x2D; ///< Device acknowledges confirmation
127
128// Key exchange commands (used during pairing)
129static constexpr uint8_t CMD_KEY_INIT = 0x31; ///< Initiate key transfer to device
130static constexpr uint8_t CMD_KEY_TRANSFER = 0x32; ///< Send encrypted system key to device
131static constexpr uint8_t CMD_KEY_CONFIRM = 0x33; ///< Device confirms key was received
132
133// Authentication commands (challenge-response for secured commands)
134static constexpr uint8_t CMD_CHALLENGE_REQ = 0x3C; ///< Device sends 6-byte random challenge
135static constexpr uint8_t CMD_CHALLENGE_RESP = 0x3D; ///< Controller responds with HMAC proof
136
137// Device info commands
138static constexpr uint8_t CMD_GET_NAME = 0x50; ///< Request device name
139static constexpr uint8_t CMD_GET_NAME_RESP = 0x51; ///< Device name response
140static constexpr uint8_t CMD_SET_NAME = 0x52; ///< Set device name (authenticated)
141static constexpr uint8_t CMD_SET_NAME_RESP = 0x53; ///< Device-name write response
142static constexpr uint8_t CMD_GET_INFO2 = 0x56; ///< Request device type/model info
143static constexpr uint8_t CMD_GET_INFO2_RESP = 0x57; ///< Device type/model response
144
145// Configuration and status update commands
146static constexpr uint8_t CMD_SET_CONFIG1 = 0x6F; ///< Configure device to auto-send status updates
147static constexpr uint8_t CMD_SET_CONFIG1_RESP = 0x70; ///< Config response
148static constexpr uint8_t CMD_STATUS_UPDATE = 0x71; ///< Device-initiated status update (needs auth)
149static constexpr uint8_t CMD_STATUS_UPDATE_RESP = 0x72; ///< Acknowledge status update
150static constexpr uint8_t CMD_ERROR_RESP = 0xFE; ///< Error response to any command
151
152// Command-result / limitation codes carried in CMD_ERROR_RESP DATA[0].
153static constexpr uint8_t RESULT_UNKNOWN_STATUS_REPLY = 0x00; ///< Device returned an unknown status reply.
154static constexpr uint8_t RESULT_COMMAND_COMPLETED_OK = 0x01; ///< No errors detected.
155static constexpr uint8_t RESULT_NO_CONTACT = 0x02; ///< No communication to node.
156static constexpr uint8_t RESULT_MANUALLY_OPERATED = 0x03; ///< Manually operated by a user.
157static constexpr uint8_t RESULT_BLOCKED = 0x04; ///< Node blocked by an object.
158static constexpr uint8_t RESULT_WRONG_SYSTEMKEY = 0x05; ///< Node contains the wrong system key.
159static constexpr uint8_t RESULT_PRIORITY_LEVEL_LOCKED = 0x06; ///< Node is locked on this priority level.
160static constexpr uint8_t RESULT_REACHED_WRONG_POSITION = 0x07; ///< Node stopped in another position than expected.
161static constexpr uint8_t RESULT_ERROR_DURING_EXECUTION = 0x08; ///< Generic execution failure.
162static constexpr uint8_t RESULT_NO_EXECUTION = 0x09; ///< Node did not move.
163static constexpr uint8_t RESULT_CALIBRATING = 0x0A; ///< Node is calibrating.
164static constexpr uint8_t RESULT_POWER_CONSUMPTION_TOO_HIGH = 0x0B; ///< Node power consumption is too high.
165static constexpr uint8_t RESULT_POWER_CONSUMPTION_TOO_LOW = 0x0C; ///< Node power consumption is too low.
166static constexpr uint8_t RESULT_LOCK_POSITION_OPEN = 0x0D; ///< Lock command failed because the door is open.
167static constexpr uint8_t RESULT_MOTION_TIME_TOO_LONG = 0x0E; ///< Target was not reached in time.
168static constexpr uint8_t RESULT_THERMAL_PROTECTION = 0x0F; ///< Node entered thermal protection mode.
169static constexpr uint8_t RESULT_PRODUCT_NOT_OPERATIONAL = 0x10; ///< Node is not currently operational.
170static constexpr uint8_t RESULT_FILTER_MAINTENANCE_NEEDED = 0x11; ///< Filter needs maintenance.
171static constexpr uint8_t RESULT_BATTERY_LEVEL = 0x12; ///< Battery level is low.
172static constexpr uint8_t RESULT_TARGET_MODIFIED = 0x13; ///< Node modified the requested target value.
173static constexpr uint8_t RESULT_MODE_NOT_IMPLEMENTED = 0x14; ///< Mode is not supported by the node.
174static constexpr uint8_t RESULT_COMMAND_INCOMPATIBLE_TO_MOVEMENT = 0x15; ///< Command cannot move the node that way.
175static constexpr uint8_t RESULT_USER_ACTION = 0x16; ///< User action overrode the command.
176static constexpr uint8_t RESULT_DEAD_BOLT_ERROR = 0x17; ///< Dead bolt error.
177static constexpr uint8_t RESULT_AUTOMATIC_CYCLE_ENGAGED = 0x18; ///< Node entered automatic cycle mode.
178static constexpr uint8_t RESULT_WRONG_LOAD_CONNECTED = 0x19; ///< Wrong load connected to node.
179static constexpr uint8_t RESULT_COLOUR_NOT_REACHABLE = 0x1A; ///< Requested colour not reachable.
180static constexpr uint8_t RESULT_TARGET_NOT_REACHABLE = 0x1B; ///< Requested target not reachable.
181static constexpr uint8_t RESULT_BAD_INDEX_RECEIVED = 0x1C; ///< Invalid index received.
182static constexpr uint8_t RESULT_COMMAND_OVERRULED = 0x1D; ///< Command was overruled by a newer command.
183static constexpr uint8_t RESULT_NODE_WAITING_FOR_POWER = 0x1E; ///< Node is waiting for power.
184static constexpr uint8_t RESULT_NODE_LOCKED = 0x20; ///< Node is locked.
185static constexpr uint8_t RESULT_WRONG_POSITION = 0x21; ///< Node reports wrong position.
186static constexpr uint8_t RESULT_LIMITS_NOT_SET = 0x22; ///< Device limits are not set.
187static constexpr uint8_t RESULT_IP_NOT_SET = 0x23; ///< Intermediate position is not set.
188static constexpr uint8_t RESULT_OUT_OF_RANGE = 0x24; ///< Requested value is out of range.
189static constexpr uint8_t RESULT_INFORMATION_CODE = 0xDF; ///< Information-only code with unknown semantics.
190static constexpr uint8_t RESULT_PARAMETER_LIMITED = 0xE0; ///< Parameter limited by an unknown device.
191static constexpr uint8_t RESULT_LIMITATION_BY_LOCAL_USER = 0xE1; ///< Parameter limited by local button.
192static constexpr uint8_t RESULT_LIMITATION_BY_USER = 0xE2; ///< Parameter limited by a remote control.
193static constexpr uint8_t RESULT_LIMITATION_BY_RAIN = 0xE3; ///< Parameter limited by a rain sensor.
194static constexpr uint8_t RESULT_LIMITATION_BY_TIMER = 0xE4; ///< Parameter limited by a timer.
195static constexpr uint8_t RESULT_LIMITATION_BY_SCD = 0xE5; ///< Parameter limited by a security actuator.
196static constexpr uint8_t RESULT_LIMITATION_BY_UPS = 0xE6; ///< Parameter limited by a power supply.
197static constexpr uint8_t RESULT_LIMITATION_BY_UNKNOWN_DEVICE = 0xE7; ///< Parameter limited by an unknown device.
198static constexpr uint8_t RESULT_LIMITATION_BY_SAAC = 0xEA; ///< Parameter limited by a standalone automatic controller.
199static constexpr uint8_t RESULT_LIMITATION_BY_WIND = 0xEB; ///< Parameter limited by a wind sensor.
200static constexpr uint8_t RESULT_LIMITATION_BY_MYSELF = 0xEC; ///< Parameter limited by the node itself.
201static constexpr uint8_t RESULT_LIMITATION_BY_AUTOMATIC_CYCLE = 0xED; ///< Parameter limited by an automatic cycle.
202static constexpr uint8_t RESULT_LIMITATION_BY_EMERGENCY = 0xEE; ///< Parameter limited by an emergency.
203
204// ============================================================================
205// Position and Status Constants
206// ============================================================================
207
208/// Position values in the IO protocol.
209/// Normal positions are 0-100 (0=fully open, 100=fully closed).
210/// Special values above 100 are control commands.
211static constexpr uint8_t POS_STOP = 0xD2; ///< Stop movement
212static constexpr uint8_t POS_UNKNOWN = 0xD4; ///< Position unknown
213static constexpr uint8_t POS_FAVORITE = 0xD8; ///< Move to favorite/"My" position
214
215/// In status responses, position is encoded as a 16-bit value where
216/// 0x0000 = fully open (0%) and 0xC800 = fully closed (100%).
217static constexpr uint16_t STATUS_POS_MAX = 0xC800;
218/// Target-reached tolerance expressed in raw IO-homecontrol position units.
219/// 100 raw units out of 51200 full-scale is about 0.195%, so this only absorbs
220/// tiny target/current mismatches from device rounding or early stopped flags.
221static constexpr uint16_t STATUS_POS_TOLERANCE_RAW = 100;
222
223/// Status byte flags in CMD_PRIVATE_RESP and CMD_STATUS_UPDATE.
224static constexpr uint8_t STATUS_STOPPED = 0x01; ///< Byte 0 bit 0: device is not moving
225static constexpr uint8_t STATUS_EXPECTED = 0x80; ///< Byte 1 bit 7: device will send auto status update
226static constexpr uint8_t STATUS_TILT_SELECTOR = 0x20; ///< Extended status payload marker for tilt-capable devices
227
228/// Packed device metadata uses two bytes where the high 8 bits carry the upper type bits and the
229/// low byte carries both the remaining type bits and the 6-bit manufacturer subtype.
230static constexpr uint8_t DEVICE_METADATA_SIZE = 2;
231static constexpr uint8_t DEVICE_TYPE_LOW_BITS_SHIFT = 2;
232static constexpr uint8_t DEVICE_TYPE_HIGH_BITS_SHIFT = 6;
233static constexpr uint8_t DEVICE_SUBTYPE_MASK = 0x3F;
234
235// ============================================================================
236// Cryptographic Constants
237// ============================================================================
238
239/// The transfer key is a hardcoded key used ONLY during pairing to obfuscate
240/// the system key during over-the-air transfer. It is NOT the system key.
241/// This is the same across all IO-Homecontrol devices worldwide.
242static constexpr uint8_t TRANSFER_KEY[AES_KEY_SIZE] = {0x34, 0xC3, 0x46, 0x6E, 0xD8, 0x8F, 0x4E, 0x8E,
243 0x16, 0xAA, 0x47, 0x39, 0x49, 0x88, 0x43, 0x73};
244static constexpr uint16_t CRC_POLYNOMIAL_REVERSED = 0x8408; ///< Reversed CRC-CCITT polynomial used by IO-homecontrol
245static constexpr uint16_t CRC_LSB_MASK = 0x0001; ///< Least-significant-bit mask for reflected CRC update
246
247/// Broadcast address for device discovery (0x00003B).
248/// Used as destination in CMD_DISCOVER_REQ frames to trigger all pairable devices to respond.
249static constexpr uint8_t BROADCAST_DISCOVER[NODE_ID_SIZE] = {0x00, 0x00, 0x3B};
250
251// ============================================================================
252// Frame Structure
253// ============================================================================
254
255/// @brief Parsed IO‑Homecontrol frame (CTRL0/1 + addresses + command + data).
256/// @ingroup hioc_protocol
257///
258/// Over the air layout: [CTRL0][CTRL1][DST 3B][SRC 3B][CMD][DATA 0-23B][CRC 2B].
259/// The CRC is handled by hardware on SX1276 (IoHomeOn) and by software on SX1262;
260/// it is not included in this struct.
261struct IoFrame {
262 uint8_t ctrl0; ///< Control byte 0: flags + length.
263 uint8_t ctrl1; ///< Control byte 1: low power, beacon, etc.
264 uint8_t dst[NODE_ID_SIZE]; ///< Destination node ID (3 bytes).
265 uint8_t src[NODE_ID_SIZE]; ///< Source node ID (3 bytes).
266 uint8_t cmd; ///< Command ID.
267 uint8_t data[FRAME_MAX_DATA_SIZE]; ///< Command parameters (0–23 bytes).
268 uint8_t data_len; ///< Actual length of data.
269};
270
271// --- Frame construction and parsing ---
272/// Initialize an IoFrame header (ctrl0/ctrl1) with flags.
273/// @param f Frame to initialize.
274/// @param is_2w True for 2‑way (default), false for 1‑way.
275/// @param start Set START flag (first frame in exchange).
276/// @param end Set END flag (final frame in exchange).
277/// @param low_power Set LOW_POWER flag.
278void init_frame(IoFrame &f, bool is_2w = true, bool start = false, bool end = false, bool low_power = false);
279/// Set destination node ID.
280/// @param f Frame to modify.
281/// @param id 3‑byte destination address.
282void set_dst(IoFrame &f, const uint8_t id[NODE_ID_SIZE]);
283/// Set source node ID.
284/// @param f Frame to modify.
285/// @param id 3‑byte source address.
286void set_src(IoFrame &f, const uint8_t id[NODE_ID_SIZE]);
287/// Set command and payload.
288/// @param f Frame to modify.
289/// @param cmd Command ID.
290/// @param params Pointer to payload bytes (may be nullptr for zero‑length).
291/// @param params_len Payload length (0–23).
292/// @return true if frame fits within size limits; false otherwise.
293bool set_cmd(IoFrame &f, uint8_t cmd, const uint8_t *params = nullptr, uint8_t params_len = 0);
294/// Get total frame length from ctrl0.
295/// @param f Parsed frame.
296/// @return Length in bytes.
297uint8_t frame_length(const IoFrame &f);
298/// Check START flag.
299/// @param f Parsed frame.
300/// @return true if START flag is set.
301bool is_start(const IoFrame &f);
302/// Check END flag.
303/// @param f Parsed frame.
304/// @return true if END flag is set.
305bool is_end(const IoFrame &f);
306/// Serialize a parsed frame into a wire buffer (without CRC).
307/// @param f Parsed frame.
308/// @param buf Output buffer (must be at least frame_length(f) bytes).
309/// @param buf_size Size of buf.
310/// @return Number of bytes written, or 0 on failure.
311uint8_t serialize(const IoFrame &f, uint8_t *buf, uint8_t buf_size);
312/// Parse a wire buffer into a parsed IoFrame (validates length and CTRL0).
313/// @param buf Raw byte buffer.
314/// @param buf_len Number of bytes in buf.
315/// @param f Output parsed frame.
316/// @return true if parse succeeded; false otherwise.
317bool parse(const uint8_t *buf, uint8_t buf_len, IoFrame &f);
318
319// ============================================================================
320// Device Types — from the IO-Homecontrol specification
321// ============================================================================
322
323/// @brief Device type identifiers reported by IO‑Homecontrol products.
324/// The numeric values follow the official specification. Do not reassign or reorder these.
325enum class DeviceType : uint8_t {
326 UNKNOWN = 0x00, ///< Unknown/unspecified device.
327 VENETIAN_BLIND = 0x01, ///< Venetian blind.
328 ROLLER_SHUTTER = 0x02, ///< Roller shutter.
329 AWNING = 0x03, ///< Awning.
330 WINDOW_OPENER = 0x04, ///< Window opening actuator.
331 GARAGE_OPENER = 0x05, ///< Garage door opener.
332 LIGHT = 0x06, ///< Binary light.
333 GATE_OPENER = 0x07, ///< Gate opener.
334 ROLLING_DOOR_OPENER = 0x08, ///< Rolling door opener.
335 LOCK = 0x09, ///< Lock.
336 BLIND = 0x0A, ///< Generic blind.
337 SCREEN = 0x0B, ///< Insect/privacy screen.
338 BEACON = 0x0C, ///< Beacon (unpaired/announcement).
339 DUAL_SHUTTER = 0x0D, ///< Dual-section shutter.
340 HEATING_TEMPERATURE_INTERFACE = 0x0E, ///< Heating temperature interface.
341 ON_OFF_SWITCH = 0x0F, ///< Generic on/off switch.
342 HORIZONTAL_AWNING = 0x10, ///< Horizontal awning (open/close inverted).
343 EXTERNAL_VENETIAN_BLIND = 0x11, ///< External venetian blind.
344 LOUVRE_BLIND = 0x12, ///< Louvre blind.
345 CURTAIN_TRACK = 0x13, ///< Curtain track.
346 VENTILATION_POINT = 0x14, ///< Ventilation point.
347 EXTERIOR_HEATING = 0x15, ///< Exterior heating.
348 HEAT_PUMP = 0x16, ///< Heat pump.
349 INTRUSION_ALARM = 0x17, ///< Intrusion alarm.
350 SWINGING_SHUTTER = 0x18, ///< Swinging shutter.
351};
352
353/// @brief High‑level capability class derived from DeviceType.
354enum class DeviceCapabilityClass : uint8_t {
355 UNKNOWN = 0x00, ///< Unknown capability.
356 COVER = 0x01, ///< Position‑controlled cover (shutter/blind/awning).
357 LIGHT = 0x02, ///< Binary on/off light.
358 SWITCH = 0x03, ///< Binary on/off switch.
359 SENSOR = 0x04, ///< Sensor device.
360 BEACON = 0x05, ///< Beacon.
361 CLIMATE = 0x06, ///< Climate device (heating/cooling).
362 LOCK = 0x07, ///< Lock.
363};
364
365/// @brief Convert a DeviceType to a lowercase string identifier.
366/// @param type Device type enum.
367/// @return Null‑terminated string name (e.g., "roller_shutter").
368const char *device_type_name(DeviceType type);
369
370/// @brief Map a raw IO‑Homecontrol type to the closest ESPHome/Home Assistant entity family.
371/// @param type Raw device type.
372/// @return Capability class (COVER, LIGHT, SWITCH, etc.).
374
375/// @brief Get a human‑readable name for a capability class.
376/// @param type Device type (unused, kept for signature compatibility).
377/// @return String like "cover", "light", "switch", "unknown".
379
380/// @brief Does this device type support precise position control (0–100)?
381/// @param type Device type.
382/// @return true for cover‑family devices.
384
385/// @brief Does this device type support binary on/off control?
386/// @param type Device type.
387/// @return true for lights and switches.
389
390/// @brief Does this device type support status request commands (0x03)?
391/// @param type Device type.
392/// @return true for covers, binary devices, and lock devices.
394
395/// @brief Does this device type support binary lock/unlock control via execute commands?
396/// @param type Device type.
397/// @return true for lock devices.
399
400/// @brief Does this device type support tilt (slat angle) control?
401/// @param type Device type.
402/// @return true for venetian blinds, blinds, external venetian blinds, louvre blinds.
404
405/// @brief Decode a protocol-packed device type from two metadata bytes.
406/// @param type_msb First metadata byte.
407/// @param type_subtype Second metadata byte containing the remaining type bits and subtype.
408/// @return Decoded device type.
409DeviceType decode_packed_device_type(uint8_t type_msb, uint8_t type_subtype);
410
411/// @brief Decode a protocol-packed device subtype from the second metadata byte.
412/// @param type_subtype Second metadata byte containing subtype in bits [5:0].
413/// @return Manufacturer-specific subtype.
414uint8_t decode_packed_device_subtype(uint8_t type_subtype);
415
416/// @brief Human‑readable operation profile name for a device type.
417/// Used for logging and diagnostics.
418/// @param type Device type.
419/// @return String such as "cover_position", "cover_position_tilt", "binary_on_off", "lock", etc.
421
422// ============================================================================
423// Device State
424// ============================================================================
425
426/// @brief Sentinel value meaning "position is not known yet".
427/// Matches POS_UNKNOWN (0xD4 = 212 decimal) for easy debugging.
428static constexpr float UNKNOWN_POSITION = 212.0F;
429static constexpr uint8_t DEVICE_NAME_BUFFER_SIZE = 32; ///< Device name storage including null terminator
430static constexpr uint8_t DEVICE_NAME_WRITE_CHAR_LIMIT = 15; ///< Reference write limit before the trailing null.
431static constexpr uint8_t DEVICE_NAME_WRITE_PAYLOAD_SIZE =
432 DEVICE_NAME_WRITE_CHAR_LIMIT + 1; ///< Fixed write payload: 15 visible chars plus trailing null/padding.
433static constexpr uint16_t LATIN1_CODEPOINT_MAX = 0x00FF; ///< Highest Unicode code point representable in Latin-1.
434
435/// @brief Validation result for outbound device-name writes.
436enum class DeviceNameValidationError : uint8_t {
437 NONE = 0x00, ///< Name is valid and encodable.
438 EMPTY = 0x01, ///< Name is empty after normalization.
439 TOO_LONG = 0x02, ///< Name exceeds the 15-character write limit.
440 INVALID_UTF8 = 0x03, ///< Name contains malformed UTF-8 bytes.
441 UNSUPPORTED_CHAR = 0x04, ///< Name contains characters outside Latin-1.
442};
443
444/// @brief Runtime state of a paired IO‑Homecontrol device.
445struct IoDevice {
446 uint8_t node_id[NODE_ID_SIZE]{}; ///< Device's 3‑byte radio address.
447 DeviceType type{DeviceType::UNKNOWN}; ///< Device type (shutter, awning, etc.).
448 uint8_t subtype{0}; ///< Device subtype (manufacturer‑specific).
449 char name[DEVICE_NAME_BUFFER_SIZE]{}; ///< Cached UTF-8 device name decoded from Latin-1 wire payloads.
450 float position{UNKNOWN_POSITION}; ///< Current position: 0=open, 100=closed, or UNKNOWN_POSITION.
451 float tilt{UNKNOWN_POSITION}; ///< Current tilt: 0=closed, 100=open, or UNKNOWN_POSITION.
452 float target{UNKNOWN_POSITION}; ///< Target position the device is moving toward.
453 bool is_stopped{true}; ///< True if device is not moving.
454 bool inverted{false}; ///< True if open/close positions are swapped (e.g., horizontal awning).
455 bool single_follow_up_poll_pending{false}; ///< True when one legacy settle poll should still be scheduled.
456 uint32_t last_status{0}; ///< millis() timestamp of last received status.
457 uint32_t status_poll_interval_ms{0}; ///< Configured follow-up poll interval while a state change is expected.
458 uint32_t next_update{0}; ///< millis() timestamp when we should poll for status next.
459 uint32_t poll_deadline{0}; ///< Hard stop for bounded follow-up polling after a command or remote activity.
460 uint8_t status_poll_failures{0}; ///< Consecutive background status-poll failures without a valid reply.
461 uint8_t auth_poll_failures{0}; ///< Consecutive background poll failures that reached 0x3C auth challenge.
462};
463
464/// @brief Convert a hex string (e.g., "123ABC") to a byte array.
465/// @param hex Hex string (must be exactly len*2 characters).
466/// @param out Output buffer (at least len bytes).
467/// @param len Number of bytes to produce.
468/// @return true on success; false if hex length mismatch or non‑hex characters.
469bool hex_to_bytes(const std::string &hex, uint8_t *out, uint8_t len);
470/// @brief Format a 3‑byte node ID as a 6‑character uppercase hex string.
471/// @param id 3‑byte node ID.
472/// @return Hex string (e.g., "123ABC").
473std::string node_id_to_string(const uint8_t id[NODE_ID_SIZE]);
474/// @brief Decode a device-name payload from IO-homecontrol's Latin-1 wire format into UTF-8.
475/// Some devices prepend an extra byte before the first character and many pad the payload with
476/// trailing 0x00 or 0x20 bytes. This helper normalizes those quirks and truncates the result to
477/// fit DEVICE_NAME_BUFFER_SIZE - 1 bytes when copied into IoDevice::name.
478/// @param data Raw payload pointer from CMD_GET_NAME_RESP.
479/// @param len Raw payload length in bytes.
480/// @return Normalized UTF-8 name, or an empty string when the payload carries no usable characters.
481std::string decode_device_name_payload(const uint8_t *data, uint8_t len);
482/// @brief Trim leading and trailing ASCII whitespace from a string.
483/// This is shared by device-name validation and management actions so both paths normalize input
484/// consistently before comparing or writing device metadata.
485/// @param value Input string.
486/// @return Copy of the string with leading and trailing ASCII whitespace removed.
487std::string trim_ascii_whitespace(const std::string &value);
488/// @brief Validate and encode a user-supplied UTF-8 device name into the fixed Latin-1 write payload.
489/// The outbound payload is a fixed 16-byte field: up to 15 Latin-1 characters followed by trailing
490/// zero padding. Leading and trailing ASCII whitespace are trimmed before validation so write-back
491/// verification against the device's padded storage is deterministic.
492/// @param name User-supplied UTF-8 device name.
493/// @param payload Output buffer for the fixed write payload (16 bytes, zero-padded on success).
494/// @param normalized_name Output normalized UTF-8 name used for later verification/logging.
495/// @return Validation status indicating success or the reason the name cannot be written.
497 uint8_t payload[DEVICE_NAME_WRITE_PAYLOAD_SIZE],
498 std::string &normalized_name);
499/// @brief Return a stable symbolic name for a device-name validation result.
500/// @param error Validation result.
501/// @return Uppercase symbolic name.
503/// @brief Return a human-readable explanation for a device-name validation result.
504/// @param error Validation result.
505/// @return Short message suitable for logs and Home Assistant action events.
507/// @brief Determine whether a device type has inverted position mapping by default.
508/// @param type Device type.
509/// @return true for horizontal awnings; false otherwise.
511/// @brief Return a stable symbolic name for a CMD_ERROR_RESP result code.
512/// @param result Result byte from CMD_ERROR_RESP data[0].
513/// @return Uppercase symbolic name, or "UNKNOWN_RESULT_CODE" when unmapped.
514const char *command_result_name(uint8_t result);
515/// @brief Return a human-readable explanation for a CMD_ERROR_RESP result code.
516/// @param result Result byte from CMD_ERROR_RESP data[0].
517/// @return Short description suitable for warn-level logs.
518const char *command_result_description(uint8_t result);
519/// @brief Check whether a result code represents an environmental or control limitation.
520/// @param result Result byte from CMD_ERROR_RESP data[0].
521/// @return true when the response reports a limitation rather than a generic execution error.
522bool is_limitation_result(uint8_t result);
523/// @brief Decode target/current position values from a status frame.
524/// @param target_raw 16‑bit raw target value.
525/// @param current_raw 16‑bit raw current value.
526/// @param is_stopped True if device reports stopped.
527/// @param target Output target position (0–100 or UNKNOWN_POSITION).
528/// @param position Output current position (0–100 or UNKNOWN_POSITION).
529void decode_position_report(uint16_t target_raw, uint16_t current_raw, bool is_stopped, float &target, float &position);
530/// @brief Has the device reached its target within tolerance?
531/// @param target Target position (0–100 or UNKNOWN_POSITION).
532/// @param position Current position (0–100 or UNKNOWN_POSITION).
533/// @return true if positions match within STATUS_POS_TOLERANCE_RAW.
534bool has_reached_target_position(float target, float position);
535/// @brief Decode tilt angle from raw 16‑bit value.
536/// @param tilt_raw Raw tilt value from status frame.
537/// @return Tilt percentage (0 = closed, 100 = open) or UNKNOWN_POSITION.
538float decode_tilt_report(uint16_t tilt_raw);
539/// @brief Compute CRC‑CCITT (poly 0x1021, init 0x0000) over a buffer.
540/// On SX1276 this is done in hardware; on SX1262 it is computed in software.
541/// @param data Pointer to data bytes.
542/// @param len Number of bytes.
543/// @return 16‑bit CRC value.
544uint16_t crc_ccitt(const uint8_t *data, uint8_t len);
545
546} // namespace home_io_control
547} // namespace esphome
bool set_cmd(IoFrame &f, uint8_t cmd, const uint8_t *params, uint8_t params_len)
Set command and payload.
static constexpr uint8_t DEVICE_NAME_BUFFER_SIZE
Device name storage including null terminator.
const char * device_operation_profile_name(DeviceType type)
Human‑readable operation profile name for a device type.
static constexpr uint8_t RESULT_OUT_OF_RANGE
Requested value is out of range.
static constexpr uint8_t RESULT_CALIBRATING
Node is calibrating.
static constexpr uint8_t BITS_PER_BYTE
Number of bits in one protocol byte.
Definition proto_frame.h:88
static constexpr uint8_t DEVICE_METADATA_SIZE
Packed device metadata uses two bytes where the high 8 bits carry the upper type bits and the low byt...
static constexpr float UNKNOWN_POSITION
Sentinel value meaning "position is not known yet".
static constexpr uint8_t CMD_DISCOVER_REQ
Broadcast discovery request.
static constexpr uint8_t NODE_ID_SIZE
Device/node addresses are 3 bytes (e.g., "123ABC").
Definition proto_frame.h:81
static constexpr uint8_t AES_BLOCK_SIZE
AES block size.
Definition proto_frame.h:85
static constexpr uint8_t CMD_SET_CONFIG1
Configure device to auto-send status updates.
static constexpr uint16_t STATUS_POS_MAX
In status responses, position is encoded as a 16-bit value where 0x0000 = fully open (0%) and 0xC800 ...
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 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 FRAME_MIN_SIZE
Minimum frame: CTRL0+CTRL1+DST(3)+SRC(3)+CMD(1).
Definition proto_frame.h:90
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 TRANSFER_KEY[AES_KEY_SIZE]
The transfer key is a hardcoded key used ONLY during pairing to obfuscate the system key during over-...
static constexpr uint8_t CMD_ERROR_RESP
Error response to any command.
static constexpr uint8_t CMD_GET_NAME_RESP
Device name response.
static constexpr uint8_t RESULT_AUTOMATIC_CYCLE_ENGAGED
Node entered automatic cycle mode.
static constexpr uint8_t CMD_DISCOVER_CONFIRM_ACK
Device acknowledges confirmation.
static constexpr uint8_t DEVICE_TYPE_HIGH_BITS_SHIFT
DeviceType
Device type identifiers reported by IO‑Homecontrol products.
@ DUAL_SHUTTER
Dual-section shutter.
@ HEATING_TEMPERATURE_INTERFACE
Heating temperature interface.
@ BEACON
Beacon (unpaired/announcement).
@ EXTERNAL_VENETIAN_BLIND
External venetian blind.
@ UNKNOWN
Unknown/unspecified device.
@ WINDOW_OPENER
Window opening actuator.
@ HORIZONTAL_AWNING
Horizontal awning (open/close inverted).
@ ROLLING_DOOR_OPENER
Rolling door opener.
@ ON_OFF_SWITCH
Generic on/off switch.
@ SCREEN
Insect/privacy screen.
@ VENTILATION_POINT
Ventilation point.
static constexpr uint8_t RESULT_MODE_NOT_IMPLEMENTED
Mode is not supported by the node.
static constexpr uint8_t RESULT_COMMAND_OVERRULED
Command was overruled by a newer command.
static constexpr uint8_t CTRL0_END
Control byte 0 (CTRL0) bit definitions.
static constexpr uint8_t FRAME_MAX_DATA_SIZE
Maximum data bytes after command ID.
Definition proto_frame.h:92
static constexpr uint8_t POS_UNKNOWN
Position unknown.
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 uint16_t STATUS_POS_TOLERANCE_RAW
Target-reached tolerance expressed in raw IO-homecontrol position units.
DeviceCapabilityClass device_capability_class(DeviceType type)
Map a raw IO‑Homecontrol type to the closest ESPHome/Home Assistant entity family.
static constexpr uint8_t CMD_STATUS_UPDATE
Device-initiated status update (needs auth).
bool default_inverted_for_type(DeviceType type)
Determine whether a device type has inverted position mapping by default.
static constexpr uint16_t LATIN1_CODEPOINT_MAX
Highest Unicode code point representable in Latin-1.
static constexpr uint8_t RESULT_LIMITATION_BY_UNKNOWN_DEVICE
Parameter limited by an unknown device.
static constexpr uint8_t LBT_MAX_RETRIES
Max carrier-sense attempts before TX anyway.
Definition proto_frame.h:74
bool is_start(const IoFrame &f)
Check START flag.
static constexpr uint8_t RESULT_LOCK_POSITION_OPEN
Lock command failed because the door is open.
static constexpr uint32_t FREQ_CH1
The protocol uses 3 frequency channels in the 868 MHz ISM band.
Definition proto_frame.h:31
bool device_supports_position_control(DeviceType type)
Does this device type support precise position control (0–100)?
static constexpr uint8_t RESULT_WRONG_SYSTEMKEY
Node contains the wrong system key.
static constexpr uint32_t FREQ_CH3
Channel 3: 869.85 MHz (2W only).
Definition proto_frame.h:33
static constexpr uint8_t CMD_GET_NAME
Request device name.
static constexpr int32_t HOP_TIME_US
Timing constants for frequency hopping and response waiting.
Definition proto_frame.h:60
static constexpr uint8_t CTRL0_PROTOCOL_1W
Bit 5: 1=OneWay protocol, 0=TwoWay protocol.
const char * device_type_name(DeviceType type)
Convert a DeviceType to a lowercase string identifier.
static constexpr uint8_t CTRL0_START
Bit 6: first frame in exchange (uses long preamble).
bool device_supports_binary_control(DeviceType type)
Does this device type support binary on/off control?
static constexpr uint8_t CMD_DISCOVER_SPE_RESP
Sub-device response.
static constexpr uint8_t RESULT_LIMITATION_BY_EMERGENCY
Parameter limited by an emergency.
uint16_t crc_ccitt(const uint8_t *data, uint8_t len)
CRC-CCITT used by the IO-Homecontrol protocol for frame validation.
static constexpr uint8_t HMAC_SIZE
Authentication HMAC is 6 bytes (truncated AES output).
Definition proto_frame.h:83
static constexpr uint8_t RESULT_NODE_WAITING_FOR_POWER
Node is waiting for power.
void init_frame(IoFrame &f, bool is_2w, bool start, bool end, bool low_power)
Initialize an IoFrame header (ctrl0/ctrl1) with flags.
static constexpr uint8_t RESULT_BATTERY_LEVEL
Battery level is low.
static constexpr uint8_t RESULT_MOTION_TIME_TOO_LONG
Target was not reached in time.
float decode_tilt_report(uint16_t tilt_raw)
Decode tilt angle from raw 16‑bit value.
static constexpr uint8_t RESULT_THERMAL_PROTECTION
Node entered thermal protection mode.
static constexpr uint8_t RESULT_POWER_CONSUMPTION_TOO_HIGH
Node power consumption is too high.
static constexpr uint8_t RESULT_ERROR_DURING_EXECUTION
Generic execution failure.
uint8_t frame_length(const IoFrame &f)
Get total frame length from ctrl0.
bool device_supports_lock_control(DeviceType type)
Does this device type support binary lock/unlock control via execute commands?
static constexpr uint8_t FRAME_MAX_SIZE
Maximum frame size (9 header + 23 data).
Definition proto_frame.h:91
static constexpr uint8_t DEVICE_NAME_WRITE_PAYLOAD_SIZE
Fixed write payload: 15 visible chars plus trailing null/padding.
static constexpr uint8_t CMD_KEY_CONFIRM
Device confirms key was received.
static constexpr int32_t EXCHANGE_RETRY_DELAY_MS
Gap between retries within one HA command.
Definition proto_frame.h:66
static constexpr uint8_t RESULT_PARAMETER_LIMITED
Parameter limited by an unknown device.
static constexpr uint8_t CMD_KEY_INIT
Initiate key transfer to device.
static constexpr uint8_t RESULT_LIMITATION_BY_RAIN
Parameter limited by a rain sensor.
static constexpr uint8_t RESULT_USER_ACTION
User action overrode the command.
const char * command_result_description(uint8_t result)
Return a human-readable explanation for a CMD_ERROR_RESP result code.
void set_dst(IoFrame &f, const uint8_t id[NODE_ID_SIZE])
Set destination node ID.
DeviceType decode_packed_device_type(uint8_t type_msb, uint8_t type_subtype)
Decode a protocol-packed device type from two metadata bytes.
const char * device_name_validation_error_name(DeviceNameValidationError error)
Return a stable symbolic name for a device-name validation result.
const char * command_result_name(uint8_t result)
Return a stable symbolic name for a CMD_ERROR_RESP result code.
static constexpr uint8_t NODE_ID_STRING_SIZE
Uppercase hex node ID plus null terminator.
Definition proto_frame.h:82
bool is_end(const IoFrame &f)
Check END flag.
static constexpr uint8_t CMD_SET_NAME_RESP
Device-name write response.
DeviceNameValidationError
Validation result for outbound device-name writes.
@ UNSUPPORTED_CHAR
Name contains characters outside Latin-1.
@ TOO_LONG
Name exceeds the 15-character write limit.
@ EMPTY
Name is empty after normalization.
@ INVALID_UTF8
Name contains malformed UTF-8 bytes.
static constexpr uint8_t RESULT_DEAD_BOLT_ERROR
Dead bolt error.
static constexpr uint8_t RESULT_NO_EXECUTION
Node did not move.
static constexpr uint16_t CRC_LSB_MASK
Least-significant-bit mask for reflected CRC update.
static constexpr int32_t SX1262_EXCHANGE_RESPONSE_WAIT_SLICE_MS
SX1262-specific per-channel dwell while waiting for authenticated exchange responses.
Definition proto_frame.h:57
bool parse(const uint8_t *buf, uint8_t buf_len, IoFrame &f)
Parse a wire buffer into a parsed IoFrame (validates length and CTRL0).
std::string trim_ascii_whitespace(const std::string &value)
Trim leading and trailing ASCII whitespace from a string.
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
Discover sub-devices (e.g., light on garage door).
static constexpr uint8_t EXCHANGE_RETRY_COUNT
Attempts per command before reporting failure.
Definition proto_frame.h:67
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).
const char * device_capability_class_name(DeviceType type)
Get a human‑readable name for a capability class.
static constexpr uint8_t RESULT_LIMITATION_BY_USER
Parameter limited by a remote control.
static constexpr uint8_t RESULT_COMMAND_COMPLETED_OK
No errors detected.
static constexpr uint32_t FREQ_CH2
Channel 2: 868.95 MHz (1W and 2W, TX channel).
Definition proto_frame.h:32
static constexpr uint8_t CMD_CHALLENGE_REQ
Device sends 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 DEVICE_NAME_WRITE_CHAR_LIMIT
Reference write limit before the trailing null.
static constexpr uint8_t RESULT_COMMAND_INCOMPATIBLE_TO_MOVEMENT
Command cannot move the node that way.
DeviceNameValidationError encode_device_name_payload(const std::string &name, uint8_t payload[DEVICE_NAME_WRITE_PAYLOAD_SIZE], std::string &normalized_name)
Validate and encode a user-supplied UTF-8 device name into the fixed Latin-1 write payload.
static constexpr uint8_t RESULT_PRODUCT_NOT_OPERATIONAL
Node is not currently operational.
static constexpr uint8_t CMD_SET_NAME
Set device name (authenticated).
static constexpr uint8_t CMD_SET_CONFIG1_RESP
Config response.
std::string node_id_to_string(const uint8_t id[NODE_ID_SIZE])
Format a 3‑byte node ID as a 6‑character uppercase hex string.
static constexpr uint8_t CMD_DISCOVER_CONFIRM
Confirm discovery to device.
std::string decode_device_name_payload(const uint8_t *data, uint8_t len)
Decode a device-name payload from IO-homecontrol's Latin-1 wire format into UTF-8.
static constexpr uint8_t POS_FAVORITE
Move to favorite/"My" position.
static constexpr uint8_t STATUS_EXPECTED
Byte 1 bit 7: device will send auto status update.
uint8_t decode_packed_device_subtype(uint8_t type_subtype)
Decode a protocol-packed device subtype from the second metadata byte.
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 RESULT_WRONG_LOAD_CONNECTED
Wrong load connected to node.
static constexpr uint8_t STATUS_TILT_SELECTOR
Extended status payload marker for tilt-capable devices.
bool device_supports_status_requests(DeviceType type)
Does this device type support status request commands (0x03)?
static constexpr uint8_t LBT_RETRY_DELAY_MS
Backoff between LBT checks (≥ 5ms per ETSI).
Definition proto_frame.h:75
static constexpr uint16_t SHORT_PREAMBLE
8 bytes for response/continuation frames
Definition proto_frame.h:41
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 DEVICE_SUBTYPE_MASK
static constexpr uint8_t RESULT_LIMITATION_BY_TIMER
Parameter limited by a timer.
static constexpr uint8_t CTRL0_LENGTH_MASK
Bits [4:0]: frame length - 1.
bool has_reached_target_position(float target, float position)
Has the device reached its target within tolerance?
static constexpr uint8_t AES_KEY_SIZE
AES-128 key size.
Definition proto_frame.h:84
DeviceCapabilityClass
High‑level capability class derived from DeviceType.
@ CLIMATE
Climate device (heating/cooling).
@ COVER
Position‑controlled cover (shutter/blind/awning).
static constexpr int32_t RESPONSE_WAIT_MS
Wait for response to non-start frame.
Definition proto_frame.h:62
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 POS_STOP
Position values in the IO protocol.
static constexpr uint8_t DEVICE_TYPE_LOW_BITS_SHIFT
static constexpr uint8_t IV_PADDING
Padding byte used in IV construction.
Definition proto_frame.h:87
static constexpr uint8_t CMD_CHALLENGE_RESP
Controller responds with HMAC proof.
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 RESULT_UNKNOWN_STATUS_REPLY
Device returned an unknown status reply.
static constexpr uint16_t LONG_PREAMBLE
Preamble is a sequence of 0xAA bytes that precedes every frame.
Definition proto_frame.h:40
static constexpr uint8_t RESULT_LIMITS_NOT_SET
Device limits are not set.
void decode_position_report(uint16_t target_raw, uint16_t current_raw, bool is_stopped, float &target, float &position)
Decode target/current position values from a status frame.
static constexpr uint8_t RESULT_NODE_LOCKED
Node is locked.
static constexpr int32_t RESPONSE_AUTH_WAIT_MS
Wait for final response after challenge response.
Definition proto_frame.h:64
static constexpr uint8_t CTRL1_LOW_POWER
Control byte 1 (CTRL1) bit definitions.
const char * device_name_validation_error_description(DeviceNameValidationError error)
Return a human-readable explanation for a device-name validation result.
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.
bool device_supports_tilt(DeviceType type)
Does this device type support tilt (slat angle) control?
static constexpr uint8_t STATUS_STOPPED
Status byte flags in CMD_PRIVATE_RESP and CMD_STATUS_UPDATE.
static constexpr int32_t RESPONSE_CHANNEL_WAIT_MS
Per-channel dwell while waiting for an exchange response.
Definition proto_frame.h:61
static constexpr uint8_t RESULT_BLOCKED
Node blocked by an object.
static constexpr uint8_t RESULT_INFORMATION_CODE
Information-only code with unknown semantics.
bool hex_to_bytes(const std::string &hex, uint8_t *out, uint8_t len)
Convert a hex string (e.g., "123ABC") to a byte array.
static constexpr int16_t LBT_RSSI_THRESHOLD_DBM
Listen-before-talk (LBT) parameters for ETSI EN 300 220 compliance.
Definition proto_frame.h:73
static constexpr uint8_t RESULT_LIMITATION_BY_LOCAL_USER
Parameter limited by local button.
static constexpr uint8_t IV_SIZE
Initialization vector size for AES.
Definition proto_frame.h:86
uint8_t serialize(const IoFrame &f, uint8_t *buf, uint8_t buf_size)
Serialize a parsed frame into a wire buffer (without CRC).
static constexpr uint8_t RESULT_TARGET_NOT_REACHABLE
Requested target not reachable.
static constexpr uint8_t RESULT_IP_NOT_SET
Intermediate position is not set.
void set_src(IoFrame &f, const uint8_t id[NODE_ID_SIZE])
Set source node ID.
static constexpr uint16_t SX1262_AUTH_RESPONSE_PREAMBLE
SX1262-specific preamble for the outbound 0x3D challenge response.
Definition proto_frame.h:49
static constexpr uint8_t RESULT_LIMITATION_BY_SCD
Parameter limited by a security actuator.
static constexpr int32_t RESPONSE_START_WAIT_MS
Wait for response to start frame (longer).
Definition proto_frame.h:63
Runtime state of a paired IO‑Homecontrol device.
float target
Target position the device is moving toward.
float tilt
Current tilt: 0=closed, 100=open, or UNKNOWN_POSITION.
uint32_t status_poll_interval_ms
Configured follow-up poll interval while a state change is expected.
uint32_t next_update
millis() timestamp when we should poll for status next.
uint32_t last_status
millis() timestamp of last received status.
char name[DEVICE_NAME_BUFFER_SIZE]
Cached UTF-8 device name decoded from Latin-1 wire payloads.
uint8_t status_poll_failures
Consecutive background status-poll failures without a valid reply.
uint8_t auth_poll_failures
Consecutive background poll failures that reached 0x3C auth challenge.
bool inverted
True if open/close positions are swapped (e.g., horizontal awning).
float position
Current position: 0=open, 100=closed, or UNKNOWN_POSITION.
uint8_t subtype
Device subtype (manufacturer‑specific).
uint8_t node_id[NODE_ID_SIZE]
Device's 3‑byte radio address.
bool single_follow_up_poll_pending
True when one legacy settle poll should still be scheduled.
DeviceType type
Device type (shutter, awning, etc.).
bool is_stopped
True if device is not moving.
uint32_t poll_deadline
Hard stop for bounded follow-up polling after a command or remote activity.
Parsed IO‑Homecontrol frame (CTRL0/1 + addresses + command + data).
uint8_t data[FRAME_MAX_DATA_SIZE]
Command parameters (0–23 bytes).
uint8_t ctrl0
Control byte 0: flags + length.
uint8_t src[NODE_ID_SIZE]
Source node ID (3 bytes).
uint8_t dst[NODE_ID_SIZE]
Destination node ID (3 bytes).
uint8_t data_len
Actual length of data.
uint8_t ctrl1
Control byte 1: low power, beacon, etc.