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