Home IO Control
ESPHome add-on for IO-Homecontrol devices
Loading...
Searching...
No Matches
pairing_engine.h
Go to the documentation of this file.
1#pragma once
2
3/// @file pairing_engine.h
4/// @brief Device discovery and key-exchange engine for IO-Homecontrol pairing.
5/// @ingroup hioc_hub
6///
7/// PairingEngine encapsulates all three phases of the IO-Homecontrol pairing flow:
8///
9/// Phase 1 — Discovery (0x28 → 0x29):
10/// Controller broadcasts a discovery packet. A device in pairing mode responds
11/// with its node ID and type/subtype metadata.
12///
13/// Phase 2 — Authenticated Key Exchange (0x31 → 0x3C → 0x32 → 0x33):
14/// The controller sends CMD_KEY_INIT (0x31). The device challenges with 0x3C.
15/// The controller proves knowledge of the system key and simultaneously
16/// transfers the encrypted system key (0x32). The device confirms with 0x33.
17///
18/// Phase 3 — Configuration (0x6F):
19/// The controller sends SetConfig1 to enable automatic device status updates.
20///
21/// The engine is constructed once by IOHomeControlComponent. It holds double-pointer
22/// indirection for the radio driver so test assignments (`comp.radio_ = &mock`) propagate
23/// without calling setup(). All radio operations go through ExchangeEngine so that
24/// LBT, preamble selection, and frequency hopping are centralised.
25///
26/// PairingEngine is non-copyable and non-movable because it stores pointer and reference
27/// addresses that would dangle in a copy.
28
29#include "proto_frame.h"
30#include "hub_pairing.h"
31#include "hub_decisions.h"
32#include "exchange_engine.h"
33#include "device_registry.h"
34#include "pairing_advisor.h"
35#include "pairing_telemetry.h"
36#include "radio_interface.h"
37#include "tuning_config.h"
38
39#include <cstdint>
40#include <string>
41
42namespace esphome {
43namespace home_io_control {
44
45/// @name Pairing timing constants
46/// Timeouts and retry limits for the pairing flow's blocking waits.
47///@{
48inline constexpr uint32_t PAIRING_DISCOVERY_RESPONSE_TIMEOUT_MS = 2000; ///< Discovery wait window after sending 0x28.
49inline constexpr uint8_t PAIRING_DISCOVERY_MAX_ATTEMPTS = 3; ///< Retry discovery TX up to this many times.
50inline constexpr uint32_t PAIRING_KEY_CHALLENGE_TIMEOUT_MS = 500; ///< Wait window for the device's 0x3C challenge.
51inline constexpr uint32_t PAIRING_KEY_CONFIRM_TIMEOUT_MS = 500; ///< Wait for 0x33 key confirm after sending 0x32.
52inline constexpr uint32_t PAIRING_KEY_CONFIRM_SLICE_MS = 150; ///< RX slice during key confirm wait (hop each slice).
53///@}
54
55/// Owns and drives all three phases of the IO-Homecontrol device pairing flow.
56///
57/// Constructed once by IOHomeControlComponent; collaborators (radio, exchange engine,
58/// device registry) are injected as pointers/references so the engine never outlives them.
59/// @ingroup hioc_hub
61 public:
62 /// Construct the engine with all required collaborators.
63 ///
64 /// @param radio_ptr Double pointer into the hub's `radio_` member — survives driver replacement in tests.
65 /// @param node_id Controller 3-byte node ID buffer, owned by the hub.
66 /// @param system_key 16-byte AES system key buffer, owned by the hub.
67 /// @param tuning Tuning configuration, owned by the hub.
68 /// @param engine Shared exchange engine for transmit/receive operations.
69 /// @param registry Device registry where paired devices are permanently registered.
70 /// @param telemetry Per-attempt telemetry recorder, owned by the hub.
71 PairingEngine(RadioDriver **radio_ptr, const uint8_t *node_id, const uint8_t *system_key, const TuningConfig *tuning,
72 ExchangeEngine &engine, DeviceRegistry &registry, PairingTelemetry &telemetry);
73
74 /// Non-copyable — stores double-pointer and references into hub member addresses.
75 PairingEngine(const PairingEngine &) = delete;
77
78 /// Discover and pair a device currently in pairing mode (three-phase orchestrator).
79 /// @return true if all three phases completed successfully; false otherwise.
80 bool discover_and_pair();
81
82 /// Extract node ID, device type, and subtype from a CMD_DISCOVER_RESP frame.
83 static void parse_device_from_discovery(const IoFrame &frame, IoDevice &device, std::string &device_id);
84
85 protected:
86 // --- Phase helpers (protected; exposed to tests via TestablePairingEngine in test_helpers.h) ---
87
88 /// Phase 1: broadcast discovery command(s) and wait for a device response (0x29).
89 /// @param context Pairing context updated on success.
90 /// @return ACCEPT on success; NO_RESPONSE or INVALID otherwise.
92
93 /// Phase 2: authenticated key exchange (0x31 → 0x3C → 0x32 → 0x33).
94 /// @param context Pairing context populated by run_discovery_phase_().
95 /// @return true if key exchange completes; false on any failure.
97
98 /// Phase 3: send SetConfig1 (0x6F) to enable automatic status updates; best-effort.
99 /// Pairing always proceeds regardless of the outcome — the return value is informational
100 /// only, used to distinguish PairingOutcome::PAIRED from PairingOutcome::CONFIG_FAILED in
101 /// telemetry; it never causes discover_and_pair() to report failure.
102 /// @param context Pairing context with device information from phases 1 and 2.
103 /// @return true if the SetConfig1 exchange completed; false if it was skipped or failed.
105
106 /// Wait for a discovery response (0x29) within timeout_ms with per-chip frequency hopping.
107 /// @param timeout_ms Maximum wait window.
108 /// @param packet Output: raw RadioRxPacket of the accepted discovery frame.
109 /// @param response_frame Output: parsed IoFrame of the accepted discovery frame.
110 /// @return ACCEPT on success; NO_RESPONSE (no traffic) or INVALID (wrong frames) otherwise.
112 IoFrame &response_frame);
113
114 /// Wait for a key-challenge (0x3C) or direct key-confirm (0x33) from the target device.
115 /// @param timeout_ms Maximum wait window.
116 /// @param packet Output: raw RadioRxPacket of the accepted frame.
117 /// @param challenge_frame Output: parsed IoFrame.
118 /// @param device_node_id Expected source node ID (devices paired to).
119 /// @return true if a valid challenge or confirm was received; false on timeout.
120 bool wait_for_key_challenge_(uint32_t timeout_ms, RadioRxPacket &packet, IoFrame &challenge_frame,
121 const uint8_t device_node_id[NODE_ID_SIZE]);
122
123 /// Transmit the 0x32 key transfer and wait for the 0x33 key confirm with retry.
125
126 private:
127 /// Convenience accessor returning the current radio driver (dereferences double pointer).
128 [[nodiscard]] RadioDriver *radio_() const { return *radio_ptr_; }
129
130 /// Record the final outcome, detach telemetry from the exchange engine, and log the
131 /// end-of-attempt summary. Called once at every discover_and_pair() exit point.
132 /// @param outcome Final disposition of this attempt.
133 void finish_pairing_attempt_(PairingOutcome outcome);
134
135 /// Record an RX or RX_REJECT telemetry event for a discovery-response candidate frame.
136 /// Factored out of wait_for_discovery_response_() purely to keep that function's cognitive
137 /// complexity under the clang-tidy threshold — no behavior beyond the telemetry call.
138 /// @param frame Parsed candidate frame.
139 /// @param accepted true if the frame was classified as a valid discovery response.
140 /// @param rssi RSSI of the captured frame.
141 void record_discovery_rx_telemetry_(const IoFrame &frame, bool accepted, int16_t rssi);
142
143 RadioDriver **radio_ptr_;
144 const uint8_t *node_id_;
145 const uint8_t *system_key_;
146 const TuningConfig *tuning_;
147 ExchangeEngine &engine_;
148 DeviceRegistry &registry_;
149 PairingTelemetry &telemetry_;
150};
151
152} // namespace home_io_control
153} // namespace esphome
Owns the per-hub device table, update callbacks, and linked-remote associations.
Authenticated exchange engine — outbound and inbound protocol flows.
decisions::PairingDiscoveryDisposition run_discovery_phase_(pairing::PairingContext &context)
Phase 1: broadcast discovery command(s) and wait for a device response (0x29).
PairingEngine(RadioDriver **radio_ptr, const uint8_t *node_id, const uint8_t *system_key, const TuningConfig *tuning, ExchangeEngine &engine, DeviceRegistry &registry, PairingTelemetry &telemetry)
Construct the engine with all required collaborators.
static void parse_device_from_discovery(const IoFrame &frame, IoDevice &device, std::string &device_id)
Extract node ID, device type, and subtype from a CMD_DISCOVER_RESP frame.
PairingEngine & operator=(const PairingEngine &)=delete
bool run_key_exchange_phase_(pairing::PairingContext &context)
Phase 2: authenticated key exchange (0x31 → 0x3C → 0x32 → 0x33).
bool wait_for_key_confirm_(pairing::PairingContext &context)
Transmit the 0x32 key transfer and wait for the 0x33 key confirm with retry.
PairingEngine(const PairingEngine &)=delete
Non-copyable — stores double-pointer and references into hub member addresses.
bool wait_for_key_challenge_(uint32_t timeout_ms, RadioRxPacket &packet, IoFrame &challenge_frame, const uint8_t device_node_id[NODE_ID_SIZE])
Wait for a key-challenge (0x3C) or direct key-confirm (0x33) from the target device.
bool discover_and_pair()
Discover and pair a device currently in pairing mode (three-phase orchestrator).
decisions::PairingDiscoveryDisposition wait_for_discovery_response_(uint32_t timeout_ms, RadioRxPacket &packet, IoFrame &response_frame)
Wait for a discovery response (0x29) within timeout_ms with per-chip frequency hopping.
bool finalize_pairing_configuration_(pairing::PairingContext &context)
Phase 3: send SetConfig1 (0x6F) to enable automatic status updates; best-effort.
Fixed-size per-attempt telemetry recorder for the pairing flow.
Abstract radio driver for IO-Homecontrol.
Per-hub device table, update-callback fan-out, and linked-remote map.
Self-contained authenticated exchange engine for IO-Homecontrol 2W.
Pure transition helpers for hub-owned exchange and pairing frame decisions.
Internal pairing-state model for hub‑owned discovery and key‑exchange flows.
PairingDiscoveryDisposition
Disposition during pairing discovery phase.
static constexpr uint8_t NODE_ID_SIZE
Device/node addresses are 3 bytes (e.g., "123ABC").
Definition proto_sizes.h:20
constexpr uint8_t PAIRING_DISCOVERY_MAX_ATTEMPTS
Retry discovery TX up to this many times.
constexpr uint32_t PAIRING_KEY_CONFIRM_TIMEOUT_MS
Wait for 0x33 key confirm after sending 0x32.
PairingOutcome
Final disposition of a pairing attempt, used by the result sensor string.
constexpr uint32_t PAIRING_KEY_CONFIRM_SLICE_MS
RX slice during key confirm wait (hop each slice).
constexpr uint32_t PAIRING_KEY_CHALLENGE_TIMEOUT_MS
Wait window for the device's 0x3C challenge.
constexpr uint32_t PAIRING_DISCOVERY_RESPONSE_TIMEOUT_MS
Discovery wait window after sending 0x28.
Read-only advisor that turns PairingTelemetry into actionable diagnostics.
Structured per-attempt telemetry recorder for the pairing flow.
IO-Homecontrol 2W frame container: control bytes, IoFrame and (de)serialization.
Radio abstraction layer for IO-Homecontrol.
Runtime state of a paired IO‑Homecontrol device.
Parsed IO‑Homecontrol frame (CTRL0/1 + addresses + command + data).
Definition proto_frame.h:71
Raw packet received from the radio.
All runtime tunable parameters for pairing and radio diagnostics.
Context object that lives for the duration of a single pairing attempt.
Definition hub_pairing.h:59
Runtime tuning configuration for pairing and radio diagnostics.