17constexpr int HEX_ALPHA_OFFSET = 10;
18constexpr uint8_t UTF8_SINGLE_BYTE_MAX = 0x80;
19constexpr uint8_t UTF8_TWO_BYTE_LEAD_BASE = 0xC0;
20constexpr uint8_t UTF8_CONTINUATION_BASE = 0x80;
21constexpr uint8_t UTF8_CONTINUATION_MASK = 0x3F;
22constexpr uint8_t UTF8_TWO_BYTE_SHIFT = 6;
23constexpr uint8_t NAME_PADDING_NUL = 0x00;
24constexpr uint8_t NAME_PADDING_SPACE = 0x20;
25constexpr uint8_t UTF8_TWO_BYTE_MASK = 0xE0;
26constexpr uint8_t UTF8_TWO_BYTE_PREFIX = 0xC0;
27constexpr uint8_t UTF8_THREE_BYTE_MASK = 0xF0;
28constexpr uint8_t UTF8_THREE_BYTE_PREFIX = 0xE0;
29constexpr uint8_t UTF8_FOUR_BYTE_MASK = 0xF8;
30constexpr uint8_t UTF8_FOUR_BYTE_PREFIX = 0xF0;
31constexpr uint8_t UTF8_CONTINUATION_PREFIX_MASK = 0xC0;
32constexpr uint8_t UTF8_CONTINUATION_PREFIX = 0x80;
33constexpr uint8_t UTF8_TWO_BYTE_VALUE_MASK = 0x1F;
34constexpr uint8_t ASCII_MAX = 0x7F;
40 while (begin < value.length() && std::isspace(
static_cast<unsigned char>(value[begin])) != 0)
43 size_t end = value.length();
44 while (end > begin && std::isspace(
static_cast<unsigned char>(value[end - 1])) != 0)
47 return value.substr(begin, end - begin);
52std::string latin1_to_utf8(
const uint8_t *data,
size_t len) {
54 result.reserve(len * 2);
56 for (
size_t index = 0; index < len; index++) {
57 uint8_t
const byte = data[index];
58 if (
byte < UTF8_SINGLE_BYTE_MAX) {
61 result.push_back(
static_cast<char>(
byte));
68 result.push_back(
static_cast<char>(UTF8_TWO_BYTE_LEAD_BASE | (
byte >> UTF8_TWO_BYTE_SHIFT)));
69 result.push_back(
static_cast<char>(UTF8_CONTINUATION_BASE | (
byte & UTF8_CONTINUATION_MASK)));
78 if (ch >=
'0' && ch <=
'9')
80 ch =
static_cast<char>(std::toupper(
static_cast<unsigned char>(ch)));
81 if (ch >=
'A' && ch <=
'F')
82 return HEX_ALPHA_OFFSET + (ch -
'A');
86bool hex_to_bytes(
const std::string &hex, uint8_t *out, uint8_t len) {
91 if (hex.length() !=
static_cast<size_t>(len) * 2)
94 for (uint8_t i = 0; i < len; i++) {
97 if (high < 0 || low < 0)
99 out[i] =
static_cast<uint8_t
>((high << 4) | low);
107 snprintf(buf,
sizeof(buf),
"%02X%02X%02X",
id[0],
id[1],
id[2]);
108 return std::string(buf);
112 if (data ==
nullptr || len == 0)
115 const uint8_t begin = data[0] > NAME_PADDING_SPACE ? 0 : 1;
119 size_t raw_len = len - begin;
120 while (raw_len > 0 &&
121 (data[begin + raw_len - 1] == NAME_PADDING_NUL || data[begin + raw_len - 1] == NAME_PADDING_SPACE))
127 return latin1_to_utf8(data + begin, raw_len);
132 std::string &normalized_name) {
133 if (payload ==
nullptr)
137 normalized_name.clear();
140 if (trimmed_name.empty())
143 uint8_t latin1_len = 0;
144 for (
size_t index = 0; index < trimmed_name.length();) {
145 const auto byte =
static_cast<uint8_t
>(trimmed_name[index]);
146 uint16_t codepoint = 0;
149 if (
byte <= ASCII_MAX) {
151 }
else if ((
byte & UTF8_TWO_BYTE_MASK) == UTF8_TWO_BYTE_PREFIX) {
152 if (index + 1 >= trimmed_name.length())
155 const auto continuation =
static_cast<uint8_t
>(trimmed_name[index + 1]);
156 if ((continuation & UTF8_CONTINUATION_PREFIX_MASK) != UTF8_CONTINUATION_PREFIX)
159 codepoint =
static_cast<uint16_t
>(((
byte & UTF8_TWO_BYTE_VALUE_MASK) << UTF8_TWO_BYTE_SHIFT) |
160 (continuation & UTF8_CONTINUATION_MASK));
161 if (codepoint < UTF8_SINGLE_BYTE_MAX)
164 }
else if ((
byte & UTF8_THREE_BYTE_MASK) == UTF8_THREE_BYTE_PREFIX ||
165 (
byte & UTF8_FOUR_BYTE_MASK) == UTF8_FOUR_BYTE_PREFIX) {
177 payload[latin1_len++] =
static_cast<uint8_t
>(codepoint);
181 normalized_name = latin1_to_utf8(payload, latin1_len);
194 return "INVALID_UTF8";
196 return "UNSUPPORTED_CHAR";
198 return "UNKNOWN_DEVICE_NAME_VALIDATION_ERROR";
205 return "name accepted";
207 return "device name must not be empty";
209 return "device name exceeds the 15-character write limit";
211 return "device name must be valid UTF-8";
213 return "device name contains characters outside Latin-1";
215 return "unknown device-name validation error";
224 return "UNKNOWN_STATUS_REPLY";
226 return "COMMAND_COMPLETED_OK";
230 return "MANUALLY_OPERATED";
234 return "WRONG_SYSTEMKEY";
236 return "PRIORITY_LEVEL_LOCKED";
238 return "REACHED_WRONG_POSITION";
240 return "ERROR_DURING_EXECUTION";
242 return "NO_EXECUTION";
244 return "CALIBRATING";
246 return "POWER_CONSUMPTION_TOO_HIGH";
248 return "POWER_CONSUMPTION_TOO_LOW";
250 return "LOCK_POSITION_OPEN";
252 return "MOTION_TIME_TOO_LONG";
254 return "THERMAL_PROTECTION";
256 return "PRODUCT_NOT_OPERATIONAL";
258 return "FILTER_MAINTENANCE_NEEDED";
260 return "BATTERY_LEVEL";
262 return "TARGET_MODIFIED";
264 return "MODE_NOT_IMPLEMENTED";
266 return "COMMAND_INCOMPATIBLE_TO_MOVEMENT";
268 return "USER_ACTION";
270 return "DEAD_BOLT_ERROR";
272 return "AUTOMATIC_CYCLE_ENGAGED";
274 return "WRONG_LOAD_CONNECTED";
276 return "COLOUR_NOT_REACHABLE";
278 return "TARGET_NOT_REACHABLE";
280 return "BAD_INDEX_RECEIVED";
282 return "COMMAND_OVERRULED";
284 return "NODE_WAITING_FOR_POWER";
286 return "NODE_LOCKED";
288 return "WRONG_POSITION";
290 return "LIMITS_NOT_SET";
294 return "OUT_OF_RANGE";
296 return "INFORMATION_CODE";
298 return "PARAMETER_LIMITED";
300 return "LIMITATION_BY_LOCAL_USER";
302 return "LIMITATION_BY_USER";
304 return "LIMITATION_BY_RAIN";
306 return "LIMITATION_BY_TIMER";
308 return "LIMITATION_BY_SCD";
310 return "LIMITATION_BY_UPS";
312 return "LIMITATION_BY_UNKNOWN_DEVICE";
314 return "LIMITATION_BY_SAAC";
316 return "LIMITATION_BY_WIND";
318 return "LIMITATION_BY_MYSELF";
320 return "LIMITATION_BY_AUTOMATIC_CYCLE";
322 return "LIMITATION_BY_EMERGENCY";
324 return "UNKNOWN_RESULT_CODE";
331 return "unknown reply";
333 return "no errors detected";
335 return "no communication to node";
337 return "manually operated by a user";
339 return "node has been blocked by an object";
341 return "node contains the wrong system key";
343 return "node is locked on this priority level";
345 return "node stopped in another position than expected";
347 return "an error occurred during command execution";
349 return "no movement of the node parameter";
351 return "node is calibrating the parameters";
353 return "node power consumption is too high";
355 return "node power consumption is too low";
357 return "door open during lock command";
359 return "target was not reached in time";
361 return "node has gone into thermal protection mode";
363 return "node is not currently operational";
365 return "filter needs maintenance";
367 return "battery level is low";
369 return "node modified the requested target value";
371 return "node does not support the received mode";
373 return "node cannot move in the requested direction";
375 return "user action overrode the command";
377 return "dead bolt error";
379 return "node has gone into automatic cycle mode";
381 return "wrong load connected to node";
383 return "node cannot reach the requested colour";
385 return "node cannot reach the requested target position";
387 return "invalid index received";
389 return "command was overruled by a newer command";
391 return "node is waiting for power";
393 return "node is locked";
395 return "wrong position";
397 return "limits are not set";
399 return "intermediate position is not set";
401 return "requested value is out of range";
403 return "information-only result with unknown semantics";
405 return "parameter was limited by an unknown device";
407 return "parameter was limited by the local button";
409 return "parameter was limited by a remote control";
411 return "parameter was limited by a rain sensor";
413 return "parameter was limited by a timer";
415 return "parameter was limited by a security controlling actuator";
417 return "parameter was limited by a power supply";
419 return "parameter was limited by an unknown device";
421 return "parameter was limited by a standalone automatic controller";
423 return "parameter was limited by a wind sensor";
425 return "parameter was limited by the node itself";
427 return "parameter was limited by an automatic cycle";
429 return "parameter was limited by an emergency";
431 return "unknown result code";
471 }
else if (is_stopped && current_valid) {
475 target = decoded_current;
481 position = decoded_current;
482 }
else if (is_stopped && target_valid) {
493 return std::fabs(target - position) <= tolerance;
507 uint16_t crc = 0x0000;
508 for (uint8_t i = 0; i < len; i++) {
517 memset(&f, 0,
sizeof(
IoFrame));
531bool set_cmd(
IoFrame &f, uint8_t cmd,
const uint8_t *params, uint8_t params_len) {
536 if (params !=
nullptr && params_len > 0)
537 memcpy(f.
data, params, params_len);
566 buf[offset++] = f.
ctrl0;
567 buf[offset++] = f.
ctrl1;
572 buf[offset++] = f.
cmd;
583 memset(&f, 0,
sizeof(
IoFrame));
585 f.
ctrl0 = buf[offset++];
586 f.
ctrl1 = buf[offset++];
600 if (offset >= buf_len)
602 f.
cmd = buf[offset++];
617 return "venetian_blind";
619 return "roller_shutter";
625 return "window_opener";
627 return "garage_opener";
631 return "gate_opener";
633 return "rolling_door_opener";
637 return "dual_shutter";
639 return "on_off_switch";
641 return "horizontal_awning";
643 return "external_venetian_blind";
645 return "louvre_blind";
647 return "curtain_track";
649 return "swinging_shutter";
655 return "heating_temperature_interface";
657 return "ventilation_point";
659 return "exterior_heating";
663 return "intrusion_alarm";
770 return "binary_on_off";
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.
static constexpr float UNKNOWN_POSITION
Sentinel value meaning "position is not known yet".
static constexpr uint8_t NODE_ID_SIZE
Device/node addresses are 3 bytes (e.g., "123ABC").
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 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).
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 RESULT_AUTOMATIC_CYCLE_ENGAGED
Node entered automatic cycle mode.
static constexpr uint8_t DEVICE_TYPE_HIGH_BITS_SHIFT
DeviceType
Device type identifiers reported by IO‑Homecontrol products.
@ LOUVRE_BLIND
Louvre blind.
@ GATE_OPENER
Gate opener.
@ 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.
@ INTRUSION_ALARM
Intrusion alarm.
@ WINDOW_OPENER
Window opening actuator.
@ SWINGING_SHUTTER
Swinging shutter.
@ HORIZONTAL_AWNING
Horizontal awning (open/close inverted).
@ ROLLING_DOOR_OPENER
Rolling door opener.
@ ON_OFF_SWITCH
Generic on/off switch.
@ SCREEN
Insect/privacy screen.
@ EXTERIOR_HEATING
Exterior heating.
@ VENTILATION_POINT
Ventilation point.
@ VENETIAN_BLIND
Venetian blind.
@ ROLLER_SHUTTER
Roller shutter.
@ CURTAIN_TRACK
Curtain track.
@ GARAGE_OPENER
Garage door opener.
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.
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.
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.
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.
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 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 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 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).
static constexpr uint8_t DEVICE_NAME_WRITE_PAYLOAD_SIZE
Fixed write payload: 15 visible chars plus trailing null/padding.
static constexpr uint8_t RESULT_PARAMETER_LIMITED
Parameter limited by an unknown 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.
bool is_end(const IoFrame &f)
Check END flag.
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.
@ NONE
Name is valid and encodable.
@ 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.
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.
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 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.
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.
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.
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 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.
bool device_supports_status_requests(DeviceType type)
Does this device type support status request commands (0x03)?
static constexpr uint8_t RESULT_LIMITATION_BY_SAAC
Parameter limited by a standalone automatic controller.
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?
DeviceCapabilityClass
High‑level capability class derived from DeviceType.
@ CLIMATE
Climate device (heating/cooling).
@ SWITCH
Binary on/off switch.
@ UNKNOWN
Unknown capability.
@ COVER
Position‑controlled cover (shutter/blind/awning).
@ LIGHT
Binary on/off light.
static constexpr uint8_t RESULT_PRIORITY_LEVEL_LOCKED
Node is locked on this priority level.
static constexpr uint8_t DEVICE_TYPE_LOW_BITS_SHIFT
bool is_limitation_result(uint8_t result)
Check whether a result code represents an environmental or control limitation.
static constexpr uint8_t RESULT_UNKNOWN_STATUS_REPLY
Device returned an unknown status reply.
static int hex_nibble(char ch)
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 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 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 uint8_t RESULT_LIMITATION_BY_LOCAL_USER
Parameter limited by local button.
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 uint8_t RESULT_LIMITATION_BY_SCD
Parameter limited by a security actuator.
IO-Homecontrol 2W protocol definitions.
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.