Home IO Control
ESPHome add-on for IO-Homecontrol devices
Loading...
Searching...
No Matches
device_registry.cpp
Go to the documentation of this file.
1/// @file device_registry.cpp
2/// @brief DeviceRegistry implementation.
3/// @ingroup hioc_hub
4
5#include "device_registry.h"
6
7#include "proto_frame.h"
8#include "proto_sizes.h"
9
10#include "esphome/core/log.h"
11
12namespace esphome {
13namespace home_io_control {
14
15static constexpr const char *TAG = "home_io_control";
16
17void DeviceRegistry::add(const std::string &device_id) { add(device_id, DeviceConfig{}); }
18
19void DeviceRegistry::add(const std::string &device_id, const DeviceConfig &cfg) {
20 if (devices_.count(device_id) != 0)
21 return;
22 IoDevice dev{};
23 if (!hex_to_bytes(device_id, dev.node_id, NODE_ID_SIZE)) {
24 ESP_LOGW(TAG, "Ignoring invalid device ID %s", device_id.c_str());
25 return;
26 }
27 dev.type = cfg.type;
28 dev.subtype = cfg.subtype;
29 if (cfg.inverted)
30 dev.inverted = true;
32 devices_[device_id] = dev;
33}
34
35void DeviceRegistry::put(const std::string &device_id, IoDevice device) { devices_[device_id] = device; }
36
37IoDevice *DeviceRegistry::get(const std::string &device_id) {
38 auto it = devices_.find(device_id);
39 return (it != devices_.end()) ? &it->second : nullptr;
40}
41
42void DeviceRegistry::set_dimmable(const std::string &device_id, bool dimmable) {
43 if (IoDevice *dev = this->get(device_id))
44 dev->dimmable = dimmable;
45}
46
47void DeviceRegistry::subscribe(DeviceUpdateCallback cb) { callbacks_.push_back(std::move(cb)); }
48
49void DeviceRegistry::notify(const std::string &device_id) {
50 auto it = devices_.find(device_id);
51 if (it == devices_.end())
52 return;
53 for (auto &cb : callbacks_)
54 cb(device_id, it->second);
55}
56
57void DeviceRegistry::add_linked_remote(const std::string &remote_id, const std::string &device_id) {
58 linked_remotes_[remote_id].push_back(device_id);
59}
60
61const std::vector<std::string> *DeviceRegistry::linked_devices(const std::string &remote_id) const {
62 auto it = linked_remotes_.find(remote_id);
63 return (it != linked_remotes_.end()) ? &it->second : nullptr;
64}
65
66void DeviceRegistry::add_linked_remote_class(DeviceType type, const std::string &device_id) {
67 linked_remote_classes_[type].push_back(device_id);
68}
69
70const std::vector<std::string> *DeviceRegistry::linked_devices_for_class(DeviceType type) const {
71 auto it = linked_remote_classes_.find(type);
72 return (it != linked_remote_classes_.end()) ? &it->second : nullptr;
73}
74
75bool DeviceRegistry::apply_optimistic_target(const std::string &device_id, float target_io_position) {
76 auto it = devices_.find(device_id);
77 if (it == devices_.end() || !it->second.optimistic_state)
78 return false;
79 // Logs the raw (non-inverted) IO-protocol values the entity layer will compare against —
80 // cross-check against platform_cover.cpp::on_device_update_()'s invert-aware direction math
81 // when verifying a specific device's optimistic behavior.
82 if (it->second.position == UNKNOWN_POSITION) {
83 ESP_LOGD(TAG, "Device %s: optimistic target=%.0f (inverted=%s, last reported position=unknown)", device_id.c_str(),
84 target_io_position, YESNO(it->second.inverted));
85 } else {
86 ESP_LOGD(TAG, "Device %s: optimistic target=%.0f (inverted=%s, last reported position=%.0f)", device_id.c_str(),
87 target_io_position, YESNO(it->second.inverted), it->second.position);
88 }
89 it->second.target = target_io_position;
90 it->second.is_stopped = false;
91 notify(device_id);
92 return true;
93}
94
95bool DeviceRegistry::clear_optimistic_target(const std::string &device_id) {
96 auto it = devices_.find(device_id);
97 if (it == devices_.end() || !it->second.optimistic_state)
98 return false;
99 ESP_LOGD(TAG, "Device %s: optimistic stop", device_id.c_str());
100 it->second.target = UNKNOWN_POSITION;
101 it->second.is_stopped = true;
102 notify(device_id);
103 return true;
104}
105
106bool DeviceRegistry::apply_optimistic_tilt(const std::string &device_id, float tilt_percent) {
107 auto it = devices_.find(device_id);
108 if (it == devices_.end() || !it->second.optimistic_state)
109 return false;
110 // Type-gated here rather than at the call sites so every present and future caller is safe:
111 // showing an angle the device will never report back would be a lie the next poll cannot
112 // correct. This admits exactly the devices whose tilt command is accepted — the command gate
113 // known_device_accepts_execute_tilt() is this same predicate plus an UNKNOWN-type check that
114 // device_supports_tilt() already fails — so the two cannot disagree about a given device.
115 if (!device_supports_tilt(it->second.type))
116 return false;
117 if (it->second.tilt == UNKNOWN_POSITION) {
118 ESP_LOGD(TAG, "Device %s: optimistic tilt=%.0f (last reported tilt=unknown)", device_id.c_str(), tilt_percent);
119 } else {
120 ESP_LOGD(TAG, "Device %s: optimistic tilt=%.0f (last reported tilt=%.0f)", device_id.c_str(), tilt_percent,
121 it->second.tilt);
122 }
123 it->second.tilt = tilt_percent;
124 notify(device_id);
125 return true;
126}
127
129 const std::function<void(const std::string &, const std::vector<std::string> &)> &fn) const {
130 for (const auto &pair : linked_remotes_)
131 fn(pair.first, pair.second);
132}
133
134} // namespace home_io_control
135} // namespace esphome
const std::vector< std::string > * linked_devices(const std::string &remote_id) const
Retrieve the list of device IDs linked to a remote.
void set_dimmable(const std::string &device_id, bool dimmable)
Set a device's dimmable flag (see IoDevice::dimmable).
bool apply_optimistic_tilt(const std::string &device_id, float tilt_percent)
Set an optimistic slat angle ahead of a confirming poll, and notify.
const std::vector< std::string > * linked_devices_for_class(DeviceType type) const
Retrieve the list of device IDs linked to a device class.
void add_linked_remote_class(DeviceType type, const std::string &device_id)
Record that a remote's typed-broadcast presses (e.g.
void put(const std::string &device_id, IoDevice device)
Insert or overwrite a device entry without deduplication or hex-validation checks.
bool apply_optimistic_target(const std::string &device_id, float target_io_position)
Set an optimistic target position ahead of a confirming poll/response, and notify.
void notify(const std::string &device_id)
Invoke all registered callbacks for device_id.
void add_linked_remote(const std::string &remote_id, const std::string &device_id)
Record that a remote node controls a registered device.
void subscribe(DeviceUpdateCallback cb)
Register a callback that fires whenever a device's state changes.
IoDevice * get(const std::string &device_id)
Retrieve a registered device by ID.
bool clear_optimistic_target(const std::string &device_id)
Clear a device's optimistic target (e.g.
void for_each_linked_remote(const std::function< void(const std::string &, const std::vector< std::string > &)> &fn) const
Invoke fn(remote_id, device_id_list) for every linked-remote entry.
void add(const std::string &device_id)
Register a device by ID with default metadata (UNKNOWN type, subtype 0, not inverted).
Per-hub device table, update-callback fan-out, and linked-remote map.
static constexpr float UNKNOWN_POSITION
Sentinel value meaning "position is not known yet".
static constexpr uint8_t NODE_ID_SIZE
Device/node addresses are 3 bytes (e.g., "123ABC").
Definition proto_sizes.h:20
DeviceType
Device type identifiers reported by IO‑Homecontrol products.
static constexpr const char * TAG
std::function< void(const std::string &device_id, const IoDevice &device)> DeviceUpdateCallback
Callback type invoked when a device's state changes.
bool device_supports_tilt(DeviceType type)
Does this device type support tilt (slat angle) control?
bool hex_to_bytes(const std::string &hex, uint8_t *out, uint8_t len)
Convert a hex string (e.g., "123ABC") to a byte array.
IO-Homecontrol 2W frame container: control bytes, IoFrame and (de)serialization.
Fundamental IO-Homecontrol frame and crypto size constants.
YAML-declared device metadata for registration; defaults match an undeclared device.
bool optimistic_state
Whether apply_optimistic_target/clear_optimistic_target may fire.
bool inverted
Position-inversion flag.
uint8_t subtype
Device subtype byte.
DeviceType type
Device type from YAML declaration.
Runtime state of a paired IO‑Homecontrol device.
bool inverted
True if open/close positions are swapped (e.g., horizontal awning).
uint8_t subtype
Device subtype (manufacturer‑specific).
uint8_t node_id[NODE_ID_SIZE]
Device's 3‑byte radio address.
bool dimmable
True for a LIGHT-class device configured dimmable: true in YAML.
bool optimistic_state
True if target may be set ahead of a confirming poll/response.
DeviceType type
Device type (shutter, awning, etc.).