Home IO Control
ESPHome add-on for IO-Homecontrol devices
Loading...
Searching...
No Matches
periodic_poll_policy.cpp
Go to the documentation of this file.
1/// @file periodic_poll_policy.cpp
2/// @brief Schedule and failure bookkeeping for the opt-in periodic polls.
3/// @ingroup hioc_hub
4
6
7#include <algorithm>
8
9namespace esphome {
10namespace home_io_control {
11
12namespace {
13
14/// True when @p deadline has been reached at @p now, correct across the millis() rollover.
15bool reached(uint32_t now, uint32_t deadline) { return static_cast<int32_t>(now - deadline) >= 0; }
16
17} // namespace
18
19void PeriodicPollPolicy::set_interval(const std::string &device_id, uint32_t interval_ms, uint32_t now,
20 uint32_t random) {
21 if (interval_ms == 0) {
22 entries_.erase(device_id);
23 return;
24 }
25 Entry &e = entries_[device_id];
26 e.interval_ms = interval_ms;
27 e.next_poll = now + periodic_poll_first_delay_ms(random);
28 e.failures = 0;
29 e.retry_armed = false;
30 e.polling_retry = false;
31}
32
33uint32_t PeriodicPollPolicy::get_interval(const std::string &device_id) const {
34 const auto it = entries_.find(device_id);
35 return it != entries_.end() ? it->second.interval_ms : 0;
36}
37
38bool PeriodicPollPolicy::has_due(uint32_t now) const {
39 return std::any_of(entries_.begin(), entries_.end(),
40 [now](const auto &entry) { return reached(now, entry.second.next_poll); });
41}
42
43std::optional<std::string> PeriodicPollPolicy::pop_due_device(uint32_t now, uint32_t random) {
44 for (auto &[device_id, e] : entries_) {
45 if (!reached(now, e.next_poll))
46 continue;
47 e.next_poll = now + periodic_poll_jittered_interval_ms(e.interval_ms, random);
48 // A retry dropped from the queue (pairing flushes background work) leaves no armed flag
49 // behind: the pop consumes it either way.
50 e.polling_retry = e.retry_armed;
51 e.retry_armed = false;
52 return device_id;
53 }
54 return std::nullopt;
55}
56
57void PeriodicPollPolicy::defer(const std::string &device_id, uint32_t delay_ms, uint32_t now) {
58 const auto it = entries_.find(device_id);
59 if (it == entries_.end())
60 return;
61 const uint32_t retry_at = now + delay_ms;
62 if (reached(it->second.next_poll, retry_at)) // next_poll is at or after retry_at: pull it earlier
63 it->second.next_poll = retry_at;
64}
65
66void PeriodicPollPolicy::restart_interval(const std::string &device_id, uint32_t now, uint32_t random) {
67 const auto it = entries_.find(device_id);
68 if (it == entries_.end())
69 return;
70 it->second.next_poll = now + periodic_poll_jittered_interval_ms(it->second.interval_ms, random);
71 it->second.retry_armed = false;
72}
73
74bool PeriodicPollPolicy::on_poll_failed(const std::string &device_id) {
75 const auto it = entries_.find(device_id);
76 if (it == entries_.end())
77 return false;
78 if (it->second.failures < UINT8_MAX)
79 it->second.failures++;
80 return it->second.failures >= PERIODIC_POLL_FAILURES_BEFORE_UNKNOWN;
81}
82
83bool PeriodicPollPolicy::on_poll_unanswered(const std::string &device_id, uint32_t now) {
84 const auto it = entries_.find(device_id);
85 if (it == entries_.end())
86 return false;
87 Entry &e = it->second;
88 if (e.polling_retry)
89 return this->on_poll_failed(device_id);
90 e.retry_armed = true;
91 this->defer(device_id, PERIODIC_POLL_MISS_RETRY_MS, now);
92 return e.failures >= PERIODIC_POLL_FAILURES_BEFORE_UNKNOWN;
93}
94
95bool PeriodicPollPolicy::retry_armed(const std::string &device_id) const {
96 const auto it = entries_.find(device_id);
97 return it != entries_.end() && it->second.retry_armed;
98}
99
100void PeriodicPollPolicy::on_poll_succeeded(const std::string &device_id) {
101 const auto it = entries_.find(device_id);
102 if (it != entries_.end())
103 it->second.failures = 0;
104}
105
106bool PeriodicPollPolicy::first_error_reply(const std::string &device_id) {
107 const auto it = entries_.find(device_id);
108 if (it == entries_.end() || it->second.error_reported)
109 return false;
110 it->second.error_reported = true;
111 return true;
112}
113
114uint8_t PeriodicPollPolicy::get_failures(const std::string &device_id) const {
115 const auto it = entries_.find(device_id);
116 return it != entries_.end() ? it->second.failures : 0;
117}
118
119uint32_t PeriodicPollPolicy::get_next_poll(const std::string &device_id) const {
120 const auto it = entries_.find(device_id);
121 return it != entries_.end() ? it->second.next_poll : 0;
122}
123
124} // namespace home_io_control
125} // namespace esphome
uint32_t get_next_poll(const std::string &device_id) const
Absolute millis() time of the device's next poll (0 for an unscheduled device).
void defer(const std::string &device_id, uint32_t delay_ms, uint32_t now)
Pull the device's next poll forward to delay_ms after now. Never pushes it later.
uint32_t get_interval(const std::string &device_id) const
Return the configured interval (0 when the device is not scheduled).
std::optional< std::string > pop_due_device(uint32_t now, uint32_t random)
Return one device whose poll is due and immediately schedule its next poll at periodic_poll_jittered_...
void restart_interval(const std::string &device_id, uint32_t now, uint32_t random)
The device has just given what the poll asks for without being polled: schedule its next poll periodi...
void on_poll_succeeded(const std::string &device_id)
Clear the unanswered-poll count.
bool on_poll_failed(const std::string &device_id)
Count one failed poll (saturating), with no retry: for a poll the device answered with something that...
bool first_error_reply(const std::string &device_id)
True the first time it is called for a device that answered the poll's request with an error reply,...
uint8_t get_failures(const std::string &device_id) const
Consecutive failed polls (0 for an unscheduled device).
bool has_due(uint32_t now) const
True when any device's poll is due at now.
bool retry_armed(const std::string &device_id) const
True while a retry armed by on_poll_unanswered() has not been popped yet.
bool on_poll_unanswered(const std::string &device_id, uint32_t now)
Handle a poll the device left unanswered.
void set_interval(const std::string &device_id, uint32_t interval_ms, uint32_t now, uint32_t random)
Set the poll interval and schedule the first poll at periodic_poll_first_delay_ms(random) after now.
uint32_t periodic_poll_jittered_interval_ms(uint32_t interval_ms, uint32_t random)
One poll interval moved by a uniform offset of up to PERIODIC_POLL_JITTER_PERCENT either way.
uint32_t periodic_poll_first_delay_ms(uint32_t random)
Delay of a device's first poll after its interval is set.
Schedule and failure bookkeeping for the opt-in periodic polls.