Home IO Control
ESPHome add-on for IO-Homecontrol devices
Loading...
Searching...
No Matches
esphome::home_io_control::KeyExtractionResponder Class Reference

Device-role responder for the "Recover System Key" feature. More...

#include <key_extraction_responder.h>

Collaboration diagram for esphome::home_io_control::KeyExtractionResponder:

Public Member Functions

 KeyExtractionResponder (const uint8_t *node_id, RadioDriver **radio, const TuningConfig *tuning, DeviceRegistry &registry, TransmitFrameFn transmit, NamedTimeoutFn schedule_auto_off)
 KeyExtractionResponder (const KeyExtractionResponder &)=delete
 Non-copyable — holds injected callbacks and references into hub member addresses.
KeyExtractionResponder & operator= (const KeyExtractionResponder &)=delete
void set_armed (bool armed)
 Arm or disarm the "Recover System Key" (key extraction) responder.
void set_armed_callback (std::function< void(bool)> cb)
 Register a callback invoked whenever the key-extraction armed state changes — manual toggle, successful extraction, or auto-off timeout — so the switch entity can keep its displayed state in sync when the responder disarms itself rather than the user.
bool try_handle_frame (const IoFrame &frame, uint32_t rx_freq_hz=0)
 Dispatch a frame to the responder if it's one of its 0x28/0x2C/0x31/0x32/0x36/0x3C frames and the responder is armed.
uint32_t session_freq () const
 Channel of the session the responder last replied in: the channel the frame it answered came in on, CH2 until it has answered anything.
bool in_session_hold () const
 True while awaiting_reply() and the frame the responder last answered was received less than the session window ago (key_extraction_session_window_ms() of the byte the last discovery answer advertised): loop() then stays on session_freq() for the peer's in-session frame.
uint32_t session_window_ms () const
 Session window for the Multi Information Byte the last discovery answer advertised.
bool discover_reply_pending () const
 True while a discovery answer waits for its slot (schedule_discover_reply_()).
uint32_t ms_until_discover_reply () const
 Milliseconds until the pending discovery answer's slot, 0 once it is due.
void generate_throwaway_id (uint8_t out[NODE_ID_SIZE])
 Generate a random throwaway node ID for one key-extraction arm cycle, avoiding collisions with the broadcast addresses, this hub's own real node ID, and any registered device.
void arm_post_extraction_grace ()
 (Re)arm the post-extraction grace window that replaces the old immediate disarm-on-extraction: called once when the key is first recovered, and again on every sign of hub progress after that (an inbound 0x36, an outbound 0x3D) so a slow multi-retry hub isn't cut off mid-round.
bool awaiting_reply () const
 True whenever the responder has replied at least once, is waiting on the hub's next step, and that wait is still within its bounded hold window — i.e.

Public Attributes

pairing_responder::ResponderContext key_extraction_ctx_
 State for the current "Accept Foreign Pairing" (key-extraction) arm cycle; DISARMED by default so a fresh boot never responds to foreign pairing traffic.
uint32_t key_extraction_hold_deadline_ms_ {0}
 Deadline (millis()) until which loop() listens for the key-extraction responder's peer, deliberately independent of key_extraction_ctx_.state itself.

Detailed Description

Device-role responder for the "Recover System Key" feature.

Constructed once by IOHomeControlComponent; non-copyable because it is wired with injected callbacks and references into hub member addresses (mirrors ManagementActions / ExchangeEngine).

Definition at line 61 of file key_extraction_responder.h.

Constructor & Destructor Documentation

◆ KeyExtractionResponder() [1/2]

esphome::home_io_control::KeyExtractionResponder::KeyExtractionResponder ( const uint8_t * node_id,
RadioDriver ** radio,
const TuningConfig * tuning,
DeviceRegistry & registry,
TransmitFrameFn transmit,
NamedTimeoutFn schedule_auto_off )
Parameters
node_idHub's real 3-byte node ID (throwaway-ID collision check).
radioDouble pointer to the hub's active radio driver, so a test's comp.radio_ = &mock propagates (mirrors ExchangeEngine).
tuningRuntime tuning config, owned by the hub (cold_broadcast_reply_preamble).
registryDevice registry, for the throwaway-ID collision check only.
transmitHow to put a reply frame on air (see TransmitFrameFn).
schedule_auto_offNamed-timeout scheduler for the 10-minute arm window and the post-extraction grace window (see NamedTimeoutFn).

Definition at line 131 of file key_extraction_responder.cpp.

◆ KeyExtractionResponder() [2/2]

esphome::home_io_control::KeyExtractionResponder::KeyExtractionResponder ( const KeyExtractionResponder & )
delete

Non-copyable — holds injected callbacks and references into hub member addresses.

Here is the call graph for this function:

Member Function Documentation

◆ arm_post_extraction_grace()

void esphome::home_io_control::KeyExtractionResponder::arm_post_extraction_grace ( )

(Re)arm the post-extraction grace window that replaces the old immediate disarm-on-extraction: called once when the key is first recovered, and again on every sign of hub progress after that (an inbound 0x36, an outbound 0x3D) so a slow multi-retry hub isn't cut off mid-round.

Uses the same named-timer replace-on-reschedule idiom as the 10-minute auto-off timer — see key_extraction_responder.cpp for why a naive "only disarm if DISARMED" guard inside the callback is not enough once a manual disarm-and-rearm can happen inside the window.

Definition at line 493 of file key_extraction_responder.cpp.

Here is the call graph for this function:

◆ awaiting_reply()

bool esphome::home_io_control::KeyExtractionResponder::awaiting_reply ( ) const
inlinenodiscard

True whenever the responder has replied at least once, is waiting on the hub's next step, and that wait is still within its bounded hold window — i.e.

key_extraction_ctx_.state is neither DISARMED (feature unused) nor ARMED_IDLE (armed, but no discovery request seen yet), AND key_extraction_hold_deadline_ms_ has not yet passed. While it is true, loop() defers background status polls and, instead of the generic idle hop, listens for the peer's next frame: on session_freq() during in_session_hold(), then with a rotating listen over all three channels.

The deadline is a plain timestamp, not a named set_timeout() timer: ending this listen is purely a radio-scheduling optimization (see key_extraction_hold_deadline_ms_'s own doc comment for why it is deliberately decoupled from key_extraction_ctx_.state itself), so nothing needs to fire a callback when it lapses — the next loop() iteration simply stops taking the responder-listen branch on its own. Default-constructed, the deadline is 0, which is always in the past relative to any real millis() reading once the device has been running — so a mid-exchange state reached without the deadline having been (re)set (e.g. a reply-builder failure between the state guard and the deadline update) safely never takes the radio, rather than holding it unboundedly.

Defined inline: defer_background_poll_() calls this every loop() iteration.

Definition at line 170 of file key_extraction_responder.h.

◆ discover_reply_pending()

bool esphome::home_io_control::KeyExtractionResponder::discover_reply_pending ( ) const
inlinenodiscard

True while a discovery answer waits for its slot (schedule_discover_reply_()).

Definition at line 125 of file key_extraction_responder.h.

◆ generate_throwaway_id()

void esphome::home_io_control::KeyExtractionResponder::generate_throwaway_id ( uint8_t out[NODE_ID_SIZE])

Generate a random throwaway node ID for one key-extraction arm cycle, avoiding collisions with the broadcast addresses, this hub's own real node ID, and any registered device.

Parameters
outOutput: 3-byte node ID.

Definition at line 141 of file key_extraction_responder.cpp.

Here is the call graph for this function:

◆ in_session_hold()

bool esphome::home_io_control::KeyExtractionResponder::in_session_hold ( ) const
inlinenodiscard

True while awaiting_reply() and the frame the responder last answered was received less than the session window ago (key_extraction_session_window_ms() of the byte the last discovery answer advertised): loop() then stays on session_freq() for the peer's in-session frame.

Once false (while still awaiting a reply), loop() scans all three channels.

Definition at line 117 of file key_extraction_responder.h.

Here is the call graph for this function:

◆ ms_until_discover_reply()

uint32_t esphome::home_io_control::KeyExtractionResponder::ms_until_discover_reply ( ) const
inlinenodiscard

Milliseconds until the pending discovery answer's slot, 0 once it is due.

The answer goes out from the scheduler, which runs only between loop() passes, so a blocking listen must end by then or the answer slips past its slot and, for the last slots, out of the searcher's window. Meaningful only while discover_reply_pending().

Definition at line 134 of file key_extraction_responder.h.

◆ operator=()

KeyExtractionResponder & esphome::home_io_control::KeyExtractionResponder::operator= ( const KeyExtractionResponder & )
delete
Here is the call graph for this function:

◆ session_freq()

uint32_t esphome::home_io_control::KeyExtractionResponder::session_freq ( ) const
inlinenodiscard

Channel of the session the responder last replied in: the channel the frame it answered came in on, CH2 until it has answered anything.

Replies go out on it (the 0x29 ends on it), and loop() stays on it while in_session_hold().

Definition at line 111 of file key_extraction_responder.h.

◆ session_window_ms()

uint32_t esphome::home_io_control::KeyExtractionResponder::session_window_ms ( ) const
inlinenodiscard

Session window for the Multi Information Byte the last discovery answer advertised.

Definition at line 122 of file key_extraction_responder.h.

◆ set_armed()

void esphome::home_io_control::KeyExtractionResponder::set_armed ( bool armed)

Arm or disarm the "Recover System Key" (key extraction) responder.

Arming picks a fresh throwaway node ID, resets the pairing_responder state machine to ARMED_IDLE, and schedules a 10-minute auto-off. While armed, the 0x28/0x2C/0x31/0x32 branches in process_received_packet_() emulate an unpaired device so a user's existing hub can pair to it and hand over its node_id/system_key (see pairing_responder.h). Disarming — manual, via the HA switch, on successful extraction, or on auto-off — immediately stops those branches from responding; it never touches the real device registry or the hub's own node_id_/ system_key_. This is the body that was IOHomeControlComponent::set_key_extraction_armed().

Parameters
armedDesired state.

Definition at line 161 of file key_extraction_responder.cpp.

Here is the call graph for this function:

◆ set_armed_callback()

void esphome::home_io_control::KeyExtractionResponder::set_armed_callback ( std::function< void(bool)> cb)
inline

Register a callback invoked whenever the key-extraction armed state changes — manual toggle, successful extraction, or auto-off timeout — so the switch entity can keep its displayed state in sync when the responder disarms itself rather than the user.

Single-slot.

Parameters
cbCallable receiving the new armed state.

Definition at line 95 of file key_extraction_responder.h.

◆ try_handle_frame()

bool esphome::home_io_control::KeyExtractionResponder::try_handle_frame ( const IoFrame & frame,
uint32_t rx_freq_hz = 0 )
nodiscard

Dispatch a frame to the responder if it's one of its 0x28/0x2C/0x31/0x32/0x36/0x3C frames and the responder is armed.

Kept a separate function (rather than inlined into process_received_packet_()) purely to keep that function's cognitive complexity under the clang-tidy threshold, mirroring PairingEngine::record_discovery_rx_telemetry_()'s reason for existing.

Parameters
frameParsed inbound frame.
rx_freq_hzChannel the frame was received on (RadioRxPacket::freq_hz); 0 if unknown, which counts as CH2. Answering the frame makes it the session channel.
Returns
true if the frame was handled (caller should stop further dispatch for it).

Definition at line 206 of file key_extraction_responder.cpp.

Member Data Documentation

◆ key_extraction_ctx_

pairing_responder::ResponderContext esphome::home_io_control::KeyExtractionResponder::key_extraction_ctx_

State for the current "Accept Foreign Pairing" (key-extraction) arm cycle; DISARMED by default so a fresh boot never responds to foreign pairing traffic.

See pairing_responder.h. Public so the host tests that script individual RX branches can preset and inspect it.

Definition at line 179 of file key_extraction_responder.h.

◆ key_extraction_hold_deadline_ms_

uint32_t esphome::home_io_control::KeyExtractionResponder::key_extraction_hold_deadline_ms_ {0}

Deadline (millis()) until which loop() listens for the key-extraction responder's peer, deliberately independent of key_extraction_ctx_.state itself.

Set (not "armed" — this is a plain timestamp, not a named set_timeout() timer) on every sign of hub progress: the three pre-extraction reply handlers (handle_discover_(), handle_discover_confirm_(), handle_key_init_(), key_extraction_responder.cpp) push it out by KEY_EXTRACTION_MID_ATTEMPT_TIMEOUT_MS, and arm_post_extraction_grace() pushes it out by KEY_EXTRACTION_POST_EXTRACT_GRACE_MS so the hold also covers the (much longer) post-extraction node-verification phase it governs.

Deliberately independent of key_extraction_ctx_.state: the pure guards in pairing_responder.cpp decide whether an inbound frame is accepted by checking state alone, never this deadline, so a real (slower) hub's next frame still completes the exchange correctly even if it arrives after the hold has expired. Coupling the two — letting the deadline also force state back to ARMED_IDLE — would silently discard that live protocol progress instead of just releasing the radio hold. See awaiting_reply()'s doc comment for how this is consumed.

Definition at line 196 of file key_extraction_responder.h.


The documentation for this class was generated from the following files: