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...
struct  TargetEvidence
 What the hub knows about the device an exchange is addressed to, as the exchange engine sees it. More...
struct  RainLimitationInput
 What one decoded status reply said, plus what the hub had predicted. 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...
enum class  PairingDiscoverConfirmDisposition : uint8_t { IGNORE , ACK , ERROR }
 Disposition for a candidate reply to a discovery-confirm request (0x2C). More...
enum class  PairingKeyConfirmDisposition : uint8_t { IGNORE , CONFIRM , CHALLENGE , REFUSE }
 Disposition for a candidate reply to the key-transfer (0x32) confirm wait — the slow-turnaround path's expanded classification of PairingKeyChallengeDisposition's device challenge to also cover a direct confirm or an explicit refusal. More...
enum class  WakeBelief : uint8_t { ASLEEP , AWAKE }
 Whether a low-power receiver is believed to be travelling right now, judged from what this hub has seen of it. More...
enum class  RainLimitationRule : uint8_t { NONE = 0 , RAIN_ORIGINATOR , CLAMPED_COMMAND }
 Which evidence path labelled a device as limited by rain. 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 is_repeatable_execute (const IoFrame &request)
 True for a CMD_EXECUTE that has the same effect sent twice as sent once.
bool retry_after_unconfirmed_accept_is_safe (const IoFrame &request, bool target_confirms_execute, uint8_t unconfirmed_tries)
 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.
PairingDiscoverConfirmDisposition classify_pairing_discover_confirm_reply (const IoFrame &candidate, const uint8_t device_id[NODE_ID_SIZE], const uint8_t controller_id[NODE_ID_SIZE])
 Decide how to handle a candidate reply to a discovery-confirm request (0x2C) during pairing.
PairingKeyConfirmDisposition classify_pairing_key_confirm_reply (const IoFrame &request, const IoFrame &candidate)
 Decide how to handle a candidate reply to the key-transfer (0x32) confirm wait (wait_for_key_confirm_(), slow-turnaround chips only).
bool discover_confirm_try_rotates (uint8_t try_index)
 Whether discover-confirm try try_index (1-based) should rotate channels rather than hold the request channel.
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, bool settles_a_stop=false)
 Transmit-attempt budget for a scheduler-owned status poll, by backoff-ladder position.
TargetEvidence target_evidence (const IoDevice &dev)
 Build the exchange engine's view of a device record.
bool is_stop_request (const IoFrame &request)
 True for a CMD_EXECUTE whose main byte is POS_STOP.
WakeBelief wake_belief (const TargetEvidence &evidence, uint32_t now, bool is_stop)
 Judge whether a low-power receiver is travelling.
uint16_t low_power_try_preamble (WakeBelief belief, uint8_t try_index, uint16_t short_preamble, uint16_t wake_preamble)
 Start-preamble length for one try of a directed exchange to a low-power receiver.
const char * wake_belief_name (WakeBelief belief)
 Lowercase name of a belief for log lines.
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).
uint32_t remote_poll_timer_id (const uint8_t node_id[NODE_ID_SIZE])
 Numeric set_timeout() id for a device's remote-activity poll timer (hub_status.cpp's schedule_status_poll_()).
uint32_t heating_poll_timer_id (const uint8_t node_id[NODE_ID_SIZE])
 Numeric set_timeout() id for a heater's regular-cadence poll timer (IOHomeControlComponent::schedule_heating_poll_()).
bool pairing_node_has_heater_at_next_address (DeviceType type, uint8_t manufacturer)
 Does a pairing node that answered discovery this way control a heater at the next address?
bool next_node_id (const uint8_t node[NODE_ID_SIZE], uint8_t next[NODE_ID_SIZE])
 The node address one above node, as a 24-bit number (B6BDFB -> B6BDFC).
RainLimitationRule rain_limitation_rule (const RainLimitationInput &in)
 Decide which rain-limitation evidence, if any, a status reply provides.
bool is_rain_limited (const RainLimitationInput &in)
 True when rain_limitation_rule() finds evidence of a rain limitation.
const char * rain_limitation_rule_name (RainLimitationRule rule)
 Name of a rain-limitation rule for log lines.
RainSensorState rain_state_from_minimum_limit (uint16_t value_raw)
 Rain state from the raw value of a minimum-limitation reply.
RainSensorState rain_state_from_limitation_reply (const LimitationStatus &status)
 Rain state from a decoded limitation reply: the one place that knows which reply fields decide.

Variables

constexpr uint32_t REMOTE_POLL_TIMER_ID_TAG = 0x01000000
 Namespace tag for remote_poll_timer_id() below: the node address occupies the low 24 bits, and this is OR-ed in above them.
constexpr uint32_t HEATING_POLL_TIMER_ID_TAG = 0x02000000
 Namespace tag for heating_poll_timer_id(): the same node-address scheme under its own bit, so a heater's regular-cadence timer and a remote-activity poll timer for the same device never replace one another.
static constexpr uint8_t RAIN_MINIMUM_LIMIT_PERCENT = 89
 A window with a wired rain sensor limits its own opening while it rains.
static constexpr uint16_t POSITION_FULL_PERCENT = 100
 Percent value of a fully limited position, the divisor of the raw position coding.
static constexpr uint16_t POSITION_RAW_PER_PERCENT = STATUS_POS_MAX / POSITION_FULL_PERCENT
 Raw position units per percent (512).
static constexpr uint16_t RAIN_MINIMUM_LIMIT_RAW = POSITION_RAW_PER_PERCENT * RAIN_MINIMUM_LIMIT_PERCENT
 RAIN_MINIMUM_LIMIT_PERCENT in the raw position coding (0xB200).

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 34 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 27 of file hub_decisions.h.

◆ PairingDiscoverConfirmDisposition

Disposition for a candidate reply to a discovery-confirm request (0x2C).

Enumerator
IGNORE 

Not from/to the expected endpoints, or a frame the step does not act on (e.g.

a repeated 0x29) — keep waiting.

ACK 

CMD_DISCOVER_CONFIRM_ACK (0x2D) from the device — it wants to proceed.

ERROR 

CMD_ERROR_RESP from the device — an explicit, named refusal.

Definition at line 53 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 40 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 47 of file hub_decisions.h.

◆ PairingKeyConfirmDisposition

Disposition for a candidate reply to the key-transfer (0x32) confirm wait — the slow-turnaround path's expanded classification of PairingKeyChallengeDisposition's device challenge to also cover a direct confirm or an explicit refusal.

Enumerator
IGNORE 

Not from/to the expected endpoints — keep waiting.

CONFIRM 

CMD_KEY_CONFIRM (0x33) — the device accepted the key.

CHALLENGE 

A fresh CMD_CHALLENGE_REQ (0x3C) — the device may challenge the key transfer before confirming it; answered via ExchangeEngine::answer_challenge().

REFUSE 

Anything else (including CMD_ERROR_RESP) — an explicit or implicit refusal; ends the whole wait, not just the current try.

Definition at line 63 of file hub_decisions.h.

◆ RainLimitationRule

Which evidence path labelled a device as limited by rain.

Enumerator
NONE 

No evidence of a rain limitation.

RAIN_ORIGINATOR 

The device names the rain sensor as the originator of its last command.

CLAMPED_COMMAND 

A position command was accepted but the stopped device ended elsewhere, while rain protection was recently active.

Definition at line 575 of file hub_decisions.h.

◆ WakeBelief

Whether a low-power receiver is believed to be travelling right now, judged from what this hub has seen of it.

Orders the tries of a directed exchange: a moving receiver hears the short start preamble and ignores the long wake-up one, a resting one needs the long one.

Having merely heard from the device recently is deliberately not a reason to lead short: a resting VELUX SSL solar roller shutter ignored the short preamble on every try in field logs, including tries sent 34 ms after it had answered (ADR 0040, amendment).

Enumerator
ASLEEP 

Not known to be moving — lead with the wake-up preamble.

AWAKE 

Moving, or about to be stopped — the short preamble is the one it hears.

Definition at line 391 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 125 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 106 of file hub_decisions.h.

Here is the call graph for this function:

◆ classify_pairing_discover_confirm_reply()

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

Decide how to handle a candidate reply to a discovery-confirm request (0x2C) during pairing.

A non-matching or unrecognised frame is IGNORE, keeping the listen open rather than ending it: the discover-confirm step never fails a pairing attempt (see run_discover_confirm_step_()'s doc) — this classification only decides whether to keep listening or stop early.

Parameters
candidateParsed IoFrame.
device_idNode ID of the device being paired (expected sender).
controller_idNode ID of this controller (expected destination).
Returns
ACK for a matching 0x2D, ERROR for a matching CMD_ERROR_RESP, IGNORE otherwise.

Definition at line 224 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 183 of file hub_decisions.h.

Here is the call graph for this function:

◆ 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 202 of file hub_decisions.h.

Here is the call graph for this function:

◆ classify_pairing_key_confirm_reply()

PairingKeyConfirmDisposition esphome::home_io_control::decisions::classify_pairing_key_confirm_reply ( const IoFrame & request,
const IoFrame & candidate )
inline

Decide how to handle a candidate reply to the key-transfer (0x32) confirm wait (wait_for_key_confirm_(), slow-turnaround chips only).

Parameters
requestThe outbound CMD_KEY_TRANSFER (0x32) this reply answers.
candidateParsed IoFrame from the device.
Returns
CONFIRM/CHALLENGE/REFUSE for a matching frame of that shape; IGNORE for a frame from the wrong endpoints.

Definition at line 244 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 background poll (OperationQueue::is_background_op()).
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 342 of file hub_decisions.h.

Here is the call graph for this function:

◆ discover_confirm_try_rotates()

bool esphome::home_io_control::decisions::discover_confirm_try_rotates ( uint8_t try_index)
inlinenodiscard

Whether discover-confirm try try_index (1-based) should rotate channels rather than hold the request channel.

Tries 1 and 3 hold: a 0x2D is a unicast reply to a unicast 0x2C, and every other unicast pairing wait in this project holds the request channel on that same expectation (see listen_for_key_confirm_()'s own reasoning), and every 0x2D a Somfy Izymo dimmer sent came back on the request channel. But the one real VELUX-system sample on record (from a hopping monitor, a weak source) logged a 0x2D on a different channel than its own 0x2C, so the middle try hedges by rotating instead, until a VELUX device's reply channel is measured. See ADR 0039 for why this is not simply one fixed policy for every try, unlike every other listen in this project (ADR 0028).

Parameters
try_index1-based try number (1..PAIRING_DISCOVER_CONFIRM_TRIES).
Returns
true if this try should use ListenPolicy::ROTATE_ALL_CHANNELS; false for ListenPolicy::HOLD_REQUEST_CHANNEL.

Definition at line 269 of file hub_decisions.h.

Here is the call graph for this function:

◆ 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 90 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 83 of file hub_decisions.h.

Here is the call graph for this function:

◆ heating_poll_timer_id()

uint32_t esphome::home_io_control::decisions::heating_poll_timer_id ( const uint8_t node_id[NODE_ID_SIZE])
inline

Numeric set_timeout() id for a heater's regular-cadence poll timer (IOHomeControlComponent::schedule_heating_poll_()).

Same keying as remote_poll_timer_id(), in the HEATING_POLL_TIMER_ID_TAG namespace, so a poll that a linked remote triggers cannot cancel it.

Parameters
node_id3-byte device node address.
Returns
Numeric timer id, unique per device and distinct from remote_poll_timer_id().

Definition at line 532 of file hub_decisions.h.

Here is the call graph for this function:

◆ 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 304 of file hub_decisions.h.

Here is the call graph for this function:

◆ 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 78 of file hub_decisions.h.

Here is the call graph for this function:

◆ is_one_way_pairing_gesture()

bool esphome::home_io_control::decisions::is_one_way_pairing_gesture ( bool oneway,
const uint8_t dst[NODE_ID_SIZE],
uint8_t cmd )
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().

Parameters
onewayCTRL0 1W-protocol bit.
dstFrame destination node ID.
cmdFrame command byte.

Definition at line 478 of file hub_decisions.h.

Here is the call graph for this function:

◆ is_rain_limited()

bool esphome::home_io_control::decisions::is_rain_limited ( const RainLimitationInput & in)
inlinenodiscard

True when rain_limitation_rule() finds evidence of a rain limitation.

Definition at line 611 of file hub_decisions.h.

Here is the call graph for this function:

◆ is_repeatable_execute()

bool esphome::home_io_control::decisions::is_repeatable_execute ( const IoFrame & request)
inlinenodiscard

True for a CMD_EXECUTE that has the same effect sent twice as sent once.

Every EXECUTE this hub sends names an absolute target (a position, open/close, STOP, a tilt angle, a light level) except the stored-position selector POS_FAVORITE, used by favourite and vent: on a Somfy motor "My" while moving means stop, so a second copy can undo what the first one started.

Parameters
requestOutbound request frame.

Definition at line 136 of file hub_decisions.h.

Here is the call graph for this function:

◆ is_stop_request()

bool esphome::home_io_control::decisions::is_stop_request ( const IoFrame & request)
inlinenodiscard

True for a CMD_EXECUTE whose main byte is POS_STOP.

A STOP is only ever sent to a receiver that is (believed) moving, so it is treated as awake whatever the stamps say.

Parameters
requestOutbound request frame.

Definition at line 418 of file hub_decisions.h.

Here is the call graph for this function:

◆ low_power_try_preamble()

uint16_t esphome::home_io_control::decisions::low_power_try_preamble ( WakeBelief belief,
uint8_t try_index,
uint16_t short_preamble,
uint16_t wake_preamble )
inlinenodiscard

Start-preamble length for one try of a directed exchange to a low-power receiver.

The plans (short = short_preamble, wake = wake_preamble): AWAKE short/wake/wake, ASLEEP wake on every try. A moving receiver answers the first short try; when it does not, it has most likely come to rest, where only the wake-up preamble reaches it, so every later try uses that one. A single-try exchange (most scheduler-owned status polls) sends only try 1's preamble; its backoff ladder, and the evidence a silent AWAKE exchange spends (ExchangeEngine::set_wake_evidence_spent_handler()), cover a wrong belief there.

Parameters
beliefSee wake_belief().
try_index1-based try number, clamped to [1, EXCHANGE_RETRY_COUNT].
short_preamblePreamble for a receiver known to be moving (normal_start_preamble).
wake_preambleWake-up preamble for a resting receiver (low_power_wake_preamble).

Definition at line 447 of file hub_decisions.h.

Here is the call graph for this function:

◆ next_node_id()

bool esphome::home_io_control::decisions::next_node_id ( const uint8_t node[NODE_ID_SIZE],
uint8_t next[NODE_ID_SIZE] )
inline

The node address one above node, as a 24-bit number (B6BDFB -> B6BDFC).

A carry into the higher bytes (xxxxFF) is computed as plain arithmetic; no radiator with such an address has been seen, so whether the device counts the same way is unconfirmed.

Parameters
nodeAddress to count from.
[out]nextThe next address; untouched when false is returned.
Returns
false for FFFFFF, which has no next address.

Definition at line 559 of file hub_decisions.h.

Here is the call graph for this function:

◆ 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 496 of file hub_decisions.h.

Here is the call graph for this function:

◆ pairing_node_has_heater_at_next_address()

bool esphome::home_io_control::decisions::pairing_node_has_heater_at_next_address ( DeviceType type,
uint8_t manufacturer )
inline

Does a pairing node that answered discovery this way control a heater at the next address?

An Atlantic radiator in its display's pairing mode answers discovery as a remote controller (node type 1023), hands over its key, and is controlled at the next node address up: three AGILIA IO radiators follow this (issue #165, tests/corpus/captures/pairing/atlantic_agilia_pairing_key_pull_heater_rollcall.yaml). The heater answers a roll-call only now and then, so the pairing asks that address directly first.

Parameters
typeNode type the pairing node answered discovery with.
manufacturerManufacturer byte of that discovery reply (0 when the reply carried none).
Returns
true for an Atlantic remote-controller node.

Definition at line 548 of file hub_decisions.h.

Here is the call graph for this function:

◆ rain_limitation_rule()

RainLimitationRule esphome::home_io_control::decisions::rain_limitation_rule ( const RainLimitationInput & in)
inlinenodiscard

Decide which rain-limitation evidence, if any, a status reply provides.

A rain limitation is an inference from observations, never a prediction (ADR 0030 keeps predictions apart): windows clamp an open to their ventilation position without sending CMD_ERROR_RESP / RESULT_LIMITATION_BY_RAIN, so the only evidence is what the device reports in its ordinary status replies. Rule order matters: the originator rule is the direct evidence and wins; the clamp rule only applies while earlier rain evidence is still fresh.

Parameters
inWhat the reply said and what had been predicted.
Returns
The rule that fired, or RainLimitationRule::NONE.

Definition at line 601 of file hub_decisions.h.

Here is the call graph for this function:

◆ rain_limitation_rule_name()

const char * esphome::home_io_control::decisions::rain_limitation_rule_name ( RainLimitationRule rule)
inlinenodiscard

Name of a rain-limitation rule for log lines.

Definition at line 616 of file hub_decisions.h.

Here is the call graph for this function:

◆ rain_state_from_limitation_reply()

RainSensorState esphome::home_io_control::decisions::rain_state_from_limitation_reply ( const LimitationStatus & status)
inlinenodiscard

Rain state from a decoded limitation reply: the one place that knows which reply fields decide.

Only the main parameter's value counts; originator and remaining time do not.

Parameters
statusA decoded CMD_LIMITATION_STATUS_RESP payload.
Returns
UNKNOWN unless the reply is for the main parameter, else rain_state_from_minimum_limit().

Definition at line 657 of file hub_decisions.h.

Here is the call graph for this function:

◆ rain_state_from_minimum_limit()

RainSensorState esphome::home_io_control::decisions::rain_state_from_minimum_limit ( uint16_t value_raw)
inlinenodiscard

Rain state from the raw value of a minimum-limitation reply.

Parameters
value_rawBig-endian raw value in position coding (LimitationStatus::value_raw).
Returns
UNKNOWN for a value above STATUS_POS_MAX (not a percentage), RAIN at or above RAIN_MINIMUM_LIMIT_RAW, DRY below it.

Definition at line 647 of file hub_decisions.h.

Here is the call graph for this function:

◆ remote_poll_timer_id()

uint32_t esphome::home_io_control::decisions::remote_poll_timer_id ( const uint8_t node_id[NODE_ID_SIZE])
inline

Numeric set_timeout() id for a device's remote-activity poll timer (hub_status.cpp's schedule_status_poll_()).

Keyed by the device's node address rather than a name, because Component::set_timeout(const char *name, ...) stores the caller's pointer, not a copy of the string (its header documents this: static lifetime required, use the numeric-id overload for a dynamically-built name) — see schedule_status_poll_()'s own comment for why a per-device name can't satisfy that here. A node address is unique per device, so this id is collision-free by construction.

Parameters
node_id3-byte device node address.
Returns
Numeric timer id, unique per device.

Definition at line 522 of file hub_decisions.h.

Here is the call graph for this function:

◆ retry_after_unconfirmed_accept_is_safe()

bool esphome::home_io_control::decisions::retry_after_unconfirmed_accept_is_safe ( const IoFrame & request,
bool target_confirms_execute,
uint8_t unconfirmed_tries )
inlinenodiscard

Whether an authenticated-but-unanswered request may be sent again.

Every request except CMD_EXECUTE is idempotent (status polls, name reads, management actions, config writes) and keeps its full retry budget when the device authenticates but never closes the exchange.

CMD_EXECUTE moves something, and a missing closing reply means two different things depending on the device. Some devices never close an EXECUTE exchange within the response window and report through a later status update instead; for them silence is normal and a re-send only repeats a command already being carried out. For a device that normally does close it, silence can mean our challenge answer never arrived and the command was not carried out at all (a STOP that left an awning moving). So an EXECUTE is sent again only to a device known to confirm, only when repeating it is harmless (is_repeatable_execute()), and at most UNCONFIRMED_EXECUTE_MAX_RESENDS times per exchange.

Parameters
requestOutbound request frame.
target_confirms_executeThe target has closed an EXECUTE exchange with a reply before (TargetEvidence::confirms_execute).
unconfirmed_triesTries of this exchange that ended accepted without a closing reply, including the one just ended (1-based).
Returns
true when the exchange should spend another try.

Definition at line 161 of file hub_decisions.h.

Here is the call graph for this function:

◆ scheduled_poll_max_tries()

uint8_t esphome::home_io_control::decisions::scheduled_poll_max_tries ( uint8_t status_poll_failures,
uint8_t auth_poll_failures,
bool settles_a_stop = false )
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.

The settle poll after an accepted STOP (settles_a_stop) gets the full budget whatever the counters say: see STOP_SETTLE_POLL_TRIES (proto_timing.h).

Parameters
status_poll_failuresConsecutive silent failures already recorded for this device.
auth_poll_failuresConsecutive challenge-seen failures already recorded.
settles_a_stopTrue for the first poll after an accepted STOP (StatusPollPolicy::take_stop_settle()).
Returns
EXCHANGE_RETRY_COUNT inside the band or after a STOP, SCHEDULED_POLL_MAX_TRIES everywhere else.

Definition at line 370 of file hub_decisions.h.

Here is the call graph for this function:

◆ target_evidence()

TargetEvidence esphome::home_io_control::decisions::target_evidence ( const IoDevice & dev)
inlinenodiscard

Build the exchange engine's view of a device record.

Parameters
devDevice record to read.

Definition at line 411 of file hub_decisions.h.

Here is the call graph for this function:

◆ wake_belief()

WakeBelief esphome::home_io_control::decisions::wake_belief ( const TargetEvidence & evidence,
uint32_t now,
bool is_stop )
inlinenodiscard

Judge whether a low-power receiver is travelling.

AWAKE for a STOP request or while moving evidence is younger than LOW_POWER_MAX_TRAVEL_MS; ASLEEP otherwise. A zero stamp means never and is never recent. Ages use unsigned subtraction, so millis() wrap-around is safe.

Parameters
evidenceStamps for the target device.
nowCurrent millis().
is_stopTrue when the request being sent is a STOP (see is_stop_request()).

Definition at line 429 of file hub_decisions.h.

Here is the call graph for this function:

◆ wake_belief_name()

const char * esphome::home_io_control::decisions::wake_belief_name ( WakeBelief belief)
inlinenodiscard

Lowercase name of a belief for log lines.

Definition at line 456 of file hub_decisions.h.

Here is the call graph for this function:

Variable Documentation

◆ HEATING_POLL_TIMER_ID_TAG

uint32_t esphome::home_io_control::decisions::HEATING_POLL_TIMER_ID_TAG = 0x02000000
constexpr

Namespace tag for heating_poll_timer_id(): the same node-address scheme under its own bit, so a heater's regular-cadence timer and a remote-activity poll timer for the same device never replace one another.

Definition at line 509 of file hub_decisions.h.

◆ POSITION_FULL_PERCENT

uint16_t esphome::home_io_control::decisions::POSITION_FULL_PERCENT = 100
staticconstexpr

Percent value of a fully limited position, the divisor of the raw position coding.

Definition at line 635 of file hub_decisions.h.

◆ POSITION_RAW_PER_PERCENT

uint16_t esphome::home_io_control::decisions::POSITION_RAW_PER_PERCENT = STATUS_POS_MAX / POSITION_FULL_PERCENT
staticconstexpr

Raw position units per percent (512).

Definition at line 637 of file hub_decisions.h.

◆ RAIN_MINIMUM_LIMIT_PERCENT

uint8_t esphome::home_io_control::decisions::RAIN_MINIMUM_LIMIT_PERCENT = 89
staticconstexpr

A window with a wired rain sensor limits its own opening while it rains.

Home Assistant's VELUX integration reads that limit and calls it rain from this minimum limit upward (observed values 89, 91, 93 and 100 %).

Definition at line 633 of file hub_decisions.h.

◆ RAIN_MINIMUM_LIMIT_RAW

uint16_t esphome::home_io_control::decisions::RAIN_MINIMUM_LIMIT_RAW = POSITION_RAW_PER_PERCENT * RAIN_MINIMUM_LIMIT_PERCENT
staticconstexpr

RAIN_MINIMUM_LIMIT_PERCENT in the raw position coding (0xB200).

Definition at line 641 of file hub_decisions.h.

◆ REMOTE_POLL_TIMER_ID_TAG

uint32_t esphome::home_io_control::decisions::REMOTE_POLL_TIMER_ID_TAG = 0x01000000
constexpr

Namespace tag for remote_poll_timer_id() below: the node address occupies the low 24 bits, and this is OR-ed in above them.

Each numeric-id timer caller has its own tag (see HEATING_POLL_TIMER_ID_TAG), so two timers for the same device never replace one another and an id says which caller it belongs to.

Definition at line 504 of file hub_decisions.h.