|
Home IO Control
ESPHome add-on for IO-Homecontrol devices
|
Classes | |
| struct | OneWayDedupState |
| Key fields of the last processed 1W frame, used to collapse a remote's repeat burst. More... | |
Enumerations | |
| enum class | ExchangeFirstResponseDisposition : uint8_t { IGNORE_UNRELATED , COMPLETE_DIRECT , REQUIRE_AUTH } |
| Disposition for the first response in an authenticated exchange. More... | |
| enum class | ExchangeFinalResponseDisposition : uint8_t { IGNORE_UNRELATED , ACCEPT } |
| Disposition for the final response after authentication. More... | |
| enum class | PairingDiscoveryDisposition : uint8_t { NO_RESPONSE , INVALID , ACCEPT } |
| Disposition during pairing discovery phase. More... | |
| enum class | PairingKeyChallengeDisposition : uint8_t { IGNORE , ACCEPT } |
| Disposition during pairing key-challenge phase. More... | |
Functions | |
| bool | is_exchange_internal_command (uint8_t cmd) |
| Returns true for commands that are internal to an exchange handshake and carry no useful information for a passive observer (challenge request/response). | |
| bool | frame_matches_nodes (const IoFrame &frame, const uint8_t expected_src[NODE_ID_SIZE], const uint8_t expected_dst[NODE_ID_SIZE]) |
| Check if two frames have identical src/dst node IDs. | |
| bool | frame_matches_exchange_endpoints (const IoFrame &request, const IoFrame &candidate) |
| Check if candidate frame endpoints are the reverse of the request (dst==request.src, src==request.dst). | |
| ExchangeFirstResponseDisposition | classify_exchange_first_response (const IoFrame &request, const IoFrame &candidate) |
| Decide how to handle the first response packet in an authenticated exchange. | |
| ExchangeFinalResponseDisposition | classify_exchange_final_response (const IoFrame &request, const IoFrame &candidate) |
| Decide if a candidate frame is an acceptable final response after authentication. | |
| bool | retry_after_unconfirmed_accept_is_safe (uint8_t cmd) |
| Whether an authenticated-but-unanswered request may be sent again. | |
| PairingDiscoveryDisposition | classify_pairing_discovery_response (const IoFrame &candidate, const uint8_t controller_id[NODE_ID_SIZE]) |
| Decide if a frame is a valid discovery response (0x29) during pairing. | |
| PairingKeyChallengeDisposition | classify_pairing_key_challenge (const IoFrame &candidate, const uint8_t device_id[NODE_ID_SIZE], const uint8_t controller_id[NODE_ID_SIZE]) |
| Decide if a frame is a valid key-challenge (0x3C) during pairing key exchange. | |
| bool | is_duplicate_1w_frame (const OneWayDedupState &last, const OneWayDedupState &incoming, uint32_t window_ms) |
| Decide whether an incoming 1W frame repeats the previous one inside the burst window. | |
| bool | defer_background_poll_for_1w_activity (bool next_op_is_background, uint32_t first_1w_activity_ms, uint32_t last_1w_activity_ms, uint32_t now, uint32_t quiet_ms, uint32_t max_defer_ms) |
| Decide whether to hold back a queued background poll because a 1W remote is still transmitting. | |
| uint8_t | scheduled_poll_max_tries (uint8_t status_poll_failures, uint8_t auth_poll_failures) |
| Transmit-attempt budget for a scheduler-owned status poll, by backoff-ladder position. | |
| bool | is_one_way_pairing_gesture (bool oneway, const uint8_t dst[NODE_ID_SIZE], uint8_t cmd) |
| True if a frame's shape matches a 1W remote's pairing gesture (issue #27/#65): CTRL0 1W bit set, addressed to the 1W broadcast address (0x00003F), with one of the three command bytes observed in the field capture — 0x20 (WRITE_PRIVATE), 0x39 (1W remove), or 0x2E (alternate discovery, 1W-flagged). | |
| bool | oneway_burst_started_fresh (uint32_t last_1w_activity_ms, uint32_t now, uint32_t quiet_ms) |
| Whether a 1W frame arriving at now starts a new burst rather than extending the current one — true if no frame has been seen yet, or the gap since the last one reached quiet_ms (the previous burst already released any deferred poll). | |
|
strong |
Disposition for the final response after authentication.
| Enumerator | |
|---|---|
| IGNORE_UNRELATED | Frame doesn't match endpoints — ignore. |
| ACCEPT | Frame matches expected response — exchange succeeds. |
Definition at line 31 of file hub_decisions.h.
|
strong |
Disposition for the first response in an authenticated exchange.
Definition at line 24 of file hub_decisions.h.
|
strong |
Disposition during pairing discovery phase.
| Enumerator | |
|---|---|
| NO_RESPONSE | No packets received on the channel within timeout. |
| INVALID | Packets seen but none were valid discovery (0x29) frames. |
| ACCEPT | Valid discovery response received. |
Definition at line 37 of file hub_decisions.h.
|
strong |
Disposition during pairing key-challenge phase.
| Enumerator | |
|---|---|
| IGNORE | Not a valid challenge (wrong cmd, length, or sender). |
| ACCEPT | Valid 0x3C challenge from target device. |
Definition at line 44 of file hub_decisions.h.
|
inline |
Decide if a candidate frame is an acceptable final response after authentication.
Only endpoint matching is checked here; command validity is encoded in the disposition mapping by the caller.
| request | Original outbound request frame. |
| candidate | Parsed IoFrame from the device. |
Definition at line 102 of file hub_decisions.h.
|
inline |
Decide how to handle the first response packet in an authenticated exchange.
Used by wait_for_first_response_() to determine whether the exchange:
| request | Original outbound request frame. |
| candidate | Parsed IoFrame from the device. |
Definition at line 83 of file hub_decisions.h.
|
inline |
Decide if a frame is a valid discovery response (0x29) during pairing.
Only the destination is checked, not the source: the discovery request goes out to a shared broadcast address, so a response arriving during the same window may be a device answering a different controller's concurrent discovery rather than ours — real hardware addresses its response back to the requesting controller's own node ID, so checking that is both possible and sufficient to reject it. The source can't be checked here — the device's node ID is exactly what discovery exists to learn, so there is nothing yet to compare it to.
| candidate | Parsed IoFrame. |
| controller_id | Node ID of this controller (expected destination). |
Definition at line 132 of file hub_decisions.h.
|
inline |
Decide if a frame is a valid key-challenge (0x3C) during pairing key exchange.
The challenge must:
| candidate | Parsed IoFrame. |
| device_id | Node ID of the device being paired (expected sender). |
| controller_id | Node ID of this controller (expected destination). |
Definition at line 151 of file hub_decisions.h.
|
inline |
Decide whether to hold back a queued background poll because a 1W remote is still transmitting.
The radio is half-duplex and an authenticated exchange blocks for 1–3 s, during which no frame can be received at all. A press on a linked remote schedules a status poll, so without this gate the hub's own poll can start on top of the burst that triggered it and go deaf to the rest of it.
Only background polls are deferred. A user command must never wait on a remote the user may not even own — 1W broadcasts carry no ownership marker, so the activity could be a neighbour's.
The hold re-arms on every 1W frame received while it is already active, so a real burst from one remote (~160 ms, well under quiet_ms) never gets cut short mid-transmission. Left unchecked that re-arming has no cap: sustained sub-quiet_ms 1W traffic from any source — including a neighbour's, since these broadcasts carry no ownership marker — would hold background polls back indefinitely. max_defer_ms bounds that: once that much time has passed since the burst started (not the most recent frame), the poll is let through regardless of ongoing traffic. The gate only ever delays a poll, never drops one — it stays queued and fires as soon as it is no longer deferred.
| next_op_is_background | True if the queue front is a REQUEST_STATUS / REQUEST_NAME. |
| first_1w_activity_ms | millis() of the first frame in the current 1W burst; 0 if none seen since boot. |
| last_1w_activity_ms | millis() of the most recent 1W frame; 0 if none seen since boot. |
| now | Current millis(). |
| quiet_ms | How long after 1W activity to hold background polls back. |
| max_defer_ms | Hard cap on total defer time, measured from first_1w_activity_ms. |
Definition at line 224 of file hub_decisions.h.
|
inline |
Check if candidate frame endpoints are the reverse of the request (dst==request.src, src==request.dst).
Definition at line 67 of file hub_decisions.h.
|
inline |
Check if two frames have identical src/dst node IDs.
Definition at line 60 of file hub_decisions.h.
|
inline |
Decide whether an incoming 1W frame repeats the previous one inside the burst window.
| last | State recorded for the previously processed 1W frame. |
| incoming | Candidate frame's key fields, with timestamp set to now. |
| window_ms | Burst-suppression window. |
Definition at line 188 of file hub_decisions.h.
|
inline |
Returns true for commands that are internal to an exchange handshake and carry no useful information for a passive observer (challenge request/response).
These frames appear in every authenticated exchange between other controllers and devices on the network, but contain only ephemeral cryptographic data.
Definition at line 55 of file hub_decisions.h.
|
inline |
True if a frame's shape matches a 1W remote's pairing gesture (issue #27/#65): CTRL0 1W bit set, addressed to the 1W broadcast address (0x00003F), with one of the three command bytes observed in the field capture — 0x20 (WRITE_PRIVATE), 0x39 (1W remove), or 0x2E (alternate discovery, 1W-flagged).
Shared between PairingAdvisor (classifying recorded telemetry events, pairing_advisor.cpp) and the hub's normal passive RX path (hub_status.cpp), which remembers a recent sighting so a PROG press completed just before "Discover & Pair" is pressed isn't invisible to the advisor purely because of when the discovery telemetry window happened to open — see PairingTelemetry::record_recent_one_way_sighting().
| oneway | CTRL0 1W-protocol bit. |
| dst | Frame destination node ID. |
| cmd | Frame command byte. |
Definition at line 268 of file hub_decisions.h.
|
inlinenodiscard |
Whether a 1W frame arriving at now starts a new burst rather than extending the current one — true if no frame has been seen yet, or the gap since the last one reached quiet_ms (the previous burst already released any deferred poll).
Callers use this to decide whether to reset a burst's start-time tracking; see defer_background_poll_for_1w_activity() for why the burst start (not just the latest frame) needs its own timestamp.
| last_1w_activity_ms | millis() of the most recent 1W frame before this one; 0 if none seen since boot. |
| now | Current millis() (this frame's arrival time). |
| quiet_ms | Gap after which a previous burst is considered over. |
Definition at line 286 of file hub_decisions.h.
|
inlinenodiscard |
Whether an authenticated-but-unanswered request may be sent again.
CMD_EXECUTE is the only request the hub sends that moves something, so a retry there is a second side effect on a device already acting on the first copy. Every other request (status polls, name reads, management actions, config writes) is idempotent and keeps its full retry budget when the device authenticates but never closes the exchange.
| cmd | Command byte of the outbound request. |
Definition at line 116 of file hub_decisions.h.
|
inline |
Transmit-attempt budget for a scheduler-owned status poll, by backoff-ladder position.
See SCHEDULED_POLL_MAX_TRIES and SCHEDULED_POLL_RETRY_GRACE_FIRST_FAILURE (proto_timing.h) for why the full budget belongs to a middle band of the ladder rather than to its start or its tail.
The two counters are mutually exclusive by construction — StatusPollPolicy::on_exchange_failed() zeroes one while incrementing the other — so an auth-shaped streak reads status_poll_failures as 0 and would otherwise fall into the band's own "fresh window" case. It is rejected first, deliberately, so the predicate stays correct even if that exclusivity is ever relaxed.
| status_poll_failures | Consecutive silent failures already recorded for this device. |
| auth_poll_failures | Consecutive challenge-seen failures already recorded. |
Definition at line 247 of file hub_decisions.h.