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 frame container: control bytes, IoFrame and (de)serialization.
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/// This header owns only the frame container itself. The rest of the protocol model lives in
16/// cohesive headers (proto_sizes/proto_timing/proto_constants/proto_device_model/proto_codecs).
17/// New code should include the specific header it needs.
18
19#include "proto_sizes.h"
20
21#include <cstdint>
22#include <cstring>
23#include <string>
24
25namespace esphome {
26namespace home_io_control {
27
28// ============================================================================
29// Control Bytes
30// ============================================================================
31
32/// Control byte 0 (CTRL0) bit definitions.
33/// CTRL0 encodes frame flags and the total frame length.
34/// Bits [4:0] = frame_length - 1 (so 0x08 means 9 bytes total).
35/// - START (bit 6): first frame in an exchange; uses long preamble (1024 bytes).
36/// - END (bit 7): last frame in an exchange; set on responses and command completions.
37/// - 1W (bit 5): 1=OneWay protocol (no response expected), 0=TwoWay (response expected).
38/// For 2W operation, the controller sets START on initial command and device replies with END; subsequent frames in an
39/// authenticated exchange also carry END.
40static constexpr uint8_t CTRL0_END = 0x80; ///< Bit 7: last frame in exchange
41static constexpr uint8_t CTRL0_START = 0x40; ///< Bit 6: first frame in exchange (uses long preamble)
42static constexpr uint8_t CTRL0_PROTOCOL_1W = 0x20; ///< Bit 5: 1=OneWay protocol, 0=TwoWay protocol
43static constexpr uint8_t CTRL0_LENGTH_MASK = 0x1F; ///< Bits [4:0]: frame length - 1
44
45/// Control byte 1 (CTRL1) bit definitions.
46/// CTRL1 carries protocol metadata flags that describe the frame's routing,
47/// power mode, and priority characteristics.
48/// - VERSION (bits [1:0]): protocol version number (usually 0 for current devices).
49/// - PRIORITY (bit 2): marks a high-priority frame (e.g., discovery, security commands).
50/// - ACK (bit 4): sender can handle 2W responses (set on all outbound 2W frames).
51/// - LOW_POWER (bit 5): device is battery/solar powered; may sleep and requires long preamble to wake.
52/// - ROUTED (bit 6): frame was relayed through a repeater node rather than direct.
53/// - BEACON (bit 7): beacon announcement frame (device presence advertisement).
54static constexpr uint8_t CTRL1_VERSION_MASK = 0x03; ///< Bits [1:0]: protocol version (usually 0).
55static constexpr uint8_t CTRL1_PRIORITY = 0x04; ///< Bit 2: high-priority frame.
56static constexpr uint8_t CTRL1_ACK = 0x10; ///< Bit 4: sender can handle 2W responses (ACK-capable).
57static constexpr uint8_t CTRL1_LOW_POWER = 0x20; ///< Bit 5: low-power device (e.g., solar-powered).
58static constexpr uint8_t CTRL1_ROUTED = 0x40; ///< Bit 6: frame was relayed through a repeater.
59static constexpr uint8_t CTRL1_BEACON = 0x80; ///< Bit 7: beacon announcement frame.
60
61// ============================================================================
62// Frame Structure
63// ============================================================================
64
65/// @brief Parsed IO‑Homecontrol frame (CTRL0/1 + addresses + command + data).
66/// @ingroup hioc_protocol
67///
68/// Over the air layout: [CTRL0][CTRL1][DST 3B][SRC 3B][CMD][DATA 0-23B][CRC 2B].
69/// The on-air CRC is the radio driver's responsibility (hardware or software,
70/// depending on the chip); it is not included in this struct.
71struct IoFrame {
72 uint8_t ctrl0; ///< Control byte 0: flags + length.
73 uint8_t ctrl1; ///< Control byte 1: low power, beacon, etc.
74 uint8_t dst[NODE_ID_SIZE]; ///< Destination node ID (3 bytes).
75 uint8_t src[NODE_ID_SIZE]; ///< Source node ID (3 bytes).
76 uint8_t cmd; ///< Command ID.
77 uint8_t data[FRAME_MAX_DATA_SIZE]; ///< Command parameters (0–23 bytes).
78 uint8_t data_len; ///< Actual length of data.
79};
80
81// --- Frame construction and parsing ---
82/// Initialize an IoFrame header (ctrl0/ctrl1) with flags.
83///
84/// Note: CTRL1_ACK is NOT automatically set on outbound frames. Some real-world
85/// devices reject frames with unexpected CTRL1 bits, causing total communication
86/// failure. The ACK constant is retained for inbound frame parsing and logging only.
87/// @param f Frame to initialize.
88/// @param is_2w True for 2‑way (default), false for 1‑way.
89/// @param start Set START flag (first frame in exchange).
90/// @param end Set END flag (final frame in exchange).
91/// @param low_power Set LOW_POWER flag.
92void init_frame(IoFrame &f, bool is_2w = true, bool start = false, bool end = false, bool low_power = false);
93/// Set destination node ID.
94/// @param f Frame to modify.
95/// @param id 3‑byte destination address.
96void set_dst(IoFrame &f, const uint8_t id[NODE_ID_SIZE]);
97/// Set source node ID.
98/// @param f Frame to modify.
99/// @param id 3‑byte source address.
100void set_src(IoFrame &f, const uint8_t id[NODE_ID_SIZE]);
101/// Set command and payload.
102/// @param f Frame to modify.
103/// @param cmd Command ID.
104/// @param params Pointer to payload bytes (may be nullptr for zero‑length).
105/// @param params_len Payload length (0–23).
106/// @return true if frame fits within size limits; false otherwise.
107bool set_cmd(IoFrame &f, uint8_t cmd, const uint8_t *params = nullptr, uint8_t params_len = 0);
108/// Get total frame length from ctrl0.
109/// @param f Parsed frame.
110/// @return Length in bytes.
111uint8_t frame_length(const IoFrame &f);
112/// Check START flag.
113/// @param f Parsed frame.
114/// @return true if START flag is set.
115bool is_start(const IoFrame &f);
116/// Check END flag.
117/// @param f Parsed frame.
118/// @return true if END flag is set.
119bool is_end(const IoFrame &f);
120/// Serialize a parsed frame into a wire buffer (without CRC).
121/// @param f Parsed frame.
122/// @param buf Output buffer (must be at least frame_length(f) bytes).
123/// @param buf_size Size of buf.
124/// @return Number of bytes written, or 0 on failure.
125uint8_t serialize(const IoFrame &f, uint8_t *buf, uint8_t buf_size);
126/// Parse a wire buffer into a parsed IoFrame (validates length and CTRL0).
127/// @param buf Raw byte buffer.
128/// @param buf_len Number of bytes in buf.
129/// @param f Output parsed frame.
130/// @return true if parse succeeded; false otherwise.
131bool parse(const uint8_t *buf, uint8_t buf_len, IoFrame &f);
132
133// ============================================================================
134// Node ID Helpers
135// ============================================================================
136
137/// @brief Convert a hex string (e.g., "123ABC") to a byte array.
138/// @param hex Hex string (must be exactly len*2 characters).
139/// @param out Output buffer (at least len bytes).
140/// @param len Number of bytes to produce.
141/// @return true on success; false if hex length mismatch or non‑hex characters.
142bool hex_to_bytes(const std::string &hex, uint8_t *out, uint8_t len);
143/// @brief Format a 3‑byte node ID as a 6‑character uppercase hex string.
144/// @param id 3‑byte node ID.
145/// @return Hex string (e.g., "123ABC").
146std::string node_id_to_string(const uint8_t id[NODE_ID_SIZE]);
147
148// ============================================================================
149// CRC
150// ============================================================================
151
152/// @brief Compute CRC‑CCITT (poly 0x1021, init 0x0000) over a buffer.
153/// Used by radio drivers without hardware IO-Homecontrol CRC support and by
154/// frame validation in tests.
155/// @param data Pointer to data bytes.
156/// @param len Number of bytes.
157/// @return 16‑bit CRC value.
158uint16_t crc_ccitt(const uint8_t *data, uint8_t len);
159
160} // namespace home_io_control
161} // 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 NODE_ID_SIZE
Device/node addresses are 3 bytes (e.g., "123ABC").
Definition proto_sizes.h:20
static constexpr uint8_t CTRL0_END
Control byte 0 (CTRL0) bit definitions.
Definition proto_frame.h:40
static constexpr uint8_t FRAME_MAX_DATA_SIZE
Maximum data bytes after command ID.
Definition proto_sizes.h:31
bool is_start(const IoFrame &f)
Check START flag.
static constexpr uint8_t CTRL0_PROTOCOL_1W
Bit 5: 1=OneWay protocol, 0=TwoWay protocol.
Definition proto_frame.h:42
static constexpr uint8_t CTRL0_START
Bit 6: first frame in exchange (uses long preamble).
Definition proto_frame.h:41
static constexpr uint8_t CTRL1_ROUTED
Bit 6: frame was relayed through a repeater.
Definition proto_frame.h:58
uint16_t crc_ccitt(const uint8_t *data, uint8_t len)
CRC-CCITT used by the IO-Homecontrol protocol for frame validation.
void init_frame(IoFrame &f, bool is_2w, bool start, bool end, bool low_power)
Initialize an IoFrame header (ctrl0/ctrl1) with flags.
uint8_t frame_length(const IoFrame &f)
Get total frame length from ctrl0.
void set_dst(IoFrame &f, const uint8_t id[NODE_ID_SIZE])
Set destination node ID.
bool is_end(const IoFrame &f)
Check END flag.
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 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 CTRL1_PRIORITY
Bit 2: high-priority frame.
Definition proto_frame.h:55
static constexpr uint8_t CTRL1_ACK
Bit 4: sender can handle 2W responses (ACK-capable).
Definition proto_frame.h:56
static constexpr uint8_t CTRL0_LENGTH_MASK
Bits [4:0]: frame length - 1.
Definition proto_frame.h:43
static constexpr uint8_t CTRL1_VERSION_MASK
Control byte 1 (CTRL1) bit definitions.
Definition proto_frame.h:54
static constexpr uint8_t CTRL1_LOW_POWER
Bit 5: low-power device (e.g., solar-powered).
Definition proto_frame.h:57
static constexpr uint8_t CTRL1_BEACON
Bit 7: beacon announcement frame.
Definition proto_frame.h:59
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.
uint8_t serialize(const IoFrame &f, uint8_t *buf, uint8_t buf_size)
Serialize a parsed frame into a wire buffer (without CRC).
void set_src(IoFrame &f, const uint8_t id[NODE_ID_SIZE])
Set source node ID.
Fundamental IO-Homecontrol frame and crypto size constants.
Parsed IO‑Homecontrol frame (CTRL0/1 + addresses + command + data).
Definition proto_frame.h:71
uint8_t data[FRAME_MAX_DATA_SIZE]
Command parameters (0–23 bytes).
Definition proto_frame.h:77
uint8_t ctrl0
Control byte 0: flags + length.
Definition proto_frame.h:72
uint8_t src[NODE_ID_SIZE]
Source node ID (3 bytes).
Definition proto_frame.h:75
uint8_t dst[NODE_ID_SIZE]
Destination node ID (3 bytes).
Definition proto_frame.h:74
uint8_t data_len
Actual length of data.
Definition proto_frame.h:78
uint8_t ctrl1
Control byte 1: low power, beacon, etc.
Definition proto_frame.h:73