Home IO Control
ESPHome add-on for IO-Homecontrol devices
Loading...
Searching...
No Matches
platform_entity_base.h
Go to the documentation of this file.
1#pragma once
2
3/// @file platform_entity_base.h
4/// @brief Shared device-binding mixins for IO-Homecontrol entity platforms.
5/// @ingroup hioc_platforms
6///
7/// IOHomeCover, IOHomeLight, IOHomeSwitch and IOHomeLock all bind an ESPHome entity to a hub
8/// device: the same five YAML setters, the same registration ritual in setup(), and the same
9/// poll-interval dump_config line. DeviceBoundEntity centralizes exactly that shared state and
10/// wiring. The auto-generated companion diagnostic sensors share a smaller, observe-only
11/// binding; DeviceBoundCompanion centralizes that one. A third mixin, HubBoundEntity, holds the
12/// single parent pointer that the hub-level control entities (which act on the hub as a whole and
13/// have no device to bind to) would otherwise each restate.
14///
15/// All three are intentionally NOT ESPHome base classes — they are plain mixins the entities inherit
16/// alongside their real ESPHome base (cover::Cover, light::LightOutput, switch_::Switch,
17/// lock::Lock, text_sensor::TextSensor, sensor::Sensor). Entity-specific state mapping (cover
18/// position/tilt/movement inference, binary on/off decoding, each companion's value rendering)
19/// deliberately stays in the entities; only the device-binding plumbing lives here.
20
21#include "hub_core.h"
22
23#include "esphome/core/application.h"
24#include "esphome/core/log.h"
25
26#include <cinttypes>
27#include <functional>
28#include <string>
29#include <utility>
30
31namespace esphome {
32namespace home_io_control {
33
34/// @brief Mixin holding the hub-device binding shared by all IO-Homecontrol entity platforms.
35/// @ingroup hioc_platforms
37 public:
38 /// @brief Set the parent controller component.
39 /// @param parent Pointer to the IOHomeControlComponent instance.
40 void set_parent(IOHomeControlComponent *parent) { this->parent_ = parent; }
41 /// @brief Set the unique IO-homecontrol device ID (from YAML).
42 /// @param id Hexadecimal node ID string (e.g., "123ABC").
43 void set_device_id(const std::string &id) { this->device_id_ = id; }
44 /// @brief Set the declared device type (from YAML).
45 /// @param type Device type enum.
46 void set_device_type(DeviceType type) { this->device_type_ = type; }
47 /// @brief Set the declared device subtype (from YAML).
48 /// @param subtype Subtype value.
49 void set_subtype(uint8_t subtype) { this->subtype_ = subtype; }
50 /// @brief Enable or disable optimistic target updates for this device (from YAML).
51 /// Only covers expose this in YAML today; other platforms keep the default (true), which is
52 /// inert for entity types that never consult the effective target / movement state.
53 /// @param optimistic_state False to disable optimistic state; default true.
54 void set_optimistic_state(bool optimistic_state) { this->optimistic_state_ = optimistic_state; }
55 /// @brief Send this device's position moves in "silent operation" mode — the reference hub's
56 /// slower travel profile. Cover-only in practice; harmless on other platforms, which never
57 /// issue position moves.
58 void set_silent(bool silent) { this->silent_ = silent; }
59 /// @brief Mark this device as a low-power / duty-cycled receiver. Directed frames to it then set
60 /// CTRL1_LOW_POWER and use the long wake-up preamble; an always-alive device (the default) gets
61 /// neither. Radio property of the device, shared by all four platforms.
62 /// @param low_power True for a battery/solar or otherwise sleeping receiver; default false.
63 void set_low_power(bool low_power) { this->low_power_ = low_power; }
64 /// @brief Configure bounded follow-up polling while a state change is expected.
65 /// @param poll_interval_ms Poll interval in milliseconds; zero keeps the default single settle poll only.
66 void set_status_poll_interval(uint32_t poll_interval_ms) { this->status_poll_interval_ms_ = poll_interval_ms; }
67
68 protected:
69 /// @brief Perform the shared setup() registration ritual.
70 ///
71 /// Registers the device with the controller, sets its status-poll interval, subscribes the
72 /// entity's update callback, and schedules the delayed initial status request — in the exact
73 /// order every entity used before.
74 /// @param self The entity itself; used for set_timeout(), since DeviceBoundEntity is not a Component.
75 /// @param inverted Initial inversion flag passed to add_device (covers compute it; others pass false).
76 /// @param on_update The entity's device-update callback.
77 /// @param schedule_initial_poll True (the default) to schedule the delayed initial status
78 /// request every position/binary/lock entity relies on. The climate entity passes false:
79 /// heating devices have no status readback at all (CMD_WRITE_PRIVATE is write-only), so
80 /// a status poll would only draw a "status request rejected" warning at boot.
81 void register_device_binding_(Component *self, bool inverted,
82 std::function<void(const std::string &, const IoDevice &)> on_update,
83 bool schedule_initial_poll = true) {
84 this->parent_->add_device(this->device_id_, DeviceConfig{this->device_type_, this->subtype_, inverted,
85 this->optimistic_state_, this->silent_, this->low_power_});
87 this->parent_->register_device_callback(std::move(on_update));
88 if (!schedule_initial_poll)
89 return;
90 // Schedule the delayed initial poll through the public scheduler: Component::set_timeout is
91 // protected, and DeviceBoundEntity is a mixin, not a Component subclass, so it cannot call it
92 // through `self`. App.scheduler.set_timeout is the public entry point set_timeout wraps.
93 App.scheduler.set_timeout(self, "init_status", INITIAL_STATUS_REQUEST_DELAY_MS,
94 [this]() { this->parent_->queue_request_device_status(this->device_id_); });
95 }
96
97 /// @brief Shared inbound-update guard for binary endpoints.
98 ///
99 /// Matches this device and accepts only a settled (stopped) status with a known position.
100 /// Covers and locks intentionally use their own richer guards instead of this filter.
101 [[nodiscard]] bool passes_binary_update_filter_(const std::string &id, const IoDevice &dev) const {
102 return id == this->device_id_ && dev.position != UNKNOWN_POSITION && effective_is_stopped(dev);
103 }
104
105 /// @brief Emit the shared two-branch poll-interval line for dump_config().
106 /// @param tag Log tag of the calling entity.
107 void log_poll_interval_config_(const char *tag) const {
108 if (this->status_poll_interval_ms_ == 0) {
109 ESP_LOGCONFIG(tag, " Status Poll Interval: device-hinted settle polling (no fixed interval)");
110 } else {
111 ESP_LOGCONFIG(tag, " Status Poll Interval: %" PRIu32 " ms", this->status_poll_interval_ms_);
112 }
113 }
114
116 std::string device_id_;
118 uint8_t subtype_{0};
121 bool silent_{false};
122 bool low_power_{false};
123};
124
125/// @brief Mixin holding the parent + device-id binding shared by per-device entities that are
126/// not full entity platforms: the auto-generated companion diagnostic sensors (device name,
127/// active issue, RSSI, last contact, exchange failures, last commanded by, last command source)
128/// and the per-device auxiliary control entities (cover favorite/vent buttons, cover silent
129/// switch).
130/// @ingroup hioc_platforms
131///
132/// These differ from the main entity platforms (DeviceBoundEntity above) in that they do not own
133/// the device: they never call add_device() or configure polling. The companion sensors use
134/// register_companion_binding_() to subscribe to update notifications and republish their one
135/// value; the auxiliary control entities take only the parent + device-id pair and act on their
136/// already-registered device on demand, so register_companion_binding_() stays opt-in. Each class
137/// keeps its own value rendering / action logic (including whether a given device state is
138/// publishable at all).
140 public:
141 /// @brief Set the parent controller component.
142 /// @param parent Pointer to the IOHomeControlComponent instance.
143 void set_parent(IOHomeControlComponent *parent) { this->parent_ = parent; }
144 /// @brief Set the device ID whose state this companion sensor exposes.
145 /// @param id Hexadecimal node ID string (for example "123ABC").
146 void set_device_id(const std::string &id) { this->device_id_ = id; }
147
148 protected:
149 /// @brief Shared setup() body: subscribe to this device's updates and publish the initial state.
150 ///
151 /// No-op when no parent is wired (codegen always wires one before setup() runs).
152 /// @param publish Renders and publishes the sensor's value from a device record — or skips
153 /// publishing when the record has no meaningful value yet. Runs once immediately when the
154 /// device is already registered, then again on every update notification for this device.
155 void register_companion_binding_(const std::function<void(const IoDevice &)> &publish) {
156 if (this->parent_ == nullptr)
157 return;
158
159 this->parent_->register_device_callback([this, publish](const std::string &id, const IoDevice &dev) {
160 if (id == this->device_id_)
161 publish(dev);
162 });
163
164 if (const auto *dev = this->parent_->get_device(this->device_id_); dev != nullptr)
165 publish(*dev);
166 }
167
169 std::string device_id_;
170};
171
172/// @brief Mixin for entities bound to the hub itself rather than to one device.
173/// @ingroup hioc_platforms
174///
175/// The hub-level control entities (discover button, scan-paired-devices button, arming switches,
176/// firmware-update button, 1W command/enroll buttons, pairing-result and 1W-last-command text
177/// sensors) need only a parent
178/// pointer — they act on the hub as a whole and have no device to bind to. This holds that one
179/// setter and one member so each entity does not restate it. The tuning number/select entities
180/// are deliberately not migrated: they take the parent by constructor injection and keep
181/// hub_core.h out of their header via a forward declaration, which this mixin's include of
182/// hub_core.h would defeat.
183///
184/// Why every one of these is created from the `home_io_control:` block (or a dedicated `button:`
185/// entry) and never from a device-bound `switch:`/`button:` platform entry: a user-declared
186/// device-bound entry would have to dispatch on the presence of a key (`io_device_id`,
187/// `commands:`, `enrollment:`, …) to decide what the entity *is*, so an entry that merely omitted
188/// `io_device_id` by mistake could be misread as one of these security-sensitive hub entities
189/// instead of failing validation. Creating them from the hub block removes that failure mode:
190/// there is no shared schema for a device-bound entity and a hub entity to be confused under.
191/// This is the shared reason behind all of them; each entity's own doc adds only what is
192/// specific to it (the ADR 0021 permission-vs-arming argument, the ADR 0026 physical-interlock
193/// argument, the "2W extraction and 1W adoption are deliberately independent" note, and so on).
195 public:
196 /// @brief Set the parent controller component.
197 /// @param parent Pointer to the IOHomeControlComponent instance.
198 void set_parent(IOHomeControlComponent *parent) { this->parent_ = parent; }
199
200 protected:
202};
203
204/// @brief Mixin for the hub-level entities that additionally scope themselves to one
205/// `oneway_controllers:` identity: the 1W command buttons, the 1W enrollment button, and the
206/// per-identity "Last 1W Command" text sensor.
207/// @ingroup hioc_platforms
208///
209/// They act on the hub, not on a paired device, so they are HubBoundEntity plus this one handle.
210/// The identity is a `oneway_controllers:` block entry, not an address in the hub's device table.
212 public:
213 /// @brief Set the controller identity this entity acts as / reports on.
214 /// @param id Handle from the `oneway_controllers:` block.
215 void set_controller_id(const std::string &id) { this->controller_id_ = id; }
216
217 protected:
218 std::string controller_id_;
219};
220
221} // namespace home_io_control
222} // namespace esphome
Mixin holding the parent + device-id binding shared by per-device entities that are not full entity p...
void set_parent(IOHomeControlComponent *parent)
Set the parent controller component.
void register_companion_binding_(const std::function< void(const IoDevice &)> &publish)
Shared setup() body: subscribe to this device's updates and publish the initial state.
void set_device_id(const std::string &id)
Set the device ID whose state this companion sensor exposes.
Mixin holding the hub-device binding shared by all IO-Homecontrol entity platforms.
void set_low_power(bool low_power)
Mark this device as a low-power / duty-cycled receiver.
void set_status_poll_interval(uint32_t poll_interval_ms)
Configure bounded follow-up polling while a state change is expected.
void set_optimistic_state(bool optimistic_state)
Enable or disable optimistic target updates for this device (from YAML).
void log_poll_interval_config_(const char *tag) const
Emit the shared two-branch poll-interval line for dump_config().
void set_silent(bool silent)
Send this device's position moves in "silent operation" mode — the reference hub's slower travel prof...
void set_device_type(DeviceType type)
Set the declared device type (from YAML).
void set_subtype(uint8_t subtype)
Set the declared device subtype (from YAML).
void register_device_binding_(Component *self, bool inverted, std::function< void(const std::string &, const IoDevice &)> on_update, bool schedule_initial_poll=true)
Perform the shared setup() registration ritual.
void set_device_id(const std::string &id)
Set the unique IO-homecontrol device ID (from YAML).
void set_parent(IOHomeControlComponent *parent)
Set the parent controller component.
bool passes_binary_update_filter_(const std::string &id, const IoDevice &dev) const
Shared inbound-update guard for binary endpoints.
Mixin for entities bound to the hub itself rather than to one device.
void set_parent(IOHomeControlComponent *parent)
Set the parent controller component.
The main IO-Homecontrol component.
Definition hub_core.h:91
virtual void register_device_callback(DeviceUpdateCallback cb)
Register a callback invoked when any device updates.
Definition hub_core.h:485
virtual IoDevice * get_device(const std::string &device_id)
Retrieve a device by ID; returns nullptr if not found.
Definition hub_core.cpp:334
virtual void queue_request_device_status(const std::string &device_id)
Queue an async status request; returns immediately, executed in loop().
virtual void add_device(const std::string &device_id)
Add a device to the registry by device ID only (undeclared/legacy path).
Definition hub_core.cpp:328
virtual void set_device_status_poll_interval(const std::string &device_id, uint32_t poll_interval_ms)
Configure the optional follow-up polling interval for a registered device.
Definition hub_core.cpp:310
Mixin for the hub-level entities that additionally scope themselves to one oneway_controllers: identi...
void set_controller_id(const std::string &id)
Set the controller identity this entity acts as / reports on.
IO-Homecontrol ESPHome component — protocol controller.
DeviceType
Device type identifiers reported by IO‑Homecontrol products.
@ UNKNOWN
Unknown/unspecified device.
bool effective_is_stopped(const IoDevice &dev)
Whether a consumer should treat the device as at rest, prediction first.
YAML-declared device metadata for registration; defaults match an undeclared device.
Runtime state of a paired IO‑Homecontrol device.
float position
Current position: 0=open, 100=closed, or UNKNOWN_POSITION.