Home IO Control
ESPHome add-on for IO-Homecontrol devices
Loading...
Searching...
No Matches
status_poll_policy.h
Go to the documentation of this file.
1#pragma once
2
3/// @file status_poll_policy.h
4/// @brief Per-device poll scheduling, failure backoff, and follow-up-poll state machine.
5/// @ingroup hioc_hub
6///
7/// Owns all per-device poll bookkeeping (intervals, deadlines, failure counters).
8/// Pure logic with injected timestamps — fully host-testable without a clock mock.
9
10#include <algorithm>
11#include <cstdint>
12#include <map>
13#include <optional>
14#include <string>
15
16namespace esphome {
17namespace home_io_control {
18
19/// @name Background-poll retry delays after silent failures (no reply received)
20/// @{
21static constexpr uint32_t STATUS_RETRY_AFTER_FAIL_MS = 5000; ///< First retry after a silent failure.
22static constexpr uint32_t STATUS_RETRY_AFTER_FAIL_STEP2_MS = 15000; ///< Second retry after a silent failure.
23static constexpr uint32_t STATUS_RETRY_AFTER_FAIL_STEP3_MS = 30000; ///< Third retry after a silent failure.
24static constexpr uint32_t STATUS_RETRY_AFTER_FAIL_STEP4_MS = 60000; ///< Fourth retry after a silent failure.
25static constexpr uint32_t STATUS_RETRY_AFTER_FAIL_MAX_MS =
26 300000; ///< Steady-state backoff after many silent failures.
27/// @}
28
29/// @name Background-poll retry delays after auth-shaped failures (0x3C challenge seen)
30/// @{
31static constexpr uint32_t STATUS_AUTH_RETRY_AFTER_FAIL_MS = 30000; ///< First retry after a challenge-seen failure.
32static constexpr uint32_t STATUS_AUTH_RETRY_AFTER_FAIL_STEP2_MS = 120000; ///< Second retry.
33static constexpr uint32_t STATUS_AUTH_RETRY_AFTER_FAIL_MAX_MS = 300000; ///< Steady-state after repeated auth failures.
34/// @}
35
36/// Hard stop for bounded follow-up polling after a command or remote activity.
37static constexpr uint32_t MAX_TRACKED_STATUS_POLL_WINDOW_MS = 600000;
38/// Delay before the first post-boot status request from an entity.
39static constexpr uint32_t INITIAL_STATUS_REQUEST_DELAY_MS = 5000;
40/// Delay before polling after overheard remote traffic.
41static constexpr uint32_t REMOTE_ACTIVITY_STATUS_POLL_DELAY_MS = 2000;
42/// Hold queued background polls back for this long after any 1W frame, so a poll the hub itself
43/// scheduled does not occupy the (half-duplex) radio while the remote is still transmitting.
44/// Comfortably longer than the ~160ms reliability burst; short enough to barely shift poll timing
45/// otherwise. Only background polls yield — see decisions::defer_background_poll_for_1w_activity().
46static constexpr uint32_t ONEWAY_QUIET_PERIOD_MS = 700;
47/// Hard cap on how long sustained 1W traffic may hold a background poll back in total, measured
48/// from the start of the burst rather than the most recent frame. ONEWAY_QUIET_PERIOD_MS re-arms on
49/// every frame, so without this cap traffic arriving faster than the quiet period apart — a stuck
50/// remote, a chatty neighbour's sensor — could starve background polls, and therefore device state
51/// freshness, indefinitely. Comfortably above a legitimate ~160ms burst; bounds the worst case
52/// without meaningfully affecting normal operation.
53static constexpr uint32_t ONEWAY_POLL_DEFER_CAP_MS = 5000;
54
55/// Default follow-up settle-poll delay used while a device may still be moving, when no explicit
56/// poll interval is configured (interval_ms == 0). Short on purpose so hint-less devices are
57/// re-checked promptly and the motion-tracking loop terminates soon after the device comes to rest.
58static constexpr uint32_t DEFAULT_SETTLE_POLL_DELAY_MS = 3000;
59/// Upper bound on the settle-poll delay after a STOP command. STOP should confirm the resting
60/// position quickly, so it always settles faster than a normal move, regardless of the configured
61/// interval or device hint.
62static constexpr uint32_t STOP_SETTLE_POLL_CAP_MS = 1000;
63
64/// @brief Resolve the follow-up settle-poll delay while a device may still be moving.
65///
66/// This is the single source of truth for the motion-tracking cadence. The delay is the configured
67/// @p interval_ms, or DEFAULT_SETTLE_POLL_DELAY_MS when no interval is configured (interval_ms == 0).
68/// A device-provided byte-7 hint (@p hint_delay_ms, 0 when absent) and a STOP command
69/// (@p cap_for_stop) may only shorten the delay, never lengthen it.
70///
71/// Note: this resolves the *settle* delay only — it is intentionally independent of whether a
72/// device keeps polling after coming to rest. Periodic monitoring after rest remains opt-in and is
73/// gated on interval_ms != 0 by the command paths, so defaulting the settle delay here does not turn
74/// every device into a periodic poller.
75///
76/// @return Settle delay in milliseconds (always non-zero).
77[[nodiscard]] inline uint32_t settle_delay_ms(uint32_t interval_ms, uint32_t hint_delay_ms, bool cap_for_stop) {
78 uint32_t delay = (interval_ms != 0) ? interval_ms : DEFAULT_SETTLE_POLL_DELAY_MS;
79 if (hint_delay_ms != 0)
80 delay = std::min(delay, hint_delay_ms);
81 if (cap_for_stop)
82 delay = std::min(delay, STOP_SETTLE_POLL_CAP_MS);
83 return delay;
84}
85
86/// @brief Per-device poll scheduling state.
88 uint32_t interval_ms{0}; ///< Configured follow-up poll interval (0 = no configured cadence; delays fall back to
89 ///< device hint / default settle delay).
90 uint32_t next_update{0}; ///< Absolute millis() timestamp for the next poll; 0 = idle.
91 uint32_t poll_deadline{0}; ///< Hard stop for bounded follow-up polling.
92 uint8_t status_poll_failures{0}; ///< Consecutive background poll failures (silent — no reply).
93 uint8_t auth_poll_failures{0}; ///< Consecutive background poll failures that saw a 0x3C challenge.
94};
95
96/// @brief Per-hub poll scheduling and failure-backoff policy.
97///
98/// Maintains a PollTracking entry per device; all hub poll bookkeeping lives
99/// here and nowhere else. Callers inject 'now' timestamps so the class is
100/// fully testable without a hardware clock.
102 public:
103 // --- Interval configuration ---
104 /// Set the configured follow-up poll interval (ms; 0 = legacy one-shot settle only).
105 void set_interval(const std::string &device_id, uint32_t interval_ms);
106 /// Return the configured interval (0 when not set or unconfigured).
107 [[nodiscard]] uint32_t get_interval(const std::string &device_id) const;
108
109 // --- Tracking lifecycle ---
110 /// Begin bounded follow-up polling after a command or remote activity.
111 /// Sets a deadline regardless of whether a configured interval exists; interval-0 devices use
112 /// device-hinted or default settle delays computed at response time.
113 void begin_tracking(const std::string &device_id, uint32_t initial_delay_ms, uint32_t now);
114 /// Stop all polling for a device and reset failure counts. Preserves interval_ms.
115 void clear(const std::string &device_id);
116
117 // --- Next-update scheduling ---
118 /// Schedule the next poll at an absolute millis() timestamp.
119 void set_next_update(const std::string &device_id, uint32_t abs_time);
120 [[nodiscard]] uint32_t get_next_update(const std::string &device_id) const;
121
122 // --- Failure tracking ---
123 /// Record a failed background poll; apply backoff or clear tracking if the window expired.
124 /// Returns the backoff delay applied in ms, or 0 if tracking was cleared.
125 uint32_t on_exchange_failed(const std::string &device_id, bool auth_like, uint32_t now);
126 /// Reset failure streaks after a successful exchange response.
127 void clear_failure_streaks(const std::string &device_id);
128 [[nodiscard]] uint8_t get_status_poll_failures(const std::string &device_id) const;
129 [[nodiscard]] uint8_t get_auth_poll_failures(const std::string &device_id) const;
130 [[nodiscard]] uint32_t get_poll_deadline(const std::string &device_id) const;
131
132 // --- Queries ---
133 /// True when the device has an active bounded polling window (deadline set and not expired).
134 [[nodiscard]] bool is_tracking_active(const std::string &device_id, uint32_t now) const;
135
136 // --- Due-device scan ---
137 /// Return and consume the first device whose next_update is overdue.
138 /// Clears expired tracking entries as a side effect. Returns nullopt if nothing is due.
139 [[nodiscard]] std::optional<std::string> pop_due_device(uint32_t now);
140
141 private:
142 std::map<std::string, PollTracking> tracking_;
143
144 /// Compute retry delay from failure count and failure class.
145 static uint32_t retry_delay_ms(uint8_t consecutive_failures, bool auth_like);
146};
147
148} // namespace home_io_control
149} // namespace esphome
Per-hub poll scheduling and failure-backoff policy.
void set_interval(const std::string &device_id, uint32_t interval_ms)
Set the configured follow-up poll interval (ms; 0 = legacy one-shot settle only).
bool is_tracking_active(const std::string &device_id, uint32_t now) const
True when the device has an active bounded polling window (deadline set and not expired).
uint32_t get_next_update(const std::string &device_id) const
uint32_t get_interval(const std::string &device_id) const
Return the configured interval (0 when not set or unconfigured).
void clear_failure_streaks(const std::string &device_id)
Reset failure streaks after a successful exchange response.
void set_next_update(const std::string &device_id, uint32_t abs_time)
Schedule the next poll at an absolute millis() timestamp.
uint32_t on_exchange_failed(const std::string &device_id, bool auth_like, uint32_t now)
Record a failed background poll; apply backoff or clear tracking if the window expired.
void begin_tracking(const std::string &device_id, uint32_t initial_delay_ms, uint32_t now)
Begin bounded follow-up polling after a command or remote activity.
uint8_t get_status_poll_failures(const std::string &device_id) const
uint8_t get_auth_poll_failures(const std::string &device_id) const
uint32_t get_poll_deadline(const std::string &device_id) const
void clear(const std::string &device_id)
Stop all polling for a device and reset failure counts. Preserves interval_ms.
std::optional< std::string > pop_due_device(uint32_t now)
Return and consume the first device whose next_update is overdue.
static constexpr uint32_t STATUS_RETRY_AFTER_FAIL_STEP4_MS
Fourth retry after a silent failure.
uint32_t settle_delay_ms(uint32_t interval_ms, uint32_t hint_delay_ms, bool cap_for_stop)
Resolve the follow-up settle-poll delay while a device may still be moving.
static constexpr uint32_t ONEWAY_QUIET_PERIOD_MS
Hold queued background polls back for this long after any 1W frame, so a poll the hub itself schedule...
static constexpr uint32_t STATUS_AUTH_RETRY_AFTER_FAIL_MAX_MS
Steady-state after repeated auth failures.
static constexpr uint32_t ONEWAY_POLL_DEFER_CAP_MS
Hard cap on how long sustained 1W traffic may hold a background poll back in total,...
static constexpr uint32_t DEFAULT_SETTLE_POLL_DELAY_MS
Default follow-up settle-poll delay used while a device may still be moving, when no explicit poll in...
static constexpr uint32_t REMOTE_ACTIVITY_STATUS_POLL_DELAY_MS
Delay before polling after overheard remote traffic.
static constexpr uint32_t STATUS_RETRY_AFTER_FAIL_STEP3_MS
Third retry after a silent failure.
static constexpr uint32_t MAX_TRACKED_STATUS_POLL_WINDOW_MS
Hard stop for bounded follow-up polling after a command or remote activity.
static constexpr uint32_t STATUS_RETRY_AFTER_FAIL_MS
First retry after a silent failure.
static constexpr uint32_t STATUS_RETRY_AFTER_FAIL_MAX_MS
Steady-state backoff after many silent failures.
static constexpr uint32_t INITIAL_STATUS_REQUEST_DELAY_MS
Delay before the first post-boot status request from an entity.
static constexpr uint32_t STATUS_AUTH_RETRY_AFTER_FAIL_STEP2_MS
Second retry.
static constexpr uint32_t STATUS_AUTH_RETRY_AFTER_FAIL_MS
First retry after a challenge-seen failure.
static constexpr uint32_t STOP_SETTLE_POLL_CAP_MS
Upper bound on the settle-poll delay after a STOP command.
static constexpr uint32_t STATUS_RETRY_AFTER_FAIL_STEP2_MS
Second retry after a silent failure.
Per-device poll scheduling state.
uint8_t auth_poll_failures
Consecutive background poll failures that saw a 0x3C challenge.
uint8_t status_poll_failures
Consecutive background poll failures (silent — no reply).
uint32_t poll_deadline
Hard stop for bounded follow-up polling.
uint32_t interval_ms
Configured follow-up poll interval (0 = no configured cadence; delays fall back to device hint / defa...
uint32_t next_update
Absolute millis() timestamp for the next poll; 0 = idle.