Home IO Control
ESPHome add-on for IO-Homecontrol devices
Loading...
Searching...
No Matches
platform_companion_sensors.h
Go to the documentation of this file.
1#pragma once
2
3/// @file platform_companion_sensors.h
4/// @brief The auto-generated per-device diagnostic companion sensors.
5/// @ingroup hioc_platforms
6///
7/// Every device-bound platform (cover, light, switch, lock) gets the same read-only companions
8/// generated alongside it by platform_common.py: smoothed RSSI, seconds since last contact,
9/// cumulative exchange-failure and unconfirmed-exchange counts, the stored device name, the
10/// currently outstanding CMD_ERROR_RESP reason, and the last-command record's
11/// commander/originator. They share the DeviceBoundCompanion binding (observe-only: no
12/// add_device(), no polling) and an all-but-identical setup()/dump_config() skeleton, so they
13/// live together here rather than in a file pair each.
14///
15/// What each one still owns is its dump_config() label and whatever its setup() has to do beyond
16/// binding; everything they share sits in the three bases below.
17
18#include "esphome/components/sensor/sensor.h"
19#include "esphome/components/text_sensor/text_sensor.h"
20#include "esphome/core/component.h"
22
23namespace esphome {
24namespace home_io_control {
25
26/// @brief Shared base for the numeric per-device diagnostic companions.
27///
28/// Carries the three-base inheritance and the setup priority every one of them needs. DATA keeps
29/// them behind the hub, so `parent_` is usable by the time their setup() runs.
30/// @ingroup hioc_platforms
31class IOHomeCompanionSensor : public sensor::Sensor, public Component, public DeviceBoundCompanion {
32 public:
33 /// @brief Get setup priority so the parent hub is available first.
34 /// @return setup_priority::DATA.
35 [[nodiscard]] float get_setup_priority() const override { return setup_priority::DATA; }
36};
37
38/// @brief Shared base for the textual per-device diagnostic companions.
39///
40/// The text-sensor counterpart of IOHomeCompanionSensor; same reasoning.
41/// @ingroup hioc_platforms
42class IOHomeCompanionTextSensor : public text_sensor::TextSensor, public Component, public DeviceBoundCompanion {
43 public:
44 /// @brief Get setup priority so the parent hub is available first.
45 /// @return setup_priority::DATA.
46 [[nodiscard]] float get_setup_priority() const override { return setup_priority::DATA; }
47};
48
49/// @brief Shared base for the companions that publish one cumulative `uint16_t` counter held on
50/// IoDevice.
51///
52/// These differ only in which field they read and what they call themselves, so the binding lives
53/// here once and each subclass supplies the field through counter_value(). Zero is a meaningful
54/// reading for all of them — "none yet", not "no data" — so unlike RSSI and Last Contact they
55/// publish unconditionally on setup.
56/// @ingroup hioc_platforms
58 public:
59 /// @brief Register the device-update subscription and publish the initial cached count.
60 void setup() final;
61
62 protected:
63 /// @brief Return the counter this sensor publishes.
64 /// @param dev Device record to read the count from.
65 [[nodiscard]] virtual uint16_t counter_value(const IoDevice &dev) const = 0;
66};
67
68/// @brief Diagnostic sensor that publishes a device's smoothed (EMA) RSSI in dBm.
69///
70/// Publishes nothing until the first RX from this device seeds the EMA (see
71/// detail::update_link_health() in hub_internal.h) — Home Assistant shows the entity as
72/// unavailable until then, rather than a misleading 0 dBm.
73/// @ingroup hioc_platforms
75 public:
76 /// @brief Register the device-update subscription and publish the initial cached state.
77 void setup() override;
78
79 /// @brief Dump sensor configuration to the log.
80 void dump_config() override;
81};
82
83/// @brief Diagnostic sensor that publishes seconds elapsed since the last frame received from a
84/// device (see detail::update_link_health() in hub_internal.h).
85///
86/// This is an age, not a timestamp: it resets to ~0 on every frame from the device — including
87/// replies to the hub's own status polls and commands, not just traffic the device sends
88/// unprompted — and counts up from there. A periodic heartbeat (see HEARTBEAT_INTERVAL_MS in the
89/// .cpp) re-publishes it even when the device stays quiet, so the value keeps advancing in Home
90/// Assistant instead of freezing at whatever it was at the last frame. Publishes nothing until
91/// the first frame is seen.
92/// @ingroup hioc_platforms
94 public:
95 /// @brief Register the device-update subscription, start the heartbeat, and publish the
96 /// initial cached state.
97 void setup() override;
98
99 /// @brief Dump sensor configuration to the log.
100 void dump_config() override;
101
102 protected:
103 /// @brief Compute and publish seconds since `dev.last_seen_ms`; no-op before the first frame.
104 /// @param dev Device to read `last_seen_ms` from.
105 void publish_age_(const IoDevice &dev);
106};
107
108/// @brief Diagnostic sensor that publishes a device's cumulative count of outbound exchanges
109/// that timed out (no valid response) — see detail::record_exchange_timeout() in
110/// hub_internal.h.
111///
112/// Unlike the RSSI and Last Contact sensors, zero is a meaningful value here (no failures yet),
113/// so this publishes on setup unconditionally.
114/// @ingroup hioc_platforms
116 public:
117 /// @brief Dump sensor configuration to the log.
118 void dump_config() override;
119
120 protected:
121 /// @copydoc IOHomeDeviceCounterSensor::counter_value
122 [[nodiscard]] uint16_t counter_value(const IoDevice &dev) const override { return dev.exchange_timeout_count; }
123};
124
125/// @brief Diagnostic sensor that publishes a device's cumulative count of exchanges it
126/// authenticated and then never closed — see detail::record_exchange_outcome() in
127/// hub_internal.h.
128///
129/// Read it against Exchange Failures: that counter rising alone means the device is not hearing
130/// the hub, while this one rising means it hears the hub's request and then either the hub's
131/// challenge answer or the device's closing reply is lost. For a movement command this outcome is
132/// reported as success, so this sensor is the only place it shows up.
133///
134/// Zero is meaningful here (none yet), so this publishes on setup unconditionally.
135/// @ingroup hioc_platforms
137 public:
138 /// @brief Dump sensor configuration to the log.
139 void dump_config() override;
140
141 protected:
142 /// @copydoc IOHomeDeviceCounterSensor::counter_value
143 [[nodiscard]] uint16_t counter_value(const IoDevice &dev) const override { return dev.exchange_unconfirmed_count; }
144};
145
146/// @brief Diagnostic text sensor that publishes the cached device name.
147///
148/// Beyond the shared companion behavior it also queues one boot-time GET_NAME request so the
149/// cache gets populated without waiting for unrelated traffic.
150/// @ingroup hioc_platforms
152 public:
153 /// @brief Register the device-update subscription and schedule an initial name fetch.
154 void setup() override;
155
156 /// @brief Dump text-sensor configuration to the log.
157 void dump_config() override;
158};
159
160/// Active Issue value while the hub's radio has stopped responding, in the style of the symbolic
161/// result names the sensor otherwise publishes.
162inline constexpr const char *ACTIVE_ISSUE_RADIO_STOPPED_RESPONDING = "RADIO_STOPPED_RESPONDING";
163
164/// Active Issue value while a device's status reports execution state 0: it cannot run its last command
165/// under the current conditions (example: temperature too high). Shown until its next state 2..5.
166inline constexpr const char *ACTIVE_ISSUE_NOT_EXECUTING = "NOT_EXECUTING";
167/// Active Issue value while a device's status reports execution state 1: an error occurred during its
168/// last move. Shown until its next state 2..5.
169inline constexpr const char *ACTIVE_ISSUE_EXECUTION_ERROR = "EXECUTION_ERROR";
170
171/// @brief Diagnostic text sensor that names what is currently limiting a device (e.g.
172/// "LIMITATION_BY_RAIN"), letting a "nothing happened" command self-explain instead of only
173/// showing up in the log. Shared by every device-bound platform (cover, light, switch, lock, fan,
174/// climate, water_heater) via platform_common.py's companion-sensor codegen.
175///
176/// Four sources, in this order:
177/// 1. ACTIVE_ISSUE_RADIO_STOPPED_RESPONDING once the hub's radio latched a failure after setup
178/// (IOHomeControlComponent::radio_stopped_responding()); sticky until a reboot.
179/// 2. The symbolic name of the device's latest explicit CMD_ERROR_RESP result. Cleared by the
180/// next successful status/command reply (see detail::clear_command_result()).
181/// 3. "LIMITATION_BY_RAIN" while `IoDevice::limited_by_rain` is set. That flag is derived from
182/// the device's own status replies (the last move was ordered by the rain sensor, or a
183/// position command was clamped while rain protection was recently active), because windows
184/// do not send an error for a rain clamp. It clears itself when the window stops reporting
185/// the rain originator and a command is no longer clamped, so it is only as fresh as the
186/// last status reply.
187/// 4. The device's execution state (`IoDevice::execution_state`, byte 0 of its status replies):
188/// "NODE_WAITING_FOR_POWER" while it waits for power (state 3), ACTIVE_ISSUE_NOT_EXECUTING after
189/// state 0 and ACTIVE_ISSUE_EXECUTION_ERROR after state 1, until its next reply says 2..5.
190/// Waiting for power is experimental: it has been captured in a main-parameter read, not yet in
191/// an ordinary status reply.
192///
193/// Not a per-operation outcome. Publishes an empty string when no source applies, so a
194/// non-empty value always means "this is still going on" rather than "this is what happened
195/// last."
196/// @ingroup hioc_platforms
198 public:
199 /// @brief Register the device-update subscription and publish the initial cached state.
200 void setup() override;
201
202 /// @brief Dump text-sensor configuration to the log.
203 void dump_config() override;
204};
205
206/// @brief Diagnostic text sensor naming the controller that last commanded this device.
207///
208/// Read from bytes the device already includes in every status reply — no extra radio traffic and
209/// no probe. Last-writer-wins and inherently stale: it only changes when something actually
210/// commands the device, and a foreign controller's node ID has no name unless the user recognises
211/// it. Publishes an empty string until the first status reply carrying the record arrives.
212/// @ingroup hioc_platforms
214 public:
215 void setup() override;
216 void dump_config() override;
217};
218
219/// @brief Diagnostic text sensor naming what kind of source issued that last command.
220///
221/// The device's own Command Originator byte, rendered "name(0xXX)". Field-validated as a clean
222/// remote-vs-motor-button split on roller shutters only; other device classes may report values
223/// with no ORIGINATOR_* name, which surface as "unknown(0xXX)" rather than being dropped.
224/// @ingroup hioc_platforms
226 public:
227 void setup() override;
228 void dump_config() override;
229};
230
231} // namespace home_io_control
232} // namespace esphome
Mixin holding the parent + device-id binding shared by per-device entities that are not full entity p...
Diagnostic text sensor that names what is currently limiting a device (e.g.
void setup() override
Register the device-update subscription and publish the initial cached state.
void dump_config() override
Dump text-sensor configuration to the log.
Shared base for the numeric per-device diagnostic companions.
float get_setup_priority() const override
Get setup priority so the parent hub is available first.
Shared base for the textual per-device diagnostic companions.
float get_setup_priority() const override
Get setup priority so the parent hub is available first.
Shared base for the companions that publish one cumulative uint16_t counter held on IoDevice.
void setup() final
Register the device-update subscription and publish the initial cached count.
virtual uint16_t counter_value(const IoDevice &dev) const =0
Return the counter this sensor publishes.
Diagnostic text sensor that publishes the cached device name.
void setup() override
Register the device-update subscription and schedule an initial name fetch.
void dump_config() override
Dump text-sensor configuration to the log.
Diagnostic sensor that publishes a device's cumulative count of outbound exchanges that timed out (no...
uint16_t counter_value(const IoDevice &dev) const override
Return the counter this sensor publishes.
void dump_config() override
Dump sensor configuration to the log.
Diagnostic text sensor naming what kind of source issued that last command.
Diagnostic text sensor naming the controller that last commanded this device.
Diagnostic sensor that publishes seconds elapsed since the last frame received from a device (see det...
void dump_config() override
Dump sensor configuration to the log.
void publish_age_(const IoDevice &dev)
Compute and publish seconds since dev.last_seen_ms; no-op before the first frame.
void setup() override
Register the device-update subscription, start the heartbeat, and publish the initial cached state.
Diagnostic sensor that publishes a device's smoothed (EMA) RSSI in dBm.
void setup() override
Register the device-update subscription and publish the initial cached state.
void dump_config() override
Dump sensor configuration to the log.
Diagnostic sensor that publishes a device's cumulative count of exchanges it authenticated and then n...
uint16_t counter_value(const IoDevice &dev) const override
Return the counter this sensor publishes.
void dump_config() override
Dump sensor configuration to the log.
constexpr const char * ACTIVE_ISSUE_RADIO_STOPPED_RESPONDING
Active Issue value while the hub's radio has stopped responding, in the style of the symbolic result ...
constexpr const char * ACTIVE_ISSUE_NOT_EXECUTING
Active Issue value while a device's status reports execution state 0: it cannot run its last command ...
constexpr const char * ACTIVE_ISSUE_EXECUTION_ERROR
Active Issue value while a device's status reports execution state 1: an error occurred during its la...
Shared device-binding mixins for IO-Homecontrol entity platforms.
Runtime state of a paired IO‑Homecontrol device.
uint16_t exchange_unconfirmed_count
Cumulative count of exchanges this device authenticated and then never closed: it answered with a 0x3...
uint16_t exchange_timeout_count
Cumulative count of outbound exchanges to this device with no valid response (see detail::record_exch...