Home IO Control
ESPHome add-on for IO-Homecontrol devices
Loading...
Searching...
No Matches
hub_pairing.h
Go to the documentation of this file.
1#pragma once
2
3/// @file hub_pairing.h
4/// @brief Internal pairing-state model for hub‑owned discovery and key‑exchange flows.
5/// @ingroup hioc_hub
6///
7/// This header defines the state machine enum and context structures for the
8/// three‑phase pairing procedure (the flow itself is implemented by PairingEngine),
9/// which temporarily tracks a newly paired device in the controller's runtime
10/// registry and installs the system key on the device:
11///
12/// Phase 1 — Discovery (broadcast 0x28 → device responds 0x29 → confirm 0x2C → ack 0x2D):
13/// Controller broadcasts a discovery packet on the primary channel. A device in
14/// pairing mode (PROG button pressed) responds with its node ID and type/subtype.
15/// The controller validates the response and extracts device metadata, then sends a
16/// discover-confirm (0x2C) directly to that device and — unless the `pairing_discover_confirm`
17/// tuning mode is `skip` — waits up to a few seconds for its 0x2D before proceeding. This step
18/// never fails the attempt; see PairingEngine::run_discover_confirm_step_().
19///
20/// Phase 2 — Authenticated Key Exchange (0x31 → 0x3C → 0x32 → 0x33):
21/// The controller sends CMD_KEY_INIT (0x31) to the device. The device challenges
22/// with CMD_CHALLENGE_REQ (0x3C). The controller proves knowledge of the system
23/// key with CMD_CHALLENGE_RESP (0x3D) and simultaneously sends the encrypted system
24/// key via CMD_KEY_TRANSFER (0x32). The device confirms with CMD_KEY_CONFIRM (0x33).
25///
26/// Phase 3 — Configuration (CMD_SET_CONFIG1):
27/// The controller sends a configuration frame (0x6F) to enable automatic status
28/// updates from the device. This phase completes even if the config frame fails;
29/// the device will still operate in polled mode.
30///
31/// All frames use the standard authenticated exchange flow (state types in
32/// hub_exchange.h, implementation in ExchangeEngine). PairingEngine serializes these
33/// phases, logs the YAML metadata the user should add, and keeps the paired device
34/// in the current runtime registry until reboot.
35
36#include "proto_device_model.h"
37#include "proto_frame.h"
38#include "radio_interface.h"
39#include <cstdint>
40#include <string>
41
42namespace esphome {
43namespace home_io_control {
44
45namespace pairing {
46
47/// @brief State machine for the three‑phase pairing flow.
48enum class PairingState : uint8_t {
49 IDLE, ///< No pairing in progress; idle state.
50 TX_DISCOVER, ///< Discovery broadcast (0x28) sent; awaiting device response.
51 WAIT_DISCOVER_RESPONSE, ///< Listening for discovery response (0x29) from a device in pairing mode.
52 TX_DISCOVER_CONFIRM, ///< Discovery-confirm (0x2C) sent to the discovered device.
53 WAIT_DISCOVER_CONFIRM, ///< Listening for the device's discovery-confirm ack (0x2D).
54 TX_KEY_INIT, ///< Key‑init (0x31) sent to the discovered device.
55 WAIT_KEY_CHALLENGE, ///< Waiting for challenge (0x3C) from device as part of key transfer.
56 TX_KEY_TRANSFER, ///< Key‑transfer (0x32) sent with encrypted system key.
57 WAIT_KEY_CONFIRM, ///< Waiting for key‑confirm (0x33) from device (key receipt acknowledgement).
58 VERIFY_KEY_HELD, ///< Key exchange unconfirmed: checking with an authenticated read whether the
59 ///< device took our key anyway.
60 TX_KEY_PULL, ///< Key transfer request (0x38) sent: `pairing_key_transfer: pull`.
61 WAIT_KEY_PULL_REPLY, ///< Waiting for the device's key transfer (0x32) in answer to our 0x38.
62 VERIFY_PULLED_KEY, ///< Challenging the device to prove it holds the key it sent.
63 SCAN_PULLED_KEY, ///< Roll-call under the pulled key: finding the devices that hold it.
64 REGISTER_DEVICE, ///< Registering device in the runtime registry for the current boot.
65 COMPLETE, ///< Pairing completed successfully; device ready for use.
66 FAILED, ///< Pairing failed (timeout, radio error, or protocol violation).
67};
68
69/// @brief Context object that lives for the duration of a single pairing attempt.
71 PairingState state{PairingState::IDLE}; ///< Current state machine state.
72 IoDevice device{}; ///< Resolved device metadata after discovery (node_id, type, subtype, etc.).
73 IoFrame req{}; ///< Outbound frame buffer (reused across all phases).
74 IoFrame resp{}; ///< Inbound frame buffer (holds key‑confirm response).
75 IoFrame rx{}; ///< Raw RX frame during waiting phases (discovery, challenge, confirm).
76 IoFrame key_init{}; ///< Key‑init frame retained for key‑transfer IV derivation.
77 RadioRxPacket packet{}; ///< Raw radio capture for the current phase.
78 std::string device_id; ///< Hex string representation of the paired node ID.
79 bool discovery_metadata_complete{false}; ///< True when discovery carried type/subtype bytes.
80 bool discovery_low_power{false}; ///< True when discovery reported POWER_SAVE_LOW_POWER.
81 uint8_t discovery_manufacturer{0}; ///< Manufacturer byte of the discovery reply; 0 when it carried none.
82 bool saw_key_challenge{false}; ///< True once the device challenged our key-init this attempt.
83 bool key_refused{false}; ///< True once the device answered the key step with CMD_ERROR_RESP.
84 uint8_t key_refusal_code{0}; ///< That CMD_ERROR_RESP byte; only meaningful if key_refused.
85 bool has_pulled_key{false}; ///< True once a pull produced a different key the device proved.
86 uint8_t pulled_key[AES_KEY_SIZE]{}; ///< That key. Key material: never logged outside the pull report.
87};
88
89/// @brief What PairingEngine::run_discover_confirm_step_() actually observed.
90///
91/// Not stored in PairingContext — nothing downstream reads it, so it is returned directly to the
92/// caller, which logs the details (reply channel, try number, elapsed time) and otherwise ignores
93/// the result: the step never fails a pairing attempt (see run_discover_confirm_step_()'s doc).
94enum class DiscoverConfirmResult : uint8_t {
95 SKIPPED, ///< `pairing_discover_confirm` tuning mode is `skip` — no 0x2C was sent.
96 ACKED, ///< The device answered with CMD_DISCOVER_CONFIRM_ACK (0x2D).
97 ERROR_REPLY, ///< The device answered with CMD_ERROR_RESP.
98 NO_REPLY, ///< Every try was silent (or every transmit failed).
99};
100
101} // namespace pairing
102
103/// @brief Get a short, log/telemetry-friendly name for a pairing state.
104/// @param state Pairing state.
105/// @return Null-terminated lowercase string such as "wait_key_challenge".
106inline const char *pairing_stage_name(pairing::PairingState state) {
107 switch (state) {
109 return "idle";
111 return "tx_discover";
113 return "wait_discover_response";
115 return "tx_discover_confirm";
117 return "wait_discover_confirm";
119 return "tx_key_init";
121 return "wait_key_challenge";
123 return "tx_key_transfer";
125 return "wait_key_confirm";
127 return "verify_key_held";
129 return "tx_key_pull";
131 return "wait_key_pull_reply";
133 return "verify_pulled_key";
135 return "scan_pulled_key";
137 return "register_device";
139 return "complete";
141 default:
142 return "failed";
143 }
144}
145
146} // namespace home_io_control
147} // namespace esphome
DiscoverConfirmResult
What PairingEngine::run_discover_confirm_step_() actually observed.
Definition hub_pairing.h:94
@ ERROR_REPLY
The device answered with CMD_ERROR_RESP.
Definition hub_pairing.h:97
@ SKIPPED
pairing_discover_confirm tuning mode is skip — no 0x2C was sent.
Definition hub_pairing.h:95
@ NO_REPLY
Every try was silent (or every transmit failed).
Definition hub_pairing.h:98
@ ACKED
The device answered with CMD_DISCOVER_CONFIRM_ACK (0x2D).
Definition hub_pairing.h:96
PairingState
State machine for the three‑phase pairing flow.
Definition hub_pairing.h:48
@ REGISTER_DEVICE
Registering device in the runtime registry for the current boot.
Definition hub_pairing.h:64
@ TX_DISCOVER
Discovery broadcast (0x28) sent; awaiting device response.
Definition hub_pairing.h:50
@ WAIT_KEY_PULL_REPLY
Waiting for the device's key transfer (0x32) in answer to our 0x38.
Definition hub_pairing.h:61
@ TX_KEY_PULL
Key transfer request (0x38) sent: pairing_key_transfer: pull.
Definition hub_pairing.h:60
@ WAIT_DISCOVER_RESPONSE
Listening for discovery response (0x29) from a device in pairing mode.
Definition hub_pairing.h:51
@ COMPLETE
Pairing completed successfully; device ready for use.
Definition hub_pairing.h:65
@ TX_DISCOVER_CONFIRM
Discovery-confirm (0x2C) sent to the discovered device.
Definition hub_pairing.h:52
@ WAIT_KEY_CHALLENGE
Waiting for challenge (0x3C) from device as part of key transfer.
Definition hub_pairing.h:55
@ WAIT_KEY_CONFIRM
Waiting for key‑confirm (0x33) from device (key receipt acknowledgement).
Definition hub_pairing.h:57
@ VERIFY_KEY_HELD
Key exchange unconfirmed: checking with an authenticated read whether the device took our key anyway.
Definition hub_pairing.h:58
@ SCAN_PULLED_KEY
Roll-call under the pulled key: finding the devices that hold it.
Definition hub_pairing.h:63
@ VERIFY_PULLED_KEY
Challenging the device to prove it holds the key it sent.
Definition hub_pairing.h:62
@ TX_KEY_INIT
Key‑init (0x31) sent to the discovered device.
Definition hub_pairing.h:54
@ TX_KEY_TRANSFER
Key‑transfer (0x32) sent with encrypted system key.
Definition hub_pairing.h:56
@ IDLE
No pairing in progress; idle state.
Definition hub_pairing.h:49
@ FAILED
Pairing failed (timeout, radio error, or protocol violation).
Definition hub_pairing.h:66
@ WAIT_DISCOVER_CONFIRM
Listening for the device's discovery-confirm ack (0x2D).
Definition hub_pairing.h:53
@ FAILED
No usable reply; the device may never have heard the request.
const char * pairing_stage_name(pairing::PairingState state)
Get a short, log/telemetry-friendly name for a pairing state.
IO-Homecontrol device-type model, capabilities and runtime device state.
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:93
Raw packet received from the radio.
Context object that lives for the duration of a single pairing attempt.
Definition hub_pairing.h:70
uint8_t discovery_manufacturer
Manufacturer byte of the discovery reply; 0 when it carried none.
Definition hub_pairing.h:81
bool saw_key_challenge
True once the device challenged our key-init this attempt.
Definition hub_pairing.h:82
IoFrame req
Outbound frame buffer (reused across all phases).
Definition hub_pairing.h:73
bool discovery_low_power
True when discovery reported POWER_SAVE_LOW_POWER.
Definition hub_pairing.h:80
bool has_pulled_key
True once a pull produced a different key the device proved.
Definition hub_pairing.h:85
PairingState state
Current state machine state.
Definition hub_pairing.h:71
IoFrame resp
Inbound frame buffer (holds key‑confirm response).
Definition hub_pairing.h:74
RadioRxPacket packet
Raw radio capture for the current phase.
Definition hub_pairing.h:77
IoDevice device
Resolved device metadata after discovery (node_id, type, subtype, etc.).
Definition hub_pairing.h:72
uint8_t key_refusal_code
That CMD_ERROR_RESP byte; only meaningful if key_refused.
Definition hub_pairing.h:84
uint8_t pulled_key[AES_KEY_SIZE]
That key. Key material: never logged outside the pull report.
Definition hub_pairing.h:86
IoFrame key_init
Key‑init frame retained for key‑transfer IV derivation.
Definition hub_pairing.h:76
std::string device_id
Hex string representation of the paired node ID.
Definition hub_pairing.h:78
bool key_refused
True once the device answered the key step with CMD_ERROR_RESP.
Definition hub_pairing.h:83
bool discovery_metadata_complete
True when discovery carried type/subtype bytes.
Definition hub_pairing.h:79
IoFrame rx
Raw RX frame during waiting phases (discovery, challenge, confirm).
Definition hub_pairing.h:75