111 uint8_t max_tries = EXCHANGE_RETRY_COUNT, uint16_t request_preamble_override = 0);
302 this->target_evidence_provider_ = std::move(provider);
360 void record_debug(
const char *stage, uint8_t tries,
bool saw_challenge);
364 void log_debug(
const char *device_id)
const;
430 void listen_hop_(uint32_t skip,
const ListenSpec &spec);
435 bool transmit_request_(
const IoFrame &request, uint32_t freq, uint16_t preamble,
461 uint8_t tries_after_unconfirmed_(
const IoFrame &request, uint8_t unconfirmed_tries, uint8_t try_index,
462 uint32_t elapsed_ms);
470 struct PreamblePlan {
474 uint16_t short_preamble{0};
475 uint32_t last_seen_ms{0};
480 [[nodiscard]] uint16_t for_try(uint8_t try_index)
const {
493 [[nodiscard]] PreamblePlan plan_request_preamble_(
const IoFrame &request, uint16_t override_preamble)
const;
497 RadioDriver **radio_ptr_;
498 const uint8_t *node_id_;
499 const uint8_t *system_key_;
501 TransmitObserver *transmit_observer_{
nullptr};
506 uint32_t last_hop_us_{0};
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.
WakeBeliefUse
Whether an exchange's start preamble followed the low-power wake belief, and if not,...
@ NOT_LOW_POWER
Not a low-power start frame: there is no wake-up preamble to reorder.
@ APPLIED
The tries followed the belief in DebugInfo::wake_belief.
void set_transmit_observer(TransmitObserver *observer)
Attach the observer transmit_frame() reports LBT deferrals and sent frames to.
uint8_t collect_broadcast_responses(const IoFrame &request, uint32_t freq, uint8_t expected_cmd, uint32_t window_ms, const BroadcastReplyHandler &on_reply, ListenPolicy policy=ListenPolicy::ROTATE_SKIPPING_REQUEST)
Transmit request once and hand every matching broadcast reply to on_reply within window_ms.
std::function< bool(const uint8_t *dst, decisions::TargetEvidence &out)> TargetEvidenceProvider
Looks up what the hub knows about a destination node (decisions::TargetEvidence).
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.
void log_debug_unconfirmed(const char *device_id) const
Log the debug snapshot for an exchange that ended accepted-but-unconfirmed, at INFO.
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.
std::function< void(const IoFrame &frame, const BroadcastReplyInfo &info)> BroadcastReplyHandler
Invoked for each matching broadcast reply, as it arrives.
uint16_t request_preamble_for(const IoFrame &request) const
Preamble length for an outbound request frame.
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...
static const char * wake_belief_use_name(WakeBeliefUse use)
Log label for a WakeBeliefUse that is not APPLIED (an applied one logs the belief itself).
bool answer_challenge(const IoFrame &request, const IoFrame &challenge, uint32_t freq)
Send a 0x3D challenge response over request's transcript, proving knowledge of the system key to whoe...
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.
ExchangeOutcome send_and_receive(const IoFrame &request, IoFrame &response, uint32_t freq, uint8_t max_tries=EXCHANGE_RETRY_COUNT, uint16_t request_preamble_override=0)
Execute an outbound authenticated exchange with retry.
void reset_hop_timestamp()
Reset the hop-timer (called after radio init in hub setup()).
ExchangeEngine & operator=(const ExchangeEngine &)=delete
void set_target_evidence_provider(TargetEvidenceProvider provider)
Install the source of per-target evidence.
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.
Abstract radio driver for IO-Homecontrol.
Receives transmit events from ExchangeEngine::transmit_frame().
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.
WakeBelief
How likely a low-power receiver is to be awake right now, judged from what this hub has seen of it.
@ ASLEEP
No recent sign of life — lead with the wake-up preamble.
uint16_t low_power_try_preamble(WakeBelief belief, uint8_t try_index, uint16_t short_preamble)
Start-preamble length for one try of a directed exchange to a low-power receiver.
static constexpr size_t EXCHANGE_DEBUG_LINE_SIZE
Longest rendered exchange-debug field list, plus headroom for a long command name.
int render_exchange_debug(char *buf, size_t buf_size, const char *device_id, const ExchangeEngine::DebugInfo &d)
Render the structured field list shared by both exchange-debug log lines.
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.
ListenPolicy
Which channels a listen covers.
@ ROTATE_SKIPPING_REQUEST
The two channels that are not the request channel.
ListenOutcome
How one call to ExchangeEngine::listen() ended.
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.
Per-reply facts collect_broadcast_responses() hands its caller alongside the frame.
uint32_t rx_freq_hz
Channel the reply was received on (RadioRxPacket::freq_hz).
int16_t rssi_dbm
RSSI of this reply (from the radio's last capture).
uint32_t after_tx_ms
Milliseconds from the request's transmit completing to this reply's delivery.
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.
uint16_t final_rx_irq
Radio IRQ status at the most recent of those failed receptions.
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.
decisions::WakeBelief wake_belief
Belief the tries followed; only meaningful when wake_belief_use is WakeBeliefUse::APPLIED.
uint8_t capture_packet_status
Chip packet-status byte.
uint8_t final_rx_ignored
Frames received during those waits and not accepted as the reply.
bool capture_valid
True if radio capture is meaningful.
uint8_t request_cmd
Command ID of the original request.
uint16_t last_try_preamble
Preamble (bytes) of the most recent request transmit attempt, 0 = none.
bool capture_rx_done
True if RxDone IRQ fired.
WakeBeliefUse wake_belief_use
Whether the tries followed wake_belief, and why not if they did not.
uint8_t final_rx_failed
Receptions started during those waits that could not be decoded.
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.
uint8_t final_waits
Final-reply waits run (one per try that got as far as our 0x3D).
Parsed IO‑Homecontrol frame (CTRL0/1 + addresses + command + data).
How one listen window is to be spent — everything ExchangeEngine::listen() needs; everything else is ...
What a listen heard without accepting it — filled by ExchangeEngine::listen() when a caller passes on...
Raw packet received from the radio.
All runtime tunable parameters for pairing and radio diagnostics.
What the hub knows about the device an exchange is addressed to, as the exchange engine sees it.
Context carried across one outbound authenticated exchange.
Observer interface for every frame the exchange engine puts on air.
Runtime tuning configuration for pairing and radio diagnostics.