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  AttemptPlan
 How one exchange spends its attempts. Handed to ExchangeEngine::send_and_receive(). More...
struct  AttemptRequest
 What a caller asks of one exchange's attempts: a plain cap on tries (today's shape), or a read kind whose plan the engine builds for the request (ExchangeEngine::plan_read()). 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...
struct  StoppedReportInput
 One decoded status report, plus what the hub remembers and whether it will poll again. 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  ReadKind : uint8_t { PROGRESS , SETTLING , COMMAND }
 What a request is for, which decides how its attempts are spent. More...
enum class  WakeBelief : uint8_t { ASLEEP , AWAKE , UNKNOWN }
 Whether a low-power receiver is believed to be travelling right now, judged from what this hub has seen of it. More...
enum class  HeaterModelHint : uint8_t { NONE , LOOKS_LIKE_RADIATOR , LOOKS_LIKE_TOWEL_DRYER }
 What the model check suggests about a heater's declared io_model:. More...
enum class  RainLimitationRule : uint8_t { NONE = 0 , RAIN_ORIGINATOR , CLAMPED_COMMAND }
 Which evidence path labelled a device as limited by rain. More...
enum class  StoppedReport : uint8_t {
  MOVING , STOPPED , STOPPED_CONFIRMED , STOPPED_UNCONFIRMABLE ,
  SETTLING
}
 What the hub makes of one status report's movement state. 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 is_device_event_report (const IoFrame &frame)
 Returns true for a frame that reports a device event rather than a status: a CMD_SET_CONFIG1_RESP, or a well-formed CMD_STATUS_UPDATE whose event is not EVENT_STATUS_CHANGE (a subscribed sensor report, see decode_status_update_event()).
bool is_subscription_answer (const IoFrame &request, const IoFrame &response)
 Returns true when response is the answer to the subscription request: the request is a CMD_SUBSCRIBE_EVENT with the subscribe action, and the response a well-formed CMD_STATUS_UPDATE carrying the same event, from the device the request went to.
bool overheard_command_writes (uint8_t cmd)
 Returns true for the commands that write to a device: an execute, a mode activation or a private register write.
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.
bool scheduled_poll_in_retry_band (uint8_t status_poll_failures, uint8_t auth_poll_failures)
 True when a scheduler-owned status poll sits in the middle band of the backoff ladder.
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 of a heater, by ladder position.
uint32_t short_lead_cost_ms (uint32_t short_tx_ms)
 What the short lead attempt costs the exchange: its transmission, its brief listen and the attempt overhead (the same terms attempt_fits() counts for any attempt).
bool attempt_fits (uint32_t elapsed_ms, uint32_t gap_ms, uint32_t tx_ms, uint32_t window_ms, uint32_t auth_tail_ms, uint32_t budget_ms)
 The attempt budget rule: an attempt starts only if the whole attempt, up to the end of the last wait it can make, ends inside budget_ms.
uint8_t attempts_that_fit (uint32_t budget_ms, uint32_t attempt_tx_ms, uint32_t early_window_ms, uint32_t last_window_ms, uint8_t cap)
 How many attempts to a silent target fit the budget when none waits a gap, capped at cap.
AttemptPlan attempt_plan_for_tries (uint8_t max_tries, uint16_t window_ms)
 The plan for a caller that names only a number of attempts: that many (at least one, at most EXCHANGE_RETRY_COUNT), the default gap, and window_ms.
AttemptPlan attempt_plan (ReadKind kind, bool low_power, const TuningConfig &tuning, uint32_t attempt_tx_ms, uint16_t last_window_ms=0, std::optional< uint32_t > short_lead_tx_ms=std::nullopt)
 The attempt plan for a request, from what it is for and who it asks.
bool is_repeat_safe_command (const IoFrame &request)
 Whether a request may be sent again to a device that said nothing, without a second action.
ReadKind scheduled_poll_read_kind (uint8_t status_poll_failures, uint8_t auth_poll_failures, bool settles_a_position)
 What a cover's status poll is for, from where it sits and what it settles.
TargetEvidence target_evidence (const IoDevice &dev)
 Build the exchange engine's view of a device record.
bool moving_until_holds (const TargetEvidence &evidence, uint32_t now)
 True while the remaining time the receiver last reported has not run out: by its own account it is still travelling, so a silent exchange is not evidence that it has come to rest.
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, bool is_status_read=false, bool state_unknown=false)
 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).
std::array< uint32_t, 3 > status_update_ack_channel_order (uint32_t receive_freq_hz)
 The order in which a 0x72 status-update acknowledgement is sent on the three channels: the channel the 0x71 exchange ran on first, then the other two in their usual CH1, CH2, CH3 order.
bool frame_issue_is_warning (bool dst_registered, bool dst_is_us)
 Whether a frame issue (a frame the hub did not act on) is logged at WARN rather than DEBUG.
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_()).
HeaterModelHint classify_heater_model_hint (DeviceModel declared, bool towel_mode_has_value)
 Compare a heater's declared model with whether it answered the towel-dryer mode register (0x0136) with a value.
std::string format_heater_model_name (const uint8_t *bytes, size_t len)
 Render a heater's model-name register (0x0119, ASCII) for a log line.
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.
bool is_executing (ExecutionState state)
 True when a device in this state is moving or about to: waiting for its turn (2), waiting for power (3) or executing (4).
bool reads_stopped_by_status_byte (uint8_t status_byte)
 Does byte 0 of a status reply say the device is at rest?
StoppedReport classify_stopped_report (const StoppedReportInput &in)
 Decide whether a "stopped" report means at rest.
bool stopped_report_at_rest (StoppedReport report)
 True when a verdict means the device is at rest.
bool stopped_report_keeps_candidate (StoppedReport report)
 True when a verdict leaves an off-target report to remember for the next one.

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.
constexpr uint8_t HEATER_MODEL_CHECK_MAX_RUNS = 3
 Runs of the one-off heater model check before the hub gives up on it for this boot.
constexpr uint8_t MODEL_NAME_PRINTABLE_FIRST = 0x20
 First and last byte format_heater_model_name() prints as itself: printable ASCII, space to '~'.
constexpr uint8_t MODEL_NAME_PRINTABLE_LAST = 0x7E
 See MODEL_NAME_PRINTABLE_FIRST.
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 37 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 30 of file hub_decisions.h.

◆ HeaterModelHint

What the model check suggests about a heater's declared io_model:.

Enumerator
NONE 

The answer fits the declaration: nothing to say.

LOOKS_LIKE_RADIATOR 

Declared a towel dryer, but the towel-dryer mode register holds no value.

LOOKS_LIKE_TOWEL_DRYER 

Declared as no towel dryer, but the towel-dryer mode register holds a value.

Definition at line 915 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 56 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 43 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 50 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 66 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 997 of file hub_decisions.h.

◆ ReadKind

enum class esphome::home_io_control::decisions::ReadKind : uint8_t
strong

What a request is for, which decides how its attempts are spent.

Enumerator
PROGRESS 

A look at a device that is probably still busy (a mid-travel poll): one attempt, the backoff ladder is the retry.

SETTLING 

A read that decides where a device ended up: as many attempts as fit the budget.

COMMAND 

A request that is harmless to repeat (is_repeat_safe_command()): to a mains device as many attempts as fit the budget, to a low-power one today's few with a gap.

Definition at line 437 of file hub_decisions.h.

◆ StoppedReport

What the hub makes of one status report's movement state.

Enumerator
MOVING 

The device reports itself moving.

STOPPED 

Stopped on its target (within STATUS_POS_TOLERANCE_RAW), or with no known target or position to compare.

STOPPED_CONFIRMED 

Stopped off its target, as in the remembered report: at rest there.

STOPPED_UNCONFIRMABLE 

Stopped off its target and no follow-up poll will run, so the device's own flag stands.

SETTLING 

Stopped off its target for the first time: treated as moving until the next report confirms it.

Definition at line 1114 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.

UNKNOWN 

A status read to a receiver the hub has no recent news of (no moving evidence, and either no status report within LOW_POWER_MAX_TRAVEL_MS — boot, a status refresh — or a read its caller marked with AttemptRequest::with_state_unknown(), such as a catch-up read): it may be travelling, when it ignores the wake-up preamble, or at rest, when it hears only that one.

Every try of a single-try read keeps the wake-up preamble; a settling read leads with one short attempt of its own (AttemptPlan::short_lead) and then sends the wake-up attempts.

Todo
Experimental: rests on a moving VELUX screen ignoring the wake-up preamble (issues #114 and #209); no bench run has met a read to a receiver in this state yet.

Definition at line 698 of file hub_decisions.h.

Function Documentation

◆ attempt_fits()

bool esphome::home_io_control::decisions::attempt_fits ( uint32_t elapsed_ms,
uint32_t gap_ms,
uint32_t tx_ms,
uint32_t window_ms,
uint32_t auth_tail_ms,
uint32_t budget_ms )
inlinenodiscard

The attempt budget rule: an attempt starts only if the whole attempt, up to the end of the last wait it can make, ends inside budget_ms.

Both the engine's guard (ExchangeEngine::try_fits_budget_()) and the plan that counts attempts (attempts_that_fit()) use it, so a plan never promises an attempt the guard would refuse for a target that stays silent. The attempt's overhead (EXCHANGE_ATTEMPT_OVERHEAD_MS) is counted here and nowhere else.

Parameters
elapsed_msTime since the exchange began.
gap_msWait before the attempt is sent.
tx_msThe attempt's transmit time as the radio estimates it.
window_msHow long the attempt then listens for the first reply.
auth_tail_msThe final-reply window a challenged attempt adds, 0 until the target has challenged.
budget_msexchange_total_budget_ms.

Definition at line 501 of file hub_decisions.h.

Here is the call graph for this function:

◆ attempt_plan()

AttemptPlan esphome::home_io_control::decisions::attempt_plan ( ReadKind kind,
bool low_power,
const TuningConfig & tuning,
uint32_t attempt_tx_ms,
uint16_t last_window_ms = 0,
std::optional< uint32_t > short_lead_tx_ms = std::nullopt )
inlinenodiscard

The attempt plan for a request, from what it is for and who it asks.

A settling read, and a repeat-safe command to a mains device, repeat without a gap: every attempt listens from the end of its own transmission, so a silent attempt is already followed by the next one as soon as its window ends. The attempts before the last listen only exchange_early_response_wait_ms (a mains device answers within tens of milliseconds or not at all) and the last listens the whole window, so how many fit is attempts_that_fit(), capped at EXCHANGE_MAX_ATTEMPTS. A settling read of a low-power device makes LOW_POWER_SETTLING_ATTEMPTS attempts of LOW_POWER_RESPONSE_WAIT_MS each. The same rule the engine's guard applies at run time, so a plan never promises an attempt the guard would refuse when the target stays silent. A challenged attempt costs more (the final reply window), so the guard can still stop a plan early.

PROGRESS and a COMMAND to a low-power device keep today's shapes: one attempt, and EXCHANGE_RETRY_COUNT attempts with the default gap, both with the tuned start window.

Parameters
kindWhat the request is for.
low_powerThe target is a low-power receiver (YAML low_power, CTRL1 low-power bit).
tuningReply windows and exchange budget.
attempt_tx_msOne attempt's transmit time as the radio estimates it (long wake-up preamble for a low-power target); only a gapless plan uses it.
last_window_msListen time of the last attempt of a mains target, or of every attempt of a gapped plan; 0 for the tuned start window.
short_lead_tx_msSet to lead a settling read of a low-power target with one short attempt (the receiver's state is unknown, see WakeBelief::UNKNOWN): that attempt's transmit time. The lead is added to the wake-up attempts the budget admits, which are the same as without it; when all of them do not fit with the lead, the exchange may run past the budget by the lead's cost (AttemptPlan::budget_allowance_ms). No lead when only one wake-up attempt fits, or for any other read.

Definition at line 571 of file hub_decisions.h.

Here is the call graph for this function:

◆ attempt_plan_for_tries()

AttemptPlan esphome::home_io_control::decisions::attempt_plan_for_tries ( uint8_t max_tries,
uint16_t window_ms )
inlinenodiscard

The plan for a caller that names only a number of attempts: that many (at least one, at most EXCHANGE_RETRY_COUNT), the default gap, and window_ms.

Parameters
max_triesAttempts the caller asks for.
window_msFirst-reply window of every attempt.

Definition at line 535 of file hub_decisions.h.

Here is the call graph for this function:

◆ attempts_that_fit()

uint8_t esphome::home_io_control::decisions::attempts_that_fit ( uint32_t budget_ms,
uint32_t attempt_tx_ms,
uint32_t early_window_ms,
uint32_t last_window_ms,
uint8_t cap )
inlinenodiscard

How many attempts to a silent target fit the budget when none waits a gap, capped at cap.

The first n - 1 attempts listen early_window_ms and the last listens last_window_ms, and the last must still pass attempt_fits(), so n is the largest with (n - 1) * (tx + overhead + early) + tx + overhead + last < budget. At least one attempt: the first always runs. A zero budget is no limit the plan can honour (the engine's guard still applies), so it yields cap.

Parameters
budget_msexchange_total_budget_ms.
attempt_tx_msOne attempt's transmit time as the radio estimates it.
early_window_msListen time of every attempt but the last.
last_window_msListen time of the last attempt.
capMost attempts the caller allows.

Definition at line 519 of file hub_decisions.h.

Here is the call graph for this function:

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

Here is the call graph for this function:

◆ classify_heater_model_hint()

HeaterModelHint esphome::home_io_control::decisions::classify_heater_model_hint ( DeviceModel declared,
bool towel_mode_has_value )
inline

Compare a heater's declared model with whether it answered the towel-dryer mode register (0x0136) with a value.

Two Atlantic Adelis towel dryers answer that register with their mode; an AGILIA radiator and a heater with a night light have not been read there yet, so the hint is worded "looks like" and never changes behaviour. The caller only asks after the device answered the read (with or without a value).

Parameters
declaredThe YAML model.
towel_mode_has_valueTrue when the register's reply carried a value byte.
Returns
The hint to log, or NONE.

Definition at line 929 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 275 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 234 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 253 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 295 of file hub_decisions.h.

Here is the call graph for this function:

◆ classify_stopped_report()

StoppedReport esphome::home_io_control::decisions::classify_stopped_report ( const StoppedReportInput & in)
inlinenodiscard

Decide whether a "stopped" report means at rest.

A cover can come to rest off its target and report STOPPED there (corpus captures named on StopCandidate). The device's flag is trusted once two reports agree on the same target and the same current position; the first such report keeps the device "moving" for one more poll. Comparing the target as well keeps a device safe that answers "stopped" with a newly commanded target before its motor starts: the report before the command had the old target, so it can never confirm. With no follow-up poll to confirm by, the device's flag stands, or the device would read moving with nothing left to correct it.

Known bound: a device that answers "stopped, new target, old position" on two polls in a row before its motor starts (a start delay longer than one settle poll, about 3 s) is taken as at rest and polling ends. No captured device does this.

Parameters
inThe report and the hub's memory of the previous one.
Returns
The verdict; stopped_report_at_rest() says whether it means at rest.

Definition at line 1152 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 393 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 320 of file hub_decisions.h.

Here is the call graph for this function:

◆ format_heater_model_name()

std::string esphome::home_io_control::decisions::format_heater_model_name ( const uint8_t * bytes,
size_t len )
inline

Render a heater's model-name register (0x0119, ASCII) for a log line.

Stops at the first NUL, turns any other non-printable byte into '?', and trims trailing spaces, so a padded or garbled reply still logs as one readable line.

Parameters
bytesThe value bytes of the reply.
lenTheir number.
Returns
The name; empty when the reply carried none.

Definition at line 947 of file hub_decisions.h.

Here is the call graph for this function:

◆ frame_issue_is_warning()

bool esphome::home_io_control::decisions::frame_issue_is_warning ( bool dst_registered,
bool dst_is_us )
inline

Whether a frame issue (a frame the hub did not act on) is logged at WARN rather than DEBUG.

WARN only when the frame is addressed to a registered device or to the hub itself: those are exchanges the hub is part of, so an unhandled or rejected frame is worth a look. A frame from a registered device to a broadcast address or to another controller is traffic the hub merely overheard (a heater's menu beacon, a gateway polling the same device), so it stays at DEBUG however well the source is known.

Parameters
dst_registeredFrame destination is a registered device.
dst_is_usFrame destination is the hub's own node ID.

Definition at line 856 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 131 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 124 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 904 of file hub_decisions.h.

Here is the call graph for this function:

◆ is_device_event_report()

bool esphome::home_io_control::decisions::is_device_event_report ( const IoFrame & frame)
inline

Returns true for a frame that reports a device event rather than a status: a CMD_SET_CONFIG1_RESP, or a well-formed CMD_STATUS_UPDATE whose event is not EVENT_STATUS_CHANGE (a subscribed sensor report, see decode_status_update_event()).

Such a frame is recorded on the "Last Device Event" sensor and never decoded as a position, whether it arrives on its own or inside an exchange.

Definition at line 87 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 355 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 81 of file hub_decisions.h.

Here is the call graph for this function:

◆ is_executing()

bool esphome::home_io_control::decisions::is_executing ( ExecutionState state)
inlinenodiscard

True when a device in this state is moving or about to: waiting for its turn (2), waiting for power (3) or executing (4).

A queued cover is part of the move that follows, so it is followed like a running one. 0 (unable to execute), 1 (error during the last move) and 5 (done) are at rest. UNDEFINED is not a state; the caller falls back on the stopped bit (reads_stopped_by_status_byte()).

Parameters
stateState read from byte 0 of a status reply (execution_state_of()).

Definition at line 1094 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 820 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 1033 of file hub_decisions.h.

Here is the call graph for this function:

◆ is_repeat_safe_command()

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

Whether a request may be sent again to a device that said nothing, without a second action.

A device that hears a request acts on it and answers; silence therefore means it did not hear, or its reply was lost. For a request whose copies have one effect (an absolute position, open, close, STOP, a read) repeating it is harmless either way. CMD_EXECUTE with POS_FAVORITE is not (is_repeatable_execute()): on a Somfy motor it stops a moving cover, so a second copy can undo the first. Every request the hub sends that is not a CMD_EXECUTE is idempotent.

Parameters
requestOutbound request frame.

Definition at line 613 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, vent, partial and pedestrian (every NAMED_POSITIONS entry): 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 178 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 748 of file hub_decisions.h.

Here is the call graph for this function:

◆ is_subscription_answer()

bool esphome::home_io_control::decisions::is_subscription_answer ( const IoFrame & request,
const IoFrame & response )
inline

Returns true when response is the answer to the subscription request: the request is a CMD_SUBSCRIBE_EVENT with the subscribe action, and the response a well-formed CMD_STATUS_UPDATE carrying the same event, from the device the request went to.

A device answers a subscription with the event's current value (tests/corpus/captures/probe/somfy_rs100_probe_subscribe_event_*), so inside the exchange the hub started for that request this event report is the reply; every other event report is still recorded and never taken as a reply (is_device_event_report()).

Definition at line 100 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 and UNKNOWN wake on every try (the short lead of an UNKNOWN settling read is an attempt of its own, AttemptPlan::short_lead). 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 787 of file hub_decisions.h.

Here is the call graph for this function:

◆ moving_until_holds()

bool esphome::home_io_control::decisions::moving_until_holds ( const TargetEvidence & evidence,
uint32_t now )
inlinenodiscard

True while the remaining time the receiver last reported has not run out: by its own account it is still travelling, so a silent exchange is not evidence that it has come to rest.

False when no remaining time is known (0). Wrap-safe.

Todo
Untested on hardware: needs a low-power cover that reports a remaining time (ADR 0077).
Parameters
evidenceStamps for the target device.
nowCurrent millis().

Definition at line 741 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 981 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 868 of file hub_decisions.h.

Here is the call graph for this function:

◆ overheard_command_writes()

bool esphome::home_io_control::decisions::overheard_command_writes ( uint8_t cmd)
inline

Returns true for the commands that write to a device: an execute, a mode activation or a private register write.

A heard write changes the device's state, so this hub's view of it is stale; a heard read (any other command) changes nothing on the device. Deliberately a short list of writes rather than a list of reads, so an unknown command counts as a read.

Definition at line 117 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 970 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 1023 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 1038 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 1079 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 1069 of file hub_decisions.h.

Here is the call graph for this function:

◆ reads_stopped_by_status_byte()

bool esphome::home_io_control::decisions::reads_stopped_by_status_byte ( uint8_t status_byte)
inlinenodiscard

Does byte 0 of a status reply say the device is at rest?

The execution state decides (is_executing()). A byte whose bits [2:0] hold 6 or 7 has no state, and its STATUS_STOPPED bit decides as it did before the state was read.

Parameters
status_byteByte 0 of the reply's payload.

Definition at line 1104 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 894 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 and CMD_SUBSCRIBE_EVENT 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.

CMD_SUBSCRIBE_EVENT stores a subscription in the device, and the device answers it with the event's current value (is_subscription_answer()). A try the device authenticated has stored the subscription, but its answer may still have been lost, so it is sent once more (UNCONFIRMED_SUBSCRIBE_MAX_RESENDS), never more: the device has accepted repeated subscriptions (issue #156), and a reading is what the request is for.

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

Here is the call graph for this function:

◆ scheduled_poll_in_retry_band()

bool esphome::home_io_control::decisions::scheduled_poll_in_retry_band ( uint8_t status_poll_failures,
uint8_t auth_poll_failures )
inlinenodiscard

True when a scheduler-owned status poll sits in the middle band of the backoff ladder.

See SCHEDULED_POLL_RETRY_GRACE_FIRST_FAILURE (proto_timing.h) for why the band is where a poll is worth more than one attempt: the slot that lands just after a manoeuvre ends, rather than the start or the tail of the ladder.

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.

Parameters
status_poll_failuresConsecutive silent failures already recorded for this device.
auth_poll_failuresConsecutive challenge-seen failures already recorded.

Definition at line 416 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 )
inline

Transmit-attempt budget for a scheduler-owned status poll of a heater, by ladder position.

Heaters keep this count-based budget; a cover's polls take their plan from scheduled_poll_read_kind() instead.

Parameters
status_poll_failuresConsecutive silent failures already recorded for this device.
auth_poll_failuresConsecutive challenge-seen failures already recorded.
Returns
EXCHANGE_RETRY_COUNT inside the band, SCHEDULED_POLL_MAX_TRIES everywhere else.

Definition at line 429 of file hub_decisions.h.

Here is the call graph for this function:

◆ scheduled_poll_read_kind()

ReadKind esphome::home_io_control::decisions::scheduled_poll_read_kind ( uint8_t status_poll_failures,
uint8_t auth_poll_failures,
bool settles_a_position )
inlinenodiscard

What a cover's status poll is for, from where it sits and what it settles.

A read that decides where the cover ended up is a SETTLING read: the poll after an accepted STOP, the one confirming a cover that stopped short of its target (settles_a_position), and the polls in the ladder's retry band (scheduled_poll_in_retry_band()). Every other scheduled poll is a look at a device that is probably still busy or unreachable (PROGRESS): the ladder is its retry.

Parameters
status_poll_failuresConsecutive silent failures already recorded for this device.
auth_poll_failuresConsecutive challenge-seen failures already recorded.
settles_a_positionTrue for the poll after an accepted STOP or one confirming a stop off target (StatusPollPolicy::take_settling_read()).

Definition at line 629 of file hub_decisions.h.

Here is the call graph for this function:

◆ short_lead_cost_ms()

uint32_t esphome::home_io_control::decisions::short_lead_cost_ms ( uint32_t short_tx_ms)
inlinenodiscard

What the short lead attempt costs the exchange: its transmission, its brief listen and the attempt overhead (the same terms attempt_fits() counts for any attempt).

Parameters
short_tx_msThe lead's transmit time as the radio estimates it (short start preamble).

Definition at line 483 of file hub_decisions.h.

Here is the call graph for this function:

◆ status_update_ack_channel_order()

std::array< uint32_t, 3 > esphome::home_io_control::decisions::status_update_ack_channel_order ( uint32_t receive_freq_hz)
inline

The order in which a 0x72 status-update acknowledgement is sent on the three channels: the channel the 0x71 exchange ran on first, then the other two in their usual CH1, CH2, CH3 order.

The device is still listening on the channel its exchange used, so the copy that matters is not left to the end of the sequence. A frequency that is not one of the three channels gives the usual order.

Parameters
receive_freq_hzFrequency the 0x71 frame (and the challenge exchange) arrived on.
Returns
The three channel frequencies in transmit order.

Definition at line 835 of file hub_decisions.h.

Here is the call graph for this function:

◆ stopped_report_at_rest()

bool esphome::home_io_control::decisions::stopped_report_at_rest ( StoppedReport report)
inlinenodiscard

True when a verdict means the device is at rest.

Definition at line 1167 of file hub_decisions.h.

Here is the call graph for this function:

◆ stopped_report_keeps_candidate()

bool esphome::home_io_control::decisions::stopped_report_keeps_candidate ( StoppedReport report)
inlinenodiscard

True when a verdict leaves an off-target report to remember for the next one.

Kept after a confirmation too, so a later poll that finds the device still resting at the same place confirms at once instead of settling again.

Definition at line 1175 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 731 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,
bool is_status_read = false,
bool state_unknown = false )
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. For a status read with neither moving evidence nor a status report within that time the answer is UNKNOWN; otherwise ASLEEP. 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()).
is_status_readTrue when the request is a status read (CMD_PRIVATE): only a read can be UNKNOWN, a command keeps the rule it had.
state_unknownTrue when the caller marked the read as made without trustworthy news (AttemptRequest::with_state_unknown()): a recent status report then does not make the belief ASLEEP. Only a status read takes it; fresh moving evidence still wins.

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

Here is the call graph for this function:

Variable Documentation

◆ HEATER_MODEL_CHECK_MAX_RUNS

uint8_t esphome::home_io_control::decisions::HEATER_MODEL_CHECK_MAX_RUNS = 3
constexpr

Runs of the one-off heater model check before the hub gives up on it for this boot.

Each run costs at most two register reads, and runs only on a poll the device answered.

Definition at line 912 of file hub_decisions.h.

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

◆ MODEL_NAME_PRINTABLE_FIRST

uint8_t esphome::home_io_control::decisions::MODEL_NAME_PRINTABLE_FIRST = 0x20
constexpr

First and last byte format_heater_model_name() prints as itself: printable ASCII, space to '~'.

Definition at line 938 of file hub_decisions.h.

◆ MODEL_NAME_PRINTABLE_LAST

uint8_t esphome::home_io_control::decisions::MODEL_NAME_PRINTABLE_LAST = 0x7E
constexpr

See MODEL_NAME_PRINTABLE_FIRST.

Definition at line 939 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 1057 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 1059 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 1055 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 1063 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 876 of file hub_decisions.h.