Home IO Control
ESPHome add-on for IO-Homecontrol devices
Loading...
Searching...
No Matches
operation_queue.cpp
Go to the documentation of this file.
1/// @file operation_queue.cpp
2/// @brief OperationQueue implementation.
3/// @ingroup hioc_hub
4
5#include "operation_queue.h"
6
7#include <algorithm>
8
9namespace esphome {
10namespace home_io_control {
11
17
18void OperationQueue::push_control_(PendingOperation op) {
19 // The stale-poll drop assumes `device_id` names a device whose reply will supersede the poll.
20 // For the ONEWAY_* types it names a controller identity instead, and a 1W command produces no
21 // reply to supersede anything — so a handle that happened to match a device ID would silently
22 // cancel that device's status poll. Exclude them rather than rely on the two namespaces never
23 // colliding.
24 const bool addresses_a_device =
27 if (addresses_a_device) {
28 queue_.erase(std::remove_if(queue_.begin(), queue_.end(),
29 [&op](const PendingOperation &existing) {
30 return existing.type == PendingOperationType::REQUEST_STATUS &&
31 existing.device_id == op.device_id;
32 }),
33 queue_.end());
34 }
35 auto it =
36 std::find_if(queue_.begin(), queue_.end(), [](const PendingOperation &o) { return is_background_op(o.type); });
37 queue_.insert(it, std::move(op));
38}
39
40bool OperationQueue::enqueue_set_position(const std::string &device_id, uint8_t position) {
41 for (auto &op : queue_) {
42 if (op.type == PendingOperationType::SET_TILT && op.device_id == device_id) {
43 // SET_TILT stores tilt in op.position (the only field available on standalone tilt ops).
44 uint8_t const pending_tilt = op.position;
46 op.position = position;
47 op.tilt = pending_tilt;
48 return true;
49 }
50 }
51 push_control_({PendingOperationType::SET_POSITION, device_id, position});
52 return false;
53}
54
55bool OperationQueue::enqueue_set_tilt(const std::string &device_id, uint8_t tilt_percent) {
56 for (auto &op : queue_) {
57 if (op.type == PendingOperationType::SET_POSITION && op.device_id == device_id) {
59 op.tilt = tilt_percent;
60 // op.position already holds the correct position from the original SET_POSITION enqueue.
61 return true;
62 }
63 }
64 push_control_({PendingOperationType::SET_TILT, device_id, tilt_percent});
65 return false;
66}
67
68void OperationQueue::enqueue_set_position_and_tilt(const std::string &device_id, uint8_t position,
69 uint8_t tilt_percent) {
70 push_control_({PendingOperationType::SET_POSITION_AND_TILT, device_id, position, tilt_percent});
71}
72
73void OperationQueue::enqueue_device_command(const std::string &device_id, CoverCommand cmd) {
76 op.device_id = device_id;
77 op.command = cmd;
78 push_control_(std::move(op));
79}
80
81void OperationQueue::enqueue_oneway_command(const std::string &controller_id, CoverCommand cmd) {
84 op.device_id = controller_id;
85 op.command = cmd;
86 push_control_(std::move(op));
87}
88
89void OperationQueue::enqueue_oneway_position(const std::string &controller_id, uint8_t position) {
90 push_control_({PendingOperationType::ONEWAY_POSITION, controller_id, position});
91}
92
93void OperationQueue::enqueue_oneway_enroll(const std::string &controller_id) {
94 push_control_({PendingOperationType::ONEWAY_ENROLL, controller_id, 0});
95}
96
97void OperationQueue::enqueue_oneway_unenroll(const std::string &controller_id) {
98 push_control_({PendingOperationType::ONEWAY_UNENROLL, controller_id, 0});
99}
100
101void OperationQueue::enqueue_set_light_position(const std::string &device_id, uint8_t position) {
102 push_control_({PendingOperationType::SET_LIGHT_STATE, device_id, position});
103}
104
105void OperationQueue::enqueue_set_fan_position(const std::string &device_id, uint8_t position) {
106 push_control_({PendingOperationType::SET_FAN_POSITION, device_id, position});
107}
108
109void OperationQueue::enqueue_set_light_state(const std::string &device_id, bool on) {
110 this->enqueue_set_light_position(device_id, on ? BINARY_ENTITY_ON_POSITION : BINARY_ENTITY_OFF_POSITION);
111}
112
113void OperationQueue::enqueue_set_lock_state(const std::string &device_id, bool locked) {
114 push_control_({PendingOperationType::SET_LOCK_STATE, device_id,
115 locked ? BINARY_ENTITY_OFF_POSITION : BINARY_ENTITY_ON_POSITION});
116}
117
118void OperationQueue::enqueue_set_switch_state(const std::string &device_id, bool on) {
119 push_control_(
120 {PendingOperationType::SET_SWITCH_STATE, device_id, on ? BINARY_ENTITY_ON_POSITION : BINARY_ENTITY_OFF_POSITION});
121}
122
123bool OperationQueue::enqueue_background_unique_(PendingOperationType type, const std::string &device_id) {
124 for (const auto &op : queue_) {
125 if (op.type == type && op.device_id == device_id)
126 return false;
127 }
128 queue_.push_back({type, device_id, 0});
129 return true;
130}
131
132bool OperationQueue::enqueue_request_status(const std::string &device_id) {
133 return enqueue_background_unique_(PendingOperationType::REQUEST_STATUS, device_id);
134}
135
136bool OperationQueue::enqueue_request_name(const std::string &device_id) {
137 return enqueue_background_unique_(PendingOperationType::REQUEST_NAME, device_id);
138}
139
140bool OperationQueue::enqueue_request_limitation(const std::string &device_id) {
141 return enqueue_background_unique_(PendingOperationType::REQUEST_LIMITATION, device_id);
142}
143
144bool OperationQueue::enqueue_request_sun_level(const std::string &device_id) {
145 return enqueue_background_unique_(PendingOperationType::REQUEST_SUN_LEVEL, device_id);
146}
147
148bool OperationQueue::enqueue_request_battery_level(const std::string &device_id) {
149 return enqueue_background_unique_(PendingOperationType::REQUEST_BATTERY_LEVEL, device_id);
150}
151
153 for (const auto &op : queue_) {
155 return false;
156 }
157 queue_.erase(std::remove_if(queue_.begin(), queue_.end(),
158 [](const PendingOperation &op) { return is_background_op(op.type); }),
159 queue_.end());
160 queue_.push_front({PendingOperationType::DISCOVER_AND_PAIR, {}, 0});
161 return true;
162}
163
164std::optional<PendingOperation> OperationQueue::pop() {
165 if (queue_.empty())
166 return std::nullopt;
167 PendingOperation op = std::move(queue_.front());
168 queue_.pop_front();
169 return op;
170}
171
172bool OperationQueue::empty() const { return queue_.empty(); }
173
174std::size_t OperationQueue::size() const { return queue_.size(); }
175
176const PendingOperation &OperationQueue::front() const { return queue_.front(); }
177
178const PendingOperation &OperationQueue::back() const { return queue_.back(); }
179
180const PendingOperation &OperationQueue::operator[](std::size_t index) const { return queue_[index]; }
181
182std::deque<PendingOperation>::const_iterator OperationQueue::begin() const { return queue_.cbegin(); }
183
184std::deque<PendingOperation>::const_iterator OperationQueue::end() const { return queue_.cend(); }
185
186} // namespace home_io_control
187} // namespace esphome
std::deque< PendingOperation >::const_iterator begin() const
const PendingOperation & front() const
void enqueue_oneway_unenroll(const std::string &controller_id)
Enqueue a 1W un-enrollment (remove-controller) for a controller identity.
void enqueue_set_lock_state(const std::string &device_id, bool locked)
void enqueue_set_position_and_tilt(const std::string &device_id, uint8_t position, uint8_t tilt_percent)
Enqueue SET_POSITION_AND_TILT directly (no coalescing needed).
void enqueue_oneway_command(const std::string &controller_id, CoverCommand cmd)
Enqueue a 1W named command for a controller identity.
std::optional< PendingOperation > pop()
void enqueue_device_command(const std::string &device_id, CoverCommand cmd)
bool enqueue_request_status(const std::string &device_id)
Enqueue REQUEST_STATUS, suppressing duplicates.
bool enqueue_request_name(const std::string &device_id)
Enqueue REQUEST_NAME, suppressing duplicates.
bool enqueue_request_sun_level(const std::string &device_id)
Enqueue REQUEST_SUN_LEVEL, suppressing duplicates.
bool enqueue_request_limitation(const std::string &device_id)
Enqueue REQUEST_LIMITATION, suppressing duplicates.
void enqueue_set_fan_position(const std::string &device_id, uint8_t position)
Enqueue SET_FAN_POSITION with an IO position (0-100; 0 = full heat, 100 = off).
void enqueue_set_switch_state(const std::string &device_id, bool on)
void enqueue_oneway_position(const std::string &controller_id, uint8_t position)
Enqueue a 1W numeric position for a controller identity.
bool enqueue_discover_and_pair()
Enqueue DISCOVER_AND_PAIR with elevated priority.
static bool is_background_op(PendingOperationType t)
True for background poll types (REQUEST_STATUS, REQUEST_NAME and the sensor polls) that yield to cont...
std::deque< PendingOperation >::const_iterator end() const
bool enqueue_set_position(const std::string &device_id, uint8_t position)
Enqueue SET_POSITION, or coalesce with a pending SET_TILT into SET_POSITION_AND_TILT.
const PendingOperation & back() const
bool enqueue_request_battery_level(const std::string &device_id)
Enqueue REQUEST_BATTERY_LEVEL, suppressing duplicates.
void enqueue_oneway_enroll(const std::string &controller_id)
Enqueue a 1W enrollment (add-controller) for a controller identity.
void enqueue_set_light_state(const std::string &device_id, bool on)
void enqueue_set_light_position(const std::string &device_id, uint8_t position)
Enqueue SET_LIGHT_STATE with an arbitrary IO position (0-100), for dimmable lights.
const PendingOperation & operator[](std::size_t index) const
bool enqueue_set_tilt(const std::string &device_id, uint8_t tilt_percent)
Enqueue SET_TILT, or coalesce with a pending SET_POSITION into SET_POSITION_AND_TILT.
PendingOperationType
Discriminator for entries in the pending-operation deque.
@ SET_LOCK_STATE
set_lock_state call (locked/unlocked).
@ SET_TILT
set_device_tilt call (tilt percentage 0–100).
@ SET_POSITION_AND_TILT
Combined set_device_position_and_tilt call.
@ SET_LIGHT_STATE
set_light_state call (binary on/off).
@ SET_FAN_POSITION
set_fan_position call (heat level of an exterior heater as an IO position).
@ REQUEST_BATTERY_LEVEL
request_device_battery_level call (subscribe to EVENT_BATTERY_LEVEL_CHANGE, read the answer).
@ SET_SWITCH_STATE
set_switch_state call (binary on/off).
@ REQUEST_NAME
request_device_name call (poll for stored device name).
@ ONEWAY_COMMAND
1W named command sent as a controller identity.
@ REQUEST_SUN_LEVEL
request_device_sun_level call (subscribe to EVENT_LOCAL_SENSOR, read the answer).
@ DISCOVER_AND_PAIR
discover_and_pair call (starts 3-phase pairing flow).
@ REQUEST_STATUS
request_device_status call (poll for current position).
@ ONEWAY_UNENROLL
1W remove-controller (0x39) un-registering an identity.
@ DEVICE_COMMAND
Named device command (STOP, FAVORITE, VENT).
@ SET_POSITION
set_device_position call (position 0–100 or special values).
@ REQUEST_LIMITATION
request_device_limitation call (read the device's minimum limitation).
@ ONEWAY_ENROLL
1W add-controller (0x30) registering an identity.
@ ONEWAY_POSITION
1W numeric position sent as a controller identity.
CoverCommand
Named device commands for cover-type actuators.
Pending-operation queue with per-type coalescing and deduplication.
A single queued operation to be dispatched from loop().
CoverCommand command
Named command for DEVICE_COMMAND operations.
std::string device_id
Target device ID (hex string, e.g., "123ABC") — except for the ONEWAY_* types, where it carries the c...
uint8_t tilt
Tilt value for SET_POSITION_AND_TILT (0–100).
uint8_t position
Position/tilt value (0–100) or binary state (ON=0, OFF=100).
PendingOperationType type
Operation type (determines which handler to invoke).