Home IO Control
ESPHome add-on for IO-Homecontrol devices
Loading...
Searching...
No Matches
esphome::home_io_control::decisions Namespace Reference

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.
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.
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).
uint32_t response_wait_slice_ms (uint32_t remaining_ms)
 Slice remaining wait time into bounded intervals to allow frequency hopping.

Enumeration Type Documentation

◆ ExchangeFinalResponseDisposition

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.

◆ ExchangeFirstResponseDisposition

Disposition for the first response in an authenticated exchange.

Enumerator
IGNORE_UNRELATED 

Frame doesn't match endpoints or failed parse — keep waiting.

COMPLETE_DIRECT 

Matching non-challenge frame — operation complete, no auth needed.

REQUIRE_AUTH 

Matching 0x3C challenge — device demands authentication.

Definition at line 24 of file hub_decisions.h.

◆ PairingDiscoveryDisposition

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.

◆ PairingKeyChallengeDisposition

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.

Function Documentation

◆ classify_exchange_final_response()

ExchangeFinalResponseDisposition esphome::home_io_control::decisions::classify_exchange_final_response ( const IoFrame & request,
const IoFrame & candidate )
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.

Parameters
requestOriginal outbound request frame.
candidateParsed IoFrame from the device.
Returns
ACCEPT if endpoints match; IGNORE_UNRELATED otherwise.

Definition at line 102 of file hub_decisions.h.

Here is the call graph for this function:

◆ classify_exchange_first_response()

ExchangeFirstResponseDisposition esphome::home_io_control::decisions::classify_exchange_first_response ( const IoFrame & request,
const IoFrame & candidate )
inline

Decide how to handle the first response packet in an authenticated exchange.

Used by wait_for_first_response_() to determine whether the exchange:

  • completes immediately (direct response),
  • requires authentication (challenge received), or
  • should ignore the frame and keep waiting.
Parameters
requestOriginal outbound request frame.
candidateParsed IoFrame from the device.
Returns
Disposition indicating next step.

Definition at line 83 of file hub_decisions.h.

Here is the call graph for this function:

◆ classify_pairing_discovery_response()

PairingDiscoveryDisposition esphome::home_io_control::decisions::classify_pairing_discovery_response ( const IoFrame & candidate,
const uint8_t controller_id[NODE_ID_SIZE] )
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.

Parameters
candidateParsed IoFrame.
controller_idNode ID of this controller (expected destination).
Returns
ACCEPT if the command is CMD_DISCOVER_RESP and addressed to this controller; INVALID otherwise.

Definition at line 122 of file hub_decisions.h.

◆ classify_pairing_key_challenge()

PairingKeyChallengeDisposition esphome::home_io_control::decisions::classify_pairing_key_challenge ( const IoFrame & candidate,
const uint8_t device_id[NODE_ID_SIZE],
const uint8_t controller_id[NODE_ID_SIZE] )
inline

Decide if a frame is a valid key-challenge (0x3C) during pairing key exchange.

The challenge must:

  • be CMD_CHALLENGE_REQ,
  • have data_len == HMAC_SIZE (6),
  • originate from the discovered device node ID,
  • be addressed to this controller's node ID.
Parameters
candidateParsed IoFrame.
device_idNode ID of the device being paired (expected sender).
controller_idNode ID of this controller (expected destination).
Returns
ACCEPT if all criteria met; IGNORE otherwise.

Definition at line 141 of file hub_decisions.h.

Here is the call graph for this function:

◆ defer_background_poll_for_1w_activity()

bool esphome::home_io_control::decisions::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 )
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.

Parameters
next_op_is_backgroundTrue if the queue front is a REQUEST_STATUS / REQUEST_NAME.
first_1w_activity_msmillis() of the first frame in the current 1W burst; 0 if none seen since boot.
last_1w_activity_msmillis() of the most recent 1W frame; 0 if none seen since boot.
nowCurrent millis().
quiet_msHow long after 1W activity to hold background polls back.
max_defer_msHard cap on total defer time, measured from first_1w_activity_ms.
Returns
true if the caller should skip dispatching this loop iteration.

Definition at line 214 of file hub_decisions.h.

◆ frame_matches_exchange_endpoints()

bool esphome::home_io_control::decisions::frame_matches_exchange_endpoints ( const IoFrame & request,
const IoFrame & candidate )
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.

Here is the call graph for this function:

◆ frame_matches_nodes()

bool esphome::home_io_control::decisions::frame_matches_nodes ( const IoFrame & frame,
const uint8_t expected_src[NODE_ID_SIZE],
const uint8_t expected_dst[NODE_ID_SIZE] )
inline

Check if two frames have identical src/dst node IDs.

Definition at line 60 of file hub_decisions.h.

◆ is_duplicate_1w_frame()

bool esphome::home_io_control::decisions::is_duplicate_1w_frame ( const OneWayDedupState & last,
const OneWayDedupState & incoming,
uint32_t window_ms )
inline

Decide whether an incoming 1W frame repeats the previous one inside the burst window.

Parameters
lastState recorded for the previously processed 1W frame.
incomingCandidate frame's key fields, with timestamp set to now.
window_msBurst-suppression window.
Returns
true if the frame should be dropped as a repeat of last.

Definition at line 178 of file hub_decisions.h.

◆ is_exchange_internal_command()

bool esphome::home_io_control::decisions::is_exchange_internal_command ( uint8_t cmd)
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.

◆ oneway_burst_started_fresh()

bool esphome::home_io_control::decisions::oneway_burst_started_fresh ( uint32_t last_1w_activity_ms,
uint32_t now,
uint32_t quiet_ms )
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.

Parameters
last_1w_activity_msmillis() of the most recent 1W frame before this one; 0 if none seen since boot.
nowCurrent millis() (this frame's arrival time).
quiet_msGap after which a previous burst is considered over.

Definition at line 234 of file hub_decisions.h.

◆ response_wait_slice_ms()

uint32_t esphome::home_io_control::decisions::response_wait_slice_ms ( uint32_t remaining_ms)
inline

Slice remaining wait time into bounded intervals to allow frequency hopping.

The wait loops (exchange and pairing) use this to avoid blocking the radio for too long without hopping. Each slice is at most RESPONSE_CHANNEL_WAIT_MS.

Parameters
remaining_msTotal time left in the wait window.
Returns
Time slice to wait in milliseconds.

Definition at line 247 of file hub_decisions.h.