Home IO Control
ESPHome add-on for IO-Homecontrol devices
Loading...
Searching...
No Matches
proto_codecs.h
Go to the documentation of this file.
1#pragma once
2
3/// @file proto_codecs.h
4/// @brief Device-name, address-classification and 1W-frame codecs.
5/// @ingroup hioc_protocol
6///
7/// Higher-level decode/encode helpers layered on the frame and device model:
8/// Latin-1 device-name round-tripping, broadcast address classification and
9/// structured decoding of overheard 1W remote frames.
10
11#include "proto_device_model.h"
12#include "proto_sizes.h"
13
14#include <cstddef>
15#include <cstdint>
16#include <optional>
17#include <string>
18
19namespace esphome {
20namespace home_io_control {
21
22struct IoFrame; // Defined in proto_frame.h; decode_1w_frame takes it by reference.
23
24// ============================================================================
25// Device Name Codec
26// ============================================================================
27
28static constexpr uint8_t DEVICE_NAME_WRITE_CHAR_LIMIT = 15; ///< Reference write limit before the trailing null.
29static constexpr uint8_t DEVICE_NAME_WRITE_PAYLOAD_SIZE =
30 DEVICE_NAME_WRITE_CHAR_LIMIT + 1; ///< Fixed write payload: 15 visible chars plus trailing null/padding.
31static constexpr uint16_t LATIN1_CODEPOINT_MAX = 0x00FF; ///< Highest Unicode code point representable in Latin-1.
32
33/// @brief Validation result for outbound device-name writes.
34enum class DeviceNameValidationError : uint8_t {
35 NONE = 0x00, ///< Name is valid and encodable.
36 EMPTY = 0x01, ///< Name is empty after normalization.
37 TOO_LONG = 0x02, ///< Name exceeds the 15-character write limit.
38 INVALID_UTF8 = 0x03, ///< Name contains malformed UTF-8 bytes.
39 UNSUPPORTED_CHAR = 0x04, ///< Name contains characters outside Latin-1.
40};
41
42/// @brief Decode a device-name payload from IO-homecontrol's Latin-1 wire format into UTF-8.
43/// Some devices prepend an extra byte before the first character and many pad the payload with
44/// trailing 0x00 or 0x20 bytes. This helper normalizes those quirks and truncates the result to
45/// fit DEVICE_NAME_BUFFER_SIZE - 1 bytes when copied into IoDevice::name.
46/// @param data Raw payload pointer from CMD_GET_NAME_RESP.
47/// @param len Raw payload length in bytes.
48/// @return Normalized UTF-8 name, or an empty string when the payload carries no usable characters.
49std::string decode_device_name_payload(const uint8_t *data, uint8_t len);
50/// @brief Trim leading and trailing ASCII whitespace from a string.
51/// This is shared by device-name validation and management actions so both paths normalize input
52/// consistently before comparing or writing device metadata.
53/// @param value Input string.
54/// @return Copy of the string with leading and trailing ASCII whitespace removed.
55std::string trim_ascii_whitespace(const std::string &value);
56/// @brief Validate and encode a user-supplied UTF-8 device name into the fixed Latin-1 write payload.
57/// The outbound payload is a fixed 16-byte field: up to 15 Latin-1 characters followed by trailing
58/// zero padding. Leading and trailing ASCII whitespace are trimmed before validation so write-back
59/// verification against the device's padded storage is deterministic.
60/// @param name User-supplied UTF-8 device name.
61/// @param payload Output buffer for the fixed write payload (16 bytes, zero-padded on success).
62/// @param normalized_name Output normalized UTF-8 name used for later verification/logging.
63/// @return Validation status indicating success or the reason the name cannot be written.
65 uint8_t payload[DEVICE_NAME_WRITE_PAYLOAD_SIZE],
66 std::string &normalized_name);
67/// @brief Return a stable symbolic name for a device-name validation result.
68/// @param error Validation result.
69/// @return Uppercase symbolic name.
71/// @brief Return a human-readable explanation for a device-name validation result.
72/// @param error Validation result.
73/// @return Short message suitable for logs and Home Assistant action events.
75
76// ============================================================================
77// Address Classification
78// ============================================================================
79
80/// @brief Well-known address suffix values in the broadcast address space.
81///
82/// When the first byte of a 3-byte IO-Homecontrol address is 0x00, the low 6 bits
83/// of the third byte carry a suffix that identifies the broadcast category.
84/// @{
85static constexpr uint8_t ADDRESS_SUFFIX_MASK = 0x3F; ///< Mask to extract the 6-bit suffix from addr[2].
86static constexpr uint8_t ADDRESS_SUFFIX_BROADCAST = 0x3F; ///< Suffix for "all devices of this type" broadcast.
87static constexpr uint8_t ADDRESS_SUFFIX_DISCOVERY = 0x3B; ///< Suffix for discovery-related broadcasts.
88/// @}
89
90/// @brief Address classification categories for diagnostic purposes.
91///
92/// IO-Homecontrol uses the target address field to encode both unicast
93/// device addresses and broadcast targets. The first byte being 0x00
94/// indicates a broadcast; the remaining bytes encode the target device type.
95enum class AddressClass : uint8_t {
96 UNICAST = 0, ///< Normal device-to-device unicast address (first byte != 0x00).
97 BROADCAST_ALL, ///< Broadcast to all devices of a type (address suffix 0x3F).
98 BROADCAST_TYPE, ///< Broadcast to specific device type with non-standard suffix.
99 DISCOVERY, ///< Discovery-related broadcast (address suffix 0x3B).
100 UNKNOWN_BROADCAST, ///< Broadcast pattern that does not match known suffixes.
101};
102
103/// @brief Classify an IO-Homecontrol 3-byte address.
104///
105/// Determines whether an address is unicast, a typed broadcast, or a
106/// discovery-related broadcast based on protocol addressing rules.
107/// @param addr Three-byte node address to classify.
108/// @return Classification indicating the address type.
109AddressClass classify_address(const uint8_t addr[NODE_ID_SIZE]);
110
111/// @brief Get a human-readable name for an address classification.
112/// @param address_class Classification returned by classify_address().
113/// @return Null-terminated string such as "broadcast_all" or "unicast".
114const char *address_class_name(AddressClass address_class);
115
116/// @brief Extract the target device type from a typed broadcast address.
117///
118/// Broadcast addresses encode the device type in bits [9:2] of the combined
119/// address bytes 1–2. This function extracts that type. Returns UNKNOWN
120/// if the address is not a broadcast (first byte != 0x00).
121/// @param addr Three-byte broadcast address.
122/// @return DeviceType encoded in the address, or DeviceType::UNKNOWN for unicast.
124
125/// @brief Encode a device type into its typed-broadcast destination address — the exact inverse
126/// of broadcast_target_type(), kept immediately beside it so the pair cannot drift apart.
127///
128/// The class occupies bits [9:2] and therefore **spans two bytes**: `out[0]` is always 0,
129/// `out[1] = raw >> DEVICE_TYPE_LOW_BITS_SHIFT` carries the high bits, and
130/// `out[2] = (raw << DEVICE_TYPE_HIGH_BITS_SHIFT) | DEVICE_SUBTYPE_MASK` carries the low bits
131/// plus the all-ones subtype field that makes the address a broadcast ("any subtype of this
132/// class"). A single-byte encoding looks right for classes 0–3 and silently produces the wrong
133/// class from 4 upward — e.g. the light class (0x06) encodes to `00 01 BF`, not `00 00 BF`.
134/// This is the addressing primitive 1W execute frames use: 1W commands a device *class*, never
135/// an individual node, so this is the only destination a 1W builder ever needs.
136/// @param type Device class to address.
137/// @param out Output: 3-byte typed-broadcast destination address.
138void encode_broadcast_address(DeviceType type, uint8_t out[NODE_ID_SIZE]);
139
140// ============================================================================
141// 1W Remote Frame Decode
142// ============================================================================
143
144/// @brief Decode the "main" position/command bytes from a 1W execute payload.
145///
146/// 1W remotes encode their command intent in a 2-byte main field:
147/// - main[0]: position (0–200 mapped to 0–100%), or a special command code.
148/// - main[1]: modifier byte (0x03 = ventilation for POS_FAVORITE).
149///
150/// @param main0 First main byte (position or special code).
151/// @param main1 Second main byte (modifier).
152/// @param out Buffer to write the decoded string into (e.g., "CLOSE", "position 75%").
153/// @param out_size Size of the output buffer.
154void decode_1w_main_intent(uint8_t main0, uint8_t main1, char *out, size_t out_size);
155
156/// @brief Resolve a 1W main-byte pair to an optimistic IO target position, if unambiguous.
157///
158/// Shares decode_1w_main_intent()'s special-code checks so the two never disagree. Returns
159/// empty for POS_STOP (the caller must clear any optimistic target instead — stop is not a
160/// target) and for codes with no settled position (FAVORITE/VENT/FORCE_OPEN/SECURED_TARGET/
161/// DEFAULT/UNKNOWN) — those cases still get a confirmation poll, just no optimistic claim.
162/// @param main0 First main byte (position or special code).
163/// @param main1 Second main byte (modifier); unused by every branch that resolves a target.
164/// @return IO target position (0=open, 100=closed) if resolvable; empty otherwise.
165std::optional<float> oneway_intent_to_target(uint8_t main0, uint8_t main1);
166
167/// @brief Buffer size for the decoded 1W main-intent string.
168static constexpr size_t ONEWAY_INTENT_BUFFER_SIZE = 24;
169
170/// @brief Decoded representation of a 1W remote frame.
171///
172/// Captures all fields extractable from a 1W broadcast frame in a structured form
173/// that can be used for logging, events, or future sensor exposure.
175 uint8_t src[NODE_ID_SIZE]{}; ///< Remote source node ID (3 bytes).
176 AddressClass address_class{AddressClass::UNKNOWN_BROADCAST}; ///< Classification of the broadcast address.
177 DeviceType target_type{DeviceType::UNKNOWN}; ///< Target device class from broadcast address.
178 uint8_t cmd{0}; ///< Command ID (e.g., CMD_EXECUTE, CMD_ACTIVATE_MODE).
179 bool has_intent{false}; ///< True if originator/ACEI/intent fields were decoded.
180 uint8_t originator{0}; ///< Command originator byte (e.g., ORIGINATOR_USER_REMOTE).
181 uint8_t acei_level{0}; ///< ACEI priority level (0–7).
182 char intent[ONEWAY_INTENT_BUFFER_SIZE]{}; ///< Human-readable command intent (e.g., "CLOSE").
183 uint8_t main0{0}; ///< Raw first main byte (has_intent only); feeds oneway_intent_to_target().
184 uint8_t main1{0}; ///< Raw second main byte (has_intent only); feeds oneway_intent_to_target().
185 uint8_t data_len{0}; ///< Raw data length (for commands without decoded intent).
186};
187
188/// @brief Decode a parsed 1W frame into a structured OneWayFrameInfo.
189///
190/// Extracts target device type from the broadcast address. For execute/activate-mode
191/// commands, also decodes originator, ACEI priority, and position/command intent.
192/// @param frame Parsed IoFrame with CTRL0_PROTOCOL_1W set.
193/// @return Populated OneWayFrameInfo.
194OneWayFrameInfo decode_1w_frame(const IoFrame &frame);
195
196// ============================================================================
197// Discovery Response Decode
198// ============================================================================
199
200/// @brief Extended discovery-response fields (manufacturer, Multi Information Byte, backbone
201/// address, timestamp) plus flags recording how much of the payload was actually present.
203 bool metadata_complete{false}; ///< data_len >= DEVICE_METADATA_SIZE (type/subtype present).
204 bool has_extended{false}; ///< data_len >= DISCOVERY_RESP_FULL_SIZE (mfr/flags/timestamp present).
205 uint8_t manufacturer{0}; ///< Raw manufacturer ID; name via manufacturer_name().
206 uint8_t flags{0}; ///< Multi Information Byte; decode with DISCOVERY_FLAGS_* masks.
207 uint8_t backbone[NODE_ID_SIZE]{}; ///< Backbone address as reported by the device.
208 uint16_t timestamp{0}; ///< Device timestamp field (advances between replies).
209};
210
211/// @brief Decode a discovery-response payload (CMD_DISCOVER_RESP 0x29 or CMD_DISCOVER_SPE_RESP 0x2B —
212/// both carry the identical DISCOVERY_RESP_FULL_SIZE layout) into device metadata.
213///
214/// Pure: no logging, no side effects. Extended fields (manufacturer, flags, timestamp) are
215/// returned rather than logged so each caller can present them its own way. Sets
216/// `device.node_id`/`type`/`subtype`/`inverted`/`position`/`target`/`is_stopped` and
217/// `device_id` exactly as a discovery reply implies: type/subtype and inversion come from the
218/// packed metadata bytes when present, position/target default to the unknown sentinel, and
219/// the device is assumed stopped. Every field read is guarded on `frame.data_len`, so a short
220/// or truncated payload degrades gracefully instead of reading past the end.
221/// @param frame Parsed discovery-response frame.
222/// @param device Output: device record populated from the frame.
223/// @param device_id Output: hex device ID string derived from `frame.src`.
224/// @return Extended discovery fields (manufacturer/flags/timestamp) and length flags.
225DiscoveryResponseInfo decode_discovery_response(const IoFrame &frame, IoDevice &device, std::string &device_id);
226
227// ============================================================================
228// 1W Add-Controller Key Adoption (CMD 0x30)
229// ============================================================================
230
231/// @brief Outcome of checking the out-of-length MAC trailer (`IoFrame::has_mac`) on a decoded
232/// CMD_ONEWAY_ADD_CONTROLLER frame.
233///
234/// Kept distinct from a bare bool because a caller-facing report needs to tell these three
235/// situations apart: no MAC was ever present to check (the reference implementation's own
236/// `_p0x30` struct omits the MAC field entirely, so this is a normal, non-error outcome — not
237/// every 0x30 on the wire carries one), a MAC was present and matched, or a MAC was present and
238/// did not match (evidence the decrypted key is wrong, or the frame was corrupted/forged).
239enum class OneWayMacStatus : uint8_t {
240 NOT_PRESENT = 0, ///< `frame.has_mac` was false; nothing to verify.
241 VERIFIED = 1, ///< `frame.has_mac` was true and the MAC verified under the recovered key.
242 FAILED = 2, ///< `frame.has_mac` was true and the MAC did NOT verify under the recovered key.
243};
244
245/// @brief Recovered controller identity from a decoded CMD_ONEWAY_ADD_CONTROLLER (0x30) frame.
246///
247/// Fixed-size, no heap, no `std::string`, no vectors. Deliberately carries no logging or
248/// `to_string()` helper: `system_key` is a real 1W installation's network key, and the surest
249/// way to leak a key is to make it convenient to print. Route `system_key` through the single
250/// intentional user-facing "adopted key" emission (a later step) and nowhere else — see
251/// `crypto::crypt_1w_key()`'s `@warning` in proto_crypto.h.
252/// @warning Carries real key material (`system_key`). Never log, print, or otherwise let this
253/// struct's contents reach any path other than the one intentional emission.
255 uint8_t system_key[AES_KEY_SIZE]{}; ///< Recovered network system key (crypto::crypt_1w_key() output).
256 uint8_t manufacturer{0}; ///< Manufacturer ID byte from the payload (`man_id`).
257 uint8_t sender_node[NODE_ID_SIZE]{}; ///< Sender's node address (`frame.src`) — the new identity's node.
258 uint16_t sequence{0}; ///< 2-byte rolling sequence from the payload (big-endian on wire).
259 OneWayMacStatus mac_status{OneWayMacStatus::NOT_PRESENT}; ///< MAC-verification outcome; see OneWayMacStatus.
260};
261
262/// @brief Decoding outcome for decode_1w_add_controller().
263enum class OneWayAddControllerDecodeError : uint8_t {
264 NONE = 0, ///< Decode succeeded; `out` is populated.
265 NOT_ONEWAY = 1, ///< `CTRL0_PROTOCOL_1W` is not set — not a 1W frame at all.
266 WRONG_COMMAND = 2, ///< `frame.cmd` is not CMD_ONEWAY_ADD_CONTROLLER.
267 BAD_LENGTH = 3, ///< Declared payload is not exactly the expected 20 bytes.
268 KEY_UNWRAP_FAILED = 4, ///< crypto::crypt_1w_key() itself reported failure.
269};
270
271/// @brief Decode a CMD_ONEWAY_ADD_CONTROLLER (0x30) frame into a recovered controller identity.
272///
273/// A 1W device broadcasts this frame while its key-copy gesture is active, handing its network's
274/// wrapped system key to whichever controller is listening (see CMD_ONEWAY_ADD_CONTROLLER's
275/// Doxygen in proto_constants.h). Three facts a future reader cannot re-derive from the code
276/// alone:
277/// - The AES key that wraps the payload is the public TRANSFER_KEY (proto_constants.h), not
278/// the recovered system key — the system key is the *output* of unwrapping, not an input.
279/// - The unwrap IV is derived only from the sender's own node address (`frame.src`), which is
280/// plaintext in the frame header — no secret or challenge is needed to begin unwrapping.
281/// - The wrap is self-inverse (crypto::crypt_1w_key()'s XOR-with-AES-keystream construction),
282/// so the same call that would encrypt a plaintext key for transmission also decrypts an
283/// overheard ciphertext; there is no direction flag to get wrong.
284///
285/// Validates before decrypting rather than decrypting garbage: the 1W protocol bit must be set,
286/// the command must be CMD_ONEWAY_ADD_CONTROLLER, and the declared payload must be exactly the
287/// expected 20 bytes (`enc_key[16] + man_id[1] + data[1] + sequence[2]`) — any mismatch is
288/// rejected before `crypto::crypt_1w_key()` is ever called.
289///
290/// When `frame.has_mac` is true (the out-of-length MAC trailer modeled by `IoFrame::mac`, see
291/// proto_frame.h), the MAC is verified under the *recovered* key with span `cmd + enc_key` (17
292/// bytes) — the only span CMD_ONEWAY_ADD_CONTROLLER authenticates (see
293/// `crypto::create_1w_hmac()`'s `@warning`). A verifying MAC is strong evidence the decryption
294/// recovered the correct key: it is the decisive self-check available at adoption time, before
295/// any device is ever commanded. A frame with no MAC is not an error — the reference
296/// implementation's own `_p0x30` struct omits the MAC field entirely — so decoding still
297/// succeeds and `out.mac_status` simply reports `NOT_PRESENT`.
298///
299/// Pure function of the frame: no logging, no timers, no I/O (ADR 0005). Never transmits.
300/// @param frame Parsed IoFrame, expected to be a CMD_ONEWAY_ADD_CONTROLLER 1W frame.
301/// @param out Output: recovered controller identity. Only meaningfully populated when the return
302/// value is NONE; left default-constructed (zeroed) otherwise.
303/// @return NONE on success; otherwise the specific validation/decode failure.
304/// @warning `out` carries real key material once populated. See OneWayAdoptedKey's warning.
305OneWayAddControllerDecodeError decode_1w_add_controller(const IoFrame &frame, OneWayAdoptedKey &out);
306
307} // namespace home_io_control
308} // namespace esphome
static constexpr uint8_t NODE_ID_SIZE
Device/node addresses are 3 bytes (e.g., "123ABC").
Definition proto_sizes.h:20
OneWayMacStatus
Outcome of checking the out-of-length MAC trailer (IoFrame::has_mac) on a decoded CMD_ONEWAY_ADD_CONT...
@ VERIFIED
frame.has_mac was true and the MAC verified under the recovered key.
@ NOT_PRESENT
frame.has_mac was false; nothing to verify.
DeviceType
Device type identifiers reported by IO‑Homecontrol products.
@ UNKNOWN
Unknown/unspecified device.
void encode_broadcast_address(DeviceType type, uint8_t out[NODE_ID_SIZE])
Encode a device type into its typed-broadcast destination address — the exact inverse of broadcast_ta...
OneWayAddControllerDecodeError decode_1w_add_controller(const IoFrame &frame, OneWayAdoptedKey &out)
Decode a CMD_ONEWAY_ADD_CONTROLLER (0x30) frame into a recovered controller identity.
static constexpr uint16_t LATIN1_CODEPOINT_MAX
Highest Unicode code point representable in Latin-1.
static constexpr uint8_t ADDRESS_SUFFIX_BROADCAST
Suffix for "all devices of this type" broadcast.
std::optional< float > oneway_intent_to_target(uint8_t main0, uint8_t main1)
Resolve a 1W main-byte pair to an optimistic IO target position, if unambiguous.
static constexpr uint8_t ADDRESS_SUFFIX_DISCOVERY
Suffix for discovery-related broadcasts.
OneWayAddControllerDecodeError
Decoding outcome for decode_1w_add_controller().
@ WRONG_COMMAND
frame.cmd is not CMD_ONEWAY_ADD_CONTROLLER.
@ NOT_ONEWAY
CTRL0_PROTOCOL_1W is not set — not a 1W frame at all.
@ BAD_LENGTH
Declared payload is not exactly the expected 20 bytes.
@ KEY_UNWRAP_FAILED
crypto::crypt_1w_key() itself reported failure.
static constexpr uint8_t ADDRESS_SUFFIX_MASK
Well-known address suffix values in the broadcast address space.
OneWayFrameInfo decode_1w_frame(const IoFrame &frame)
Decode a parsed 1W frame into a structured OneWayFrameInfo.
static constexpr uint8_t DEVICE_NAME_WRITE_PAYLOAD_SIZE
Fixed write payload: 15 visible chars plus trailing null/padding.
const char * device_name_validation_error_name(DeviceNameValidationError error)
Return a stable symbolic name for a device-name validation result.
@ FAILED
No usable reply; the device may never have heard the request.
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.
std::string trim_ascii_whitespace(const std::string &value)
Trim leading and trailing ASCII whitespace from a string.
static constexpr uint8_t DEVICE_NAME_WRITE_CHAR_LIMIT
Reference write limit before the trailing null.
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.
AddressClass classify_address(const uint8_t addr[NODE_ID_SIZE])
Classify an IO-Homecontrol 3-byte address.
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.
DeviceType broadcast_target_type(const uint8_t addr[NODE_ID_SIZE])
Extract the target device type from a typed broadcast address.
AddressClass
Address classification categories for diagnostic purposes.
@ UNKNOWN_BROADCAST
Broadcast pattern that does not match known suffixes.
@ UNICAST
Normal device-to-device unicast address (first byte != 0x00).
@ BROADCAST_TYPE
Broadcast to specific device type with non-standard suffix.
@ BROADCAST_ALL
Broadcast to all devices of a type (address suffix 0x3F).
@ DISCOVERY
Discovery-related broadcast (address suffix 0x3B).
const char * address_class_name(AddressClass address_class)
Get a human-readable name for an address classification.
static constexpr size_t ONEWAY_INTENT_BUFFER_SIZE
Buffer size for the decoded 1W main-intent string.
static constexpr uint8_t AES_KEY_SIZE
AES-128 key size.
Definition proto_sizes.h:23
DiscoveryResponseInfo decode_discovery_response(const IoFrame &frame, IoDevice &device, std::string &device_id)
Decode a discovery-response payload (CMD_DISCOVER_RESP 0x29 or CMD_DISCOVER_SPE_RESP 0x2B — both carr...
const char * device_name_validation_error_description(DeviceNameValidationError error)
Return a human-readable explanation for a device-name validation result.
@ NONE
target_fw is unknown, or its required bootloader matches bootloader_version.
void decode_1w_main_intent(uint8_t main0, uint8_t main1, char *out, size_t out_size)
Decode the "main" position/command bytes from a 1W execute payload.
IO-Homecontrol device-type model, capabilities and runtime device state.
Fundamental IO-Homecontrol frame and crypto size constants.
Extended discovery-response fields (manufacturer, Multi Information Byte, backbone address,...
bool has_extended
data_len >= DISCOVERY_RESP_FULL_SIZE (mfr/flags/timestamp present).
uint8_t backbone[NODE_ID_SIZE]
Backbone address as reported by the device.
bool metadata_complete
data_len >= DEVICE_METADATA_SIZE (type/subtype present).
uint8_t manufacturer
Raw manufacturer ID; name via manufacturer_name().
uint8_t flags
Multi Information Byte; decode with DISCOVERY_FLAGS_* masks.
uint16_t timestamp
Device timestamp field (advances between replies).
Parsed IO‑Homecontrol frame (CTRL0/1 + addresses + command + data).
Definition proto_frame.h:88
Recovered controller identity from a decoded CMD_ONEWAY_ADD_CONTROLLER (0x30) frame.
uint8_t manufacturer
Manufacturer ID byte from the payload (man_id).
OneWayMacStatus mac_status
MAC-verification outcome; see OneWayMacStatus.
uint8_t system_key[AES_KEY_SIZE]
Recovered network system key (crypto::crypt_1w_key() output).
uint8_t sender_node[NODE_ID_SIZE]
Sender's node address (frame.src) — the new identity's node.
uint16_t sequence
2-byte rolling sequence from the payload (big-endian on wire).
Decoded representation of a 1W remote frame.
bool has_intent
True if originator/ACEI/intent fields were decoded.
uint8_t originator
Command originator byte (e.g., ORIGINATOR_USER_REMOTE).
DeviceType target_type
Target device class from broadcast address.
uint8_t main0
Raw first main byte (has_intent only); feeds oneway_intent_to_target().
uint8_t acei_level
ACEI priority level (0–7).
uint8_t main1
Raw second main byte (has_intent only); feeds oneway_intent_to_target().
uint8_t cmd
Command ID (e.g., CMD_EXECUTE, CMD_ACTIVATE_MODE).
AddressClass address_class
Classification of the broadcast address.
char intent[ONEWAY_INTENT_BUFFER_SIZE]
Human-readable command intent (e.g., "CLOSE").
uint8_t src[NODE_ID_SIZE]
Remote source node ID (3 bytes).
uint8_t data_len
Raw data length (for commands without decoded intent).