243 void record_debug(
const char *stage, uint8_t tries,
bool saw_challenge);
247 void log_debug(
const char *device_id)
const;
302 void listen_hop_(uint32_t skip,
const ListenSpec &spec);
307 bool transmit_request_(
const IoFrame &request, uint32_t freq, uint16_t preamble,
315 [[nodiscard]] uint16_t request_preamble_for_(
const IoFrame &request)
const;
331 const uint8_t *node_id_;
332 const uint8_t *system_key_;
338 uint32_t last_hop_us_{0};
std::function< void(const IoFrame &frame, int16_t rssi_dbm)> BroadcastReplyHandler
Invoked for each matching broadcast reply, as it arrives.
void reset_counters()
Zero every counter (e.g.
ListenOutcome listen(const ListenSpec &spec, RadioRxPacket &packet, IoFrame &frame, const ReplyHandler &on_frame)
The one listen primitive every radio wait loop in this project is built on.
ExchangeOutcome send_and_receive(const IoFrame &request, IoFrame &response, uint32_t freq, uint8_t max_tries=EXCHANGE_RETRY_COUNT)
Execute an outbound authenticated exchange with retry.
void maybe_hop()
Hop only if the minimum dwell has elapsed and no frame is currently arriving on this channel — RadioD...
void reset_debug(uint8_t request_cmd)
Clear the debug snapshot and record the upcoming request command.
uint8_t collect_broadcast_responses(const IoFrame &request, uint32_t freq, uint8_t expected_cmd, uint32_t window_ms, const BroadcastReplyHandler &on_reply)
Transmit request once and hand every matching broadcast reply to on_reply within window_ms.
void log_debug(const char *device_id) const
Log the debug snapshot as a WARN-level structured line.
const DebugInfo & get_debug() const
Read-only access to the current debug snapshot.
void set_pairing_telemetry(PairingTelemetry *telemetry)
Attach a telemetry recorder so transmit_frame()'s LBT loop records defer events.
ExchangeEngine(RadioDriver **radio_ptr, const uint8_t *node_id, const uint8_t *system_key, const TuningConfig *tuning)
Construct the engine with double-pointer indirection into the hub's RadioDriver pointer and direct po...
bool transmit_frame(const IoFrame &frame, uint32_t freq, uint16_t preamble)
Transmit a raw IoFrame with LBT and the given preamble length.
bool authenticate_request(const IoFrame &request, uint32_t freq)
Authenticate an inbound device command via 0x3C challenge / 0x3D HMAC.
void reset_hop_timestamp()
Reset the hop-timer (called after radio init in hub setup()).
ExchangeEngine & operator=(const ExchangeEngine &)=delete
const Counters & counters() const
Read-only access to the running counters.
ExchangeEngine(const ExchangeEngine &)=delete
void hop_frequency(uint32_t skip_freq=0)
Advance the receiver one step along the protocol's channel rotation (CH1→CH2→CH3→CH1).
void record_debug(const char *stage, uint8_t tries, bool saw_challenge)
Update the debug snapshot with the current stage and radio capture.
Fixed-size per-attempt telemetry recorder for the pairing flow.
Abstract radio driver for IO-Homecontrol.
Pure transition helpers for hub-owned exchange and pairing frame decisions.
Internal exchange-state model for hub-owned authenticated non‑pairing flows.
ExchangeFinalResponseDisposition
Disposition for the final response after authentication.
ExchangeFirstResponseDisposition
Disposition for the first response in an authenticated exchange.
std::function< ReplyDisposition(const IoFrame *parsed, const RadioRxPacket &packet)> ReplyHandler
Invoked for every packet the radio delivers during a listen, before the listen decides whether to kee...
ExchangeOutcome
Authenticated exchange engine — outbound and inbound protocol flows.
@ SUCCESS_WITH_RESPONSE
Device replied; the caller's response frame is populated.
@ SUCCESS_UNCONFIRMED
Device authenticated the request — so it received and accepted it — but sent no final response.
@ FAILED
No usable reply; the device may never have heard the request.
static constexpr uint8_t EXCHANGE_RETRY_COUNT
Attempts per command before reporting failure.
ListenOutcome
How one call to ExchangeEngine::listen() ended.
Structured per-attempt telemetry recorder for the pairing flow.
IO-Homecontrol 2W frame container: control bytes, IoFrame and (de)serialization.
Physical-layer radio and timing parameters for the IO-Homecontrol protocol.
Radio abstraction layer for IO-Homecontrol.
Free-running counters for the engine's own retry/parse behavior — not per-device (see the RSSI/Exchan...
uint32_t parse_failures
Frames wait_for_first_response_()/wait_for_final_response_() could not parse; does not count pairing_...
uint32_t challenge_round_trips
Completed 0x3C/0x3D challenge-response cycles, either direction: a device challenging our outbound co...
uint32_t lbt_retries
transmit_frame() LBT backoff iterations (channel busy); covers pairing traffic too,...
uint32_t retransmits
send_and_receive() TX attempts beyond the first; no pairing path.
Snapshot of the last exchange attempt for diagnostics.
bool saw_challenge
True if a 0x3C was seen during this exchange.
uint8_t capture_reported_len
Length reported by radio packet engine.
int16_t capture_rssi_dbm
RSSI of the captured packet (dBm).
bool capture_crc_error
True if CRC error flagged; see RadioCaptureInfo::crc_error.
uint8_t tries
Retry count (1-based).
uint16_t capture_irq_status
Raw IRQ register value.
uint8_t capture_frame_len
Parsed protocol frame length.
uint8_t capture_packet_status
Chip packet-status byte.
bool capture_valid
True if radio capture is meaningful.
uint8_t request_cmd
Command ID of the original request.
bool capture_rx_done
True if RxDone IRQ fired.
const char * stage
Last recorded stage label.
uint32_t capture_freq_hz
RF frequency of the captured packet.
uint8_t max_tries
Attempt cap this exchange was budgeted for.
Parsed IO‑Homecontrol frame (CTRL0/1 + addresses + command + data).
How one listen window is to be spent — everything ExchangeEngine::listen() needs; everything else is ...
Raw packet received from the radio.
All runtime tunable parameters for pairing and radio diagnostics.
Context carried across one outbound authenticated exchange.
Runtime tuning configuration for pairing and radio diagnostics.