Home IO Control
ESPHome add-on for IO-Homecontrol devices
Loading...
Searching...
No Matches
oneway_key_adoption.h
Go to the documentation of this file.
1#pragma once
2
3/// @file oneway_key_adoption.h
4/// @brief Opt-in, receive-only adoption of a 1W installation's controller key.
5/// @ingroup hioc_hub
6///
7/// See oneway_key_adoption.cpp for the security framing. This header carries only the collaborator
8/// class; IOHomeControlComponent owns one instance (hub_core.h) and forwards the three public
9/// entry points to it.
10
11#include "hub_hooks.h"
12#include "proto_codecs.h"
13#include "proto_device_model.h"
14#include "proto_frame.h"
15
16#include <cstdint>
17#include <string>
18
19namespace esphome {
20namespace home_io_control {
21
22/// @brief Opt-in, receive-only listener that adopts an overheard 1W controller key.
23///
24/// Receive-only: nothing here transmits. While armed, an overheard CMD_ONEWAY_ADD_CONTROLLER
25/// broadcast is decrypted and reported once, after which the listener disarms itself — one
26/// adoption per arm. Constructed once by IOHomeControlComponent; non-copyable because it is wired
27/// with an injected scheduling callback.
28/// @ingroup hioc_hub
30 public:
31 /// @param schedule_auto_off Named-timeout scheduler for the 10-minute arm window (see NamedTimeoutFn).
32 explicit OnewayKeyAdoption(NamedTimeoutFn schedule_auto_off) : schedule_auto_off_(std::move(schedule_auto_off)) {}
33
34 /// Non-copyable — holds an injected callback and is owned by the hub.
37
38 /// @brief Arm or disarm the 1W controller-key adoption listener.
39 ///
40 /// Arming resets any class observed in an earlier window and schedules a 10-minute auto-off.
41 /// Disarming — manual, on successful adoption, or on auto-off — is immediate. See the class doc
42 /// comment; this is the body that was IOHomeControlComponent::set_oneway_key_adoption_armed().
43 /// @param armed Desired state.
44 void set_armed(bool armed);
45
46 /// @brief Whether the listener is currently armed.
47 [[nodiscard]] bool armed() const { return this->armed_; }
48
49 /// Register a callback invoked whenever the armed state changes (manual toggle, successful
50 /// adoption, or auto-off), so the switch entity stays in sync when the listener disarms itself.
51 /// @param cb Callable receiving the new armed state.
52 void set_armed_callback(std::function<void(bool)> cb) { this->armed_callback_ = std::move(cb); }
53
54 /// Remember the most recent 1W target device class observed from `info.src`, for the adoption
55 /// report's `io_device_type` prefill. No-op unless armed and `info.target_type` is a real class.
56 /// @param info Already-decoded 1W frame info (see decode_1w_frame()).
57 void record_observed_class(const OneWayFrameInfo &info);
58
59 /// Decode an inbound CMD_ONEWAY_ADD_CONTROLLER (0x30) while armed, report the result, and
60 /// disarm. Returns nothing and never consumes the frame — the caller still runs it through the
61 /// normal 1W logging path.
62 /// @param frame Parsed inbound 1W frame.
63 void try_adopt(const IoFrame &frame);
64
65 /// Most recent 1W target device class observed while armed. Single-slot — this is a one-gesture
66 /// flow, not a per-node registry — and reset on every arm so a stale observation from an earlier
67 /// window never leaks into the next one. Feeds the `io_device_type` prefill in the report.
69 uint8_t node[NODE_ID_SIZE]{};
71 bool valid{false};
72 };
73 /// @return The most recent observed class (see ObservedClass).
74 [[nodiscard]] const ObservedClass &last_observed_class() const { return this->observed_class_; }
75
76 private:
77 NamedTimeoutFn schedule_auto_off_;
78 bool armed_{false};
79 std::function<void(bool)> armed_callback_;
80 ObservedClass observed_class_{};
81};
82
83namespace detail {
84
85/// @brief Build the full 1W controller-key-adoption report: MAC-verification status, the
86/// own-address transmission rationale, and the ready-to-paste `oneway_controllers:` YAML block.
87///
88/// Pure — takes already-decoded values, performs no I/O — so it is directly unit-testable
89/// without a live radio or a captured log line (ESP_LOG's host stub discards its arguments).
90/// This is the single intentional place `adopted.system_key` is formatted for display (via
91/// format_key_hex(), log_helpers.h); the caller (OnewayKeyAdoption::try_adopt()) logs the returned
92/// text through log_multiline_result() and nowhere else.
93///
94/// `node_id` is deliberately never mentioned as something to fill in — a later step derives one
95/// from the hub's own node ID, and the report says so rather than asking the user to invent a
96/// 3-byte address. The report also explains that the hub always transmits under its own address:
97/// impersonating the sender would hijack that remote's rolling sequence counter and break it.
98///
99/// The emitted keys must track `ONEWAY_CONTROLLER_SCHEMA` (`oneway_controllers.py`) by hand — a newly
100/// required schema key needs a matching line here too. `make yaml-emitter-sync`
101/// (scripts/check-yaml-emitters.py) catches drift between the two statically; it does not tell
102/// you what to add here.
103///
104/// @param adopted Decoded controller identity from decode_1w_add_controller() (proto_codecs.h).
105/// @param observed_type_known True if this sender's other 1W traffic was observed while armed
106/// (see OnewayKeyAdoption::record_observed_class()); false prints a commented-out
107/// fallback pointing at the DEBUG log line that would reveal it instead.
108/// @param observed_type The observed target class; only meaningful when observed_type_known.
109/// @return Multi-line report text, ready to pass to log_multiline_result().
110std::string build_oneway_adoption_report(const OneWayAdoptedKey &adopted, bool observed_type_known,
111 DeviceType observed_type);
112
113} // namespace detail
114
115} // namespace home_io_control
116} // namespace esphome
OnewayKeyAdoption(NamedTimeoutFn schedule_auto_off)
void try_adopt(const IoFrame &frame)
Decode an inbound CMD_ONEWAY_ADD_CONTROLLER (0x30) while armed, report the result,...
bool armed() const
Whether the listener is currently armed.
void set_armed_callback(std::function< void(bool)> cb)
Register a callback invoked whenever the armed state changes (manual toggle, successful adoption,...
const ObservedClass & last_observed_class() const
OnewayKeyAdoption & operator=(const OnewayKeyAdoption &)=delete
OnewayKeyAdoption(const OnewayKeyAdoption &)=delete
Non-copyable — holds an injected callback and is owned by the hub.
void set_armed(bool armed)
Arm or disarm the 1W controller-key adoption listener.
void record_observed_class(const OneWayFrameInfo &info)
Remember the most recent 1W target device class observed from info.src, for the adoption report's io_...
Injected-capability callback aliases shared by the hub's collaborator objects.
std::string build_oneway_adoption_report(const OneWayAdoptedKey &adopted, bool observed_type_known, DeviceType observed_type)
Build the full 1W controller-key-adoption report: MAC-verification status, the own-address transmissi...
DeviceType
Device type identifiers reported by IO‑Homecontrol products.
@ UNKNOWN
Unknown/unspecified device.
std::function< void(const char *name, uint32_t delay_ms, std::function< void()> callback)> NamedTimeoutFn
Schedules a named, replace-on-same-name timeout on the hub's ESPHome scheduler.
Definition hub_hooks.h:27
Device-name, address-classification and 1W-frame codecs.
IO-Homecontrol device-type model, capabilities and runtime device state.
IO-Homecontrol 2W frame container: control bytes, IoFrame and (de)serialization.
Parsed IO‑Homecontrol frame (CTRL0/1 + addresses + command + data).
Definition proto_frame.h:93
Decoded representation of a 1W remote frame.
Most recent 1W target device class observed while armed.