Home IO Control
ESPHome add-on for IO-Homecontrol devices
Loading...
Searching...
No Matches
rain_poll_policy.h
Go to the documentation of this file.
1#pragma once
2
3/// @file rain_poll_policy.h
4/// @brief Schedule and failure bookkeeping for the opt-in rain sensor poll.
5/// @ingroup hioc_hub
6///
7/// Owns the per-device periodic schedule of the limitation read behind `rain_sensor_poll_interval`.
8/// Kept apart from StatusPollPolicy on purpose: that one runs bounded tracking windows which end
9/// when a device stops, this one is an unbounded periodic schedule. Pure logic with injected
10/// timestamps and random numbers — fully host-testable without a clock or a random source.
11
12#include <cstdint>
13#include <map>
14#include <optional>
15#include <string>
16
17namespace esphome {
18namespace home_io_control {
19
20/// Earliest first poll after boot. Sits after the entities' initial status request
21/// (INITIAL_STATUS_REQUEST_DELAY_MS) so the boot burst is spread over time.
22static constexpr uint32_t RAIN_POLL_INITIAL_DELAY_MS = 15000;
23/// Width of the random window added to RAIN_POLL_INITIAL_DELAY_MS, so devices do not start in step.
24static constexpr uint32_t RAIN_POLL_INITIAL_SPREAD_MS = 30000;
25/// Per-poll offset, as a percentage of the interval, applied symmetrically around it. At the
26/// one-minute minimum that is six seconds either way — more than one exchange lasts — so two
27/// devices sharing an interval drift apart within a few polls. Symmetric on purpose: the mean stays
28/// the configured interval, so the transmit time per hour in the documentation stays true.
29static constexpr uint32_t RAIN_POLL_JITTER_PERCENT = 10;
30/// Delay before asking again when the device is moving. Keeps the poll out of a running manoeuvre
31/// without waiting a whole interval.
32static constexpr uint32_t RAIN_POLL_MOVING_RETRY_MS = 30000;
33/// Tries per poll. A missed poll only costs one interval of latency, so the full retry budget is
34/// not worth its transmit time here.
35static constexpr uint8_t RAIN_POLL_MAX_TRIES = 2;
36/// Consecutive unanswered polls after which the last reading is no longer usable.
37static constexpr uint8_t RAIN_POLL_FAILURES_BEFORE_UNKNOWN = 3;
38
39/// @brief Delay of a device's first poll after its interval is set.
40/// @param random A uniformly distributed 32-bit random number.
41/// @return RAIN_POLL_INITIAL_DELAY_MS plus an offset in [0, RAIN_POLL_INITIAL_SPREAD_MS].
42[[nodiscard]] inline uint32_t rain_poll_first_delay_ms(uint32_t random) {
44}
45
46/// @brief One poll interval moved by a uniform offset of up to RAIN_POLL_JITTER_PERCENT either way.
47///
48/// The offset scales @p random over the whole 32-bit range, so 0 gives the lower bound, UINT32_MAX
49/// the upper bound and the middle of the range the unmodified interval. Computed in 64 bit so a
50/// 24 hour interval cannot overflow.
51/// @param interval_ms The configured interval.
52/// @param random A uniformly distributed 32-bit random number.
53/// @return The delay until the next poll, in [interval - jitter, interval + jitter].
54[[nodiscard]] inline uint32_t rain_poll_jittered_interval_ms(uint32_t interval_ms, uint32_t random) {
55 const uint64_t jitter = static_cast<uint64_t>(interval_ms) * RAIN_POLL_JITTER_PERCENT / 100;
56 const uint64_t offset = (static_cast<uint64_t>(random) * 2 * jitter) / UINT32_MAX; // 0 .. 2 * jitter
57 return static_cast<uint32_t>(interval_ms - jitter + offset);
58}
59
60/// @brief Per-device periodic limitation-read schedule and miss counter.
62 public:
63 /// Set the poll interval and schedule the first poll at rain_poll_first_delay_ms(@p random)
64 /// after @p now. 0 removes the device from the schedule.
65 void set_interval(const std::string &device_id, uint32_t interval_ms, uint32_t now, uint32_t random);
66 /// Return the configured interval (0 when the device is not scheduled).
67 [[nodiscard]] uint32_t get_interval(const std::string &device_id) const;
68
69 /// True when any device's poll is due at @p now. Lets the caller draw a random number only when
70 /// pop_due_device() will use it.
71 [[nodiscard]] bool has_due(uint32_t now) const;
72
73 /// Return one device whose poll is due and immediately schedule its next poll at
74 /// rain_poll_jittered_interval_ms() after @p now, so a poll that is later dropped from the
75 /// operation queue (pairing flushes background work) is skipped rather than lost forever.
76 /// Returns nullopt when nothing is due. Comparison is wrap-safe across the 32-bit millis() rollover.
77 [[nodiscard]] std::optional<std::string> pop_due_device(uint32_t now, uint32_t random);
78
79 /// Pull the device's next poll forward to @p delay_ms after @p now. Never pushes it later.
80 void defer(const std::string &device_id, uint32_t delay_ms, uint32_t now);
81
82 /// Count one unanswered poll (saturating).
83 /// @return True once RAIN_POLL_FAILURES_BEFORE_UNKNOWN is reached, i.e. the last reading is no
84 /// longer usable. Stays true until on_poll_succeeded().
85 bool on_poll_failed(const std::string &device_id);
86 /// Clear the unanswered-poll count.
87 void on_poll_succeeded(const std::string &device_id);
88 /// True the first time it is called for a device that answered the limitation read with an error
89 /// reply, false afterwards, so the log names that once instead of every poll.
90 [[nodiscard]] bool first_error_reply(const std::string &device_id);
91 /// Consecutive unanswered polls (0 for an unscheduled device).
92 [[nodiscard]] uint8_t get_failures(const std::string &device_id) const;
93 /// Absolute millis() time of the device's next poll (0 for an unscheduled device).
94 [[nodiscard]] uint32_t get_next_poll(const std::string &device_id) const;
95
96 private:
97 /// Schedule state of one device.
98 struct Entry {
99 uint32_t interval_ms{0}; ///< Configured interval.
100 uint32_t next_poll{0}; ///< Absolute millis() time of the next poll.
101 uint8_t failures{0}; ///< Consecutive unanswered polls.
102 bool error_reported{false}; ///< True once first_error_reply() has returned true.
103 };
104
105 std::map<std::string, Entry> entries_; ///< Scheduled devices, keyed by device id.
106};
107
108} // namespace home_io_control
109} // namespace esphome
Per-device periodic limitation-read schedule and miss counter.
uint8_t get_failures(const std::string &device_id) const
Consecutive unanswered polls (0 for an unscheduled device).
bool has_due(uint32_t now) const
True when any device's poll is due at now.
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 rain_poll_first_delay_ms(random) after now.
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 rain_poll_jittered_inte...
uint32_t get_interval(const std::string &device_id) const
Return the configured interval (0 when the device is not scheduled).
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.
bool first_error_reply(const std::string &device_id)
True the first time it is called for a device that answered the limitation read with an error reply,...
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 unanswered poll (saturating).
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).
static constexpr uint8_t RAIN_POLL_MAX_TRIES
Tries per poll.
uint32_t rain_poll_first_delay_ms(uint32_t random)
Delay of a device's first poll after its interval is set.
static constexpr uint8_t RAIN_POLL_FAILURES_BEFORE_UNKNOWN
Consecutive unanswered polls after which the last reading is no longer usable.
uint32_t rain_poll_jittered_interval_ms(uint32_t interval_ms, uint32_t random)
One poll interval moved by a uniform offset of up to RAIN_POLL_JITTER_PERCENT either way.
static constexpr uint32_t RAIN_POLL_INITIAL_SPREAD_MS
Width of the random window added to RAIN_POLL_INITIAL_DELAY_MS, so devices do not start in step.
static constexpr uint32_t RAIN_POLL_INITIAL_DELAY_MS
Earliest first poll after boot.
static constexpr uint32_t RAIN_POLL_JITTER_PERCENT
Per-poll offset, as a percentage of the interval, applied symmetrically around it.
static constexpr uint32_t RAIN_POLL_MOVING_RETRY_MS
Delay before asking again when the device is moving.