83 const uint8_t expected_dst[NODE_ID_SIZE]) {
84 return std::memcmp(frame.
src, expected_src, NODE_ID_SIZE) == 0 &&
85 std::memcmp(frame.
dst, expected_dst, NODE_ID_SIZE) == 0;
111 if (candidate.
cmd == CMD_CHALLENGE_REQ)
136 return request.
cmd == CMD_EXECUTE && request.
data_len > EXECUTE_MAIN_BYTE_OFFSET &&
137 request.
data[EXECUTE_MAIN_BYTE_OFFSET] != POS_FAVORITE;
161 uint8_t unconfirmed_tries) {
162 if (request.
cmd != CMD_EXECUTE)
165 unconfirmed_tries <= UNCONFIRMED_EXECUTE_MAX_RESENDS;
183 const uint8_t controller_id[NODE_ID_SIZE]) {
184 return candidate.
cmd == CMD_DISCOVER_RESP && std::memcmp(candidate.
dst, controller_id, NODE_ID_SIZE) == 0
202 const uint8_t device_id[NODE_ID_SIZE],
203 const uint8_t controller_id[NODE_ID_SIZE]) {
207 return candidate.
cmd == CMD_CHALLENGE_REQ && candidate.
data_len == HMAC_SIZE &&
224 const IoFrame &candidate,
const uint8_t device_id[NODE_ID_SIZE],
const uint8_t controller_id[NODE_ID_SIZE]) {
227 if (candidate.
cmd == CMD_DISCOVER_CONFIRM_ACK)
229 if (candidate.
cmd == CMD_ERROR_RESP)
247 if (candidate.
cmd == CMD_KEY_CONFIRM)
294 uint8_t
dst[NODE_ID_SIZE]{};
309 if (!incoming.
has_intent && std::memcmp(last.
dst, incoming.
dst, NODE_ID_SIZE) != 0)
342 uint32_t last_1w_activity_ms, uint32_t now, uint32_t quiet_ms,
343 uint32_t max_defer_ms) {
344 if (!next_op_is_background || last_1w_activity_ms == 0)
346 if (now - first_1w_activity_ms >= max_defer_ms)
348 return (now - last_1w_activity_ms) < quiet_ms;
370 bool settles_a_stop =
false) {
372 return STOP_SETTLE_POLL_TRIES;
373 if (auth_poll_failures != 0)
374 return SCHEDULED_POLL_MAX_TRIES;
375 if (status_poll_failures < SCHEDULED_POLL_RETRY_GRACE_FIRST_FAILURE ||
376 status_poll_failures > SCHEDULED_POLL_RETRY_GRACE_LAST_FAILURE)
377 return SCHEDULED_POLL_MAX_TRIES;
378 return EXCHANGE_RETRY_COUNT;
392static_assert(LOW_POWER_AWAKE_HOLD_MS <= LOW_POWER_MAX_TRAVEL_MS,
393 "wake_belief() relies on moving evidence outliving the maybe-awake hold");
417 return request.
cmd == CMD_EXECUTE && request.
data_len > EXECUTE_MAIN_BYTE_OFFSET &&
418 request.
data[EXECUTE_MAIN_BYTE_OFFSET] == POS_STOP;
429 const auto recent = [now](uint32_t stamp, uint32_t window_ms) {
return stamp != 0 && (now - stamp) < window_ms; };
434 if (recent(evidence.
last_seen_ms, LOW_POWER_AWAKE_HOLD_MS))
449 const uint8_t try_1based = std::max<uint8_t>(1, std::min<uint8_t>(try_index, EXCHANGE_RETRY_COUNT));
452 return try_1based == 2 ? LONG_PREAMBLE : short_preamble;
454 return try_1based == 1 ? short_preamble : LONG_PREAMBLE;
457 return LONG_PREAMBLE;
467 return "maybe_awake";
489 if (std::memcmp(dst, BROADCAST_DISCOVER_ALT, NODE_ID_SIZE) != 0)
491 return cmd == CMD_WRITE_PRIVATE || cmd == CMD_ONEWAY_REMOVE || cmd == CMD_DISCOVER_ALT_REQ;
505 return last_1w_activity_ms == 0 || (now - last_1w_activity_ms) >= quiet_ms;
527 (
static_cast<uint32_t
>(node_id[1]) << BITS_PER_BYTE) |
static_cast<uint32_t
>(node_id[2]);
bool is_repeatable_execute(const IoFrame &request)
True for a CMD_EXECUTE that has the same effect sent twice as sent once.
PairingDiscoveryDisposition
Disposition during pairing discovery phase.
@ ACCEPT
Valid discovery response received.
@ NO_RESPONSE
No packets received on the channel within timeout.
@ INVALID
Packets seen but none were valid discovery (0x29) frames.
bool is_one_way_pairing_gesture(bool oneway, const uint8_t dst[NODE_ID_SIZE], uint8_t cmd)
True if a frame's shape matches a 1W remote's pairing gesture (issue #27/#65): CTRL0 1W bit set,...
bool frame_matches_nodes(const IoFrame &frame, const uint8_t expected_src[NODE_ID_SIZE], const uint8_t expected_dst[NODE_ID_SIZE])
Check if two frames have identical src/dst node IDs.
bool is_duplicate_1w_frame(const OneWayDedupState &last, const OneWayDedupState &incoming, uint32_t window_ms)
Decide whether an incoming 1W frame repeats the previous one inside the burst window.
bool is_exchange_internal_command(uint8_t cmd)
Returns true for commands that are internal to an exchange handshake and carry no useful information ...
PairingKeyChallengeDisposition classify_pairing_key_challenge(const IoFrame &candidate, const uint8_t device_id[NODE_ID_SIZE], const uint8_t controller_id[NODE_ID_SIZE])
Decide if a frame is a valid key-challenge (0x3C) during pairing key exchange.
ExchangeFirstResponseDisposition classify_exchange_first_response(const IoFrame &request, const IoFrame &candidate)
Decide how to handle the first response packet in an authenticated exchange.
bool retry_after_unconfirmed_accept_is_safe(const IoFrame &request, bool target_confirms_execute, uint8_t unconfirmed_tries)
Whether an authenticated-but-unanswered request may be sent again.
ExchangeFinalResponseDisposition
Disposition for the final response after authentication.
@ ACCEPT
Frame matches expected response — exchange succeeds.
@ IGNORE_UNRELATED
Frame doesn't match endpoints — ignore.
bool oneway_burst_started_fresh(uint32_t last_1w_activity_ms, uint32_t now, uint32_t quiet_ms)
Whether a 1W frame arriving at now starts a new burst rather than extending the current one — true if...
PairingKeyChallengeDisposition
Disposition during pairing key-challenge phase.
@ ACCEPT
Valid 0x3C challenge from target device.
@ IGNORE
Not a valid challenge (wrong cmd, length, or sender).
bool is_stop_request(const IoFrame &request)
True for a CMD_EXECUTE whose main byte is POS_STOP.
ExchangeFirstResponseDisposition
Disposition for the first response in an authenticated exchange.
@ REQUIRE_AUTH
Matching 0x3C challenge — device demands authentication.
@ IGNORE_UNRELATED
Frame doesn't match endpoints or failed parse — keep waiting.
@ COMPLETE_DIRECT
Matching non-challenge frame — operation complete, no auth needed.
PairingKeyConfirmDisposition classify_pairing_key_confirm_reply(const IoFrame &request, const IoFrame &candidate)
Decide how to handle a candidate reply to the key-transfer (0x32) confirm wait (wait_for_key_confirm_...
PairingKeyConfirmDisposition
Disposition for a candidate reply to the key-transfer (0x32) confirm wait — the slow-turnaround path'...
@ REFUSE
Anything else (including CMD_ERROR_RESP) — an explicit or implicit refusal; ends the whole wait,...
@ CHALLENGE
A fresh CMD_CHALLENGE_REQ (0x3C) — the device may challenge the key transfer before confirming it; an...
@ IGNORE
Not from/to the expected endpoints — keep waiting.
@ CONFIRM
CMD_KEY_CONFIRM (0x33) — the device accepted the key.
TargetEvidence target_evidence(const IoDevice &dev)
Build the exchange engine's view of a device record.
bool defer_background_poll_for_1w_activity(bool next_op_is_background, uint32_t first_1w_activity_ms, uint32_t last_1w_activity_ms, uint32_t now, uint32_t quiet_ms, uint32_t max_defer_ms)
Decide whether to hold back a queued background poll because a 1W remote is still transmitting.
const char * wake_belief_name(WakeBelief belief)
Lowercase name of a belief for log lines.
PairingDiscoveryDisposition classify_pairing_discovery_response(const IoFrame &candidate, const uint8_t controller_id[NODE_ID_SIZE])
Decide if a frame is a valid discovery response (0x29) during pairing.
PairingDiscoverConfirmDisposition classify_pairing_discover_confirm_reply(const IoFrame &candidate, const uint8_t device_id[NODE_ID_SIZE], const uint8_t controller_id[NODE_ID_SIZE])
Decide how to handle a candidate reply to a discovery-confirm request (0x2C) during pairing.
WakeBelief
How likely a low-power receiver is to be awake right now, judged from what this hub has seen of it.
@ MAYBE_AWAKE
Recently heard from — try the short preamble first, keep the wake-up as fallback.
@ ASLEEP
No recent sign of life — lead with the wake-up preamble.
@ AWAKE
Moving, or about to be stopped — the short preamble is the one it hears.
constexpr uint32_t REMOTE_POLL_TIMER_ID_TAG
Namespace tag for remote_poll_timer_id() below: the node address occupies the low 24 bits,...
WakeBelief wake_belief(const TargetEvidence &evidence, uint32_t now, bool is_stop)
Judge how awake a low-power receiver is.
PairingDiscoverConfirmDisposition
Disposition for a candidate reply to a discovery-confirm request (0x2C).
@ ACK
CMD_DISCOVER_CONFIRM_ACK (0x2D) from the device — it wants to proceed.
@ IGNORE
Not from/to the expected endpoints, or a frame the step does not act on (e.g.
@ ERROR
CMD_ERROR_RESP from the device — an explicit, named refusal.
bool discover_confirm_try_rotates(uint8_t try_index)
Whether discover-confirm try try_index (1-based) should rotate channels rather than hold the request ...
bool frame_matches_exchange_endpoints(const IoFrame &request, const IoFrame &candidate)
Check if candidate frame endpoints are the reverse of the request (dst==request.src,...
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.
uint32_t remote_poll_timer_id(const uint8_t node_id[NODE_ID_SIZE])
Numeric set_timeout() id for a device's remote-activity poll timer (hub_status.cpp's schedule_status_...
ExchangeFinalResponseDisposition classify_exchange_final_response(const IoFrame &request, const IoFrame &candidate)
Decide if a candidate frame is an acceptable final response after authentication.
uint8_t scheduled_poll_max_tries(uint8_t status_poll_failures, uint8_t auth_poll_failures, bool settles_a_stop=false)
Transmit-attempt budget for a scheduler-owned status poll, by backoff-ladder position.
IO-Homecontrol command IDs, result codes and protocol enumerations.
IO-Homecontrol device-type model, capabilities and runtime device state.
IO-Homecontrol 2W frame container: control bytes, IoFrame and (de)serialization.
Physical-layer radio and timing parameters for the IO-Homecontrol protocol.
Runtime state of a paired IO‑Homecontrol device.
uint32_t last_moving_evidence_ms
millis() of the last sign this device is travelling (see note_moving_evidence(), clear_moving_evidenc...
uint32_t last_seen_ms
millis() of the last frame received from this device (any command), 0 = never.
bool confirms_execute
True once this device has answered a CMD_EXECUTE with a reply that closed the exchange (a status,...
Parsed IO‑Homecontrol frame (CTRL0/1 + addresses + command + data).
uint8_t data[FRAME_MAX_DATA_SIZE]
Command parameters (0–23 bytes). Never includes mac.
uint8_t src[NODE_ID_SIZE]
Source node ID (3 bytes).
uint8_t dst[NODE_ID_SIZE]
Destination node ID (3 bytes).
uint8_t data_len
Actual length of data.
Key fields of the last processed 1W frame, used to collapse a remote's repeat burst.
uint8_t dst[NODE_ID_SIZE]
Destination address; compared only when has_intent is false.
uint8_t main1
Second main byte.
std::string src_id
Source node ID of the last processed frame; empty before the first.
uint8_t main0
First main byte — what distinguishes a move from a stop.
uint32_t timestamp
millis() when the frame was processed.
bool has_intent
Whether main0/main1 were decoded (execute / activate-mode only).
What the hub knows about the device an exchange is addressed to, as the exchange engine sees it.
uint32_t last_seen_ms
Last frame received from the receiver, any command.
bool confirms_execute
The device has closed a CMD_EXECUTE exchange with a reply before.
uint32_t last_moving_evidence_ms
Last sign the receiver is travelling (see note_moving_evidence(), clear_moving_evidence()).