88 if (frame.
cmd == CMD_SET_CONFIG1_RESP)
101 if (request.
cmd != CMD_SUBSCRIBE_EVENT || request.
data_len != SUBSCRIBE_EVENT_DATA_SIZE ||
102 request.
data[0] != EVENT_SUBSCRIPTION_SUBSCRIBE)
104 if (memcmp(response.
src, request.
dst, NODE_ID_SIZE) != 0)
109 const auto requested =
static_cast<uint16_t
>((request.
data[1] << BITS_PER_BYTE) | request.
data[2]);
118 return cmd == CMD_EXECUTE || cmd == CMD_ACTIVATE_MODE || cmd == CMD_WRITE_PRIVATE;
125 const uint8_t expected_dst[NODE_ID_SIZE]) {
126 return std::memcmp(frame.
src, expected_src, NODE_ID_SIZE) == 0 &&
127 std::memcmp(frame.
dst, expected_dst, NODE_ID_SIZE) == 0;
153 if (candidate.
cmd == CMD_CHALLENGE_REQ)
179 return request.
cmd == CMD_EXECUTE && request.
data_len > EXECUTE_MAIN_BYTE_OFFSET &&
180 request.
data[EXECUTE_MAIN_BYTE_OFFSET] != POS_FAVORITE;
211 uint8_t unconfirmed_tries) {
212 if (request.
cmd == CMD_SUBSCRIBE_EVENT)
213 return unconfirmed_tries <= UNCONFIRMED_SUBSCRIBE_MAX_RESENDS;
214 if (request.
cmd != CMD_EXECUTE)
217 unconfirmed_tries <= UNCONFIRMED_EXECUTE_MAX_RESENDS;
235 const uint8_t controller_id[NODE_ID_SIZE]) {
236 return candidate.
cmd == CMD_DISCOVER_RESP && std::memcmp(candidate.
dst, controller_id, NODE_ID_SIZE) == 0
254 const uint8_t device_id[NODE_ID_SIZE],
255 const uint8_t controller_id[NODE_ID_SIZE]) {
259 return candidate.
cmd == CMD_CHALLENGE_REQ && candidate.
data_len == HMAC_SIZE &&
276 const IoFrame &candidate,
const uint8_t device_id[NODE_ID_SIZE],
const uint8_t controller_id[NODE_ID_SIZE]) {
279 if (candidate.
cmd == CMD_DISCOVER_CONFIRM_ACK)
281 if (candidate.
cmd == CMD_ERROR_RESP)
299 if (candidate.
cmd == CMD_KEY_CONFIRM)
346 uint8_t
dst[NODE_ID_SIZE]{};
361 if (!incoming.
has_intent && std::memcmp(last.
dst, incoming.
dst, NODE_ID_SIZE) != 0)
394 uint32_t last_1w_activity_ms, uint32_t now, uint32_t quiet_ms,
395 uint32_t max_defer_ms) {
396 if (!next_op_is_background || last_1w_activity_ms == 0)
398 if (now - first_1w_activity_ms >= max_defer_ms)
400 return (now - last_1w_activity_ms) < quiet_ms;
417 return auth_poll_failures == 0 && status_poll_failures >= SCHEDULED_POLL_RETRY_GRACE_FIRST_FAILURE &&
418 status_poll_failures <= SCHEDULED_POLL_RETRY_GRACE_LAST_FAILURE;
431 : SCHEDULED_POLL_MAX_TRIES;
448 uint16_t
gap_ms{EXCHANGE_RETRY_DELAY_MS};
475 return LOW_POWER_SHORT_LEAD_WINDOW_MS;
484 return short_tx_ms + EXCHANGE_ATTEMPT_OVERHEAD_MS + LOW_POWER_SHORT_LEAD_WINDOW_MS;
501[[nodiscard]]
inline bool attempt_fits(uint32_t elapsed_ms, uint32_t gap_ms, uint32_t tx_ms, uint32_t window_ms,
502 uint32_t auth_tail_ms, uint32_t budget_ms) {
503 return elapsed_ms + gap_ms + tx_ms + EXCHANGE_ATTEMPT_OVERHEAD_MS + window_ms + auth_tail_ms < budget_ms;
519[[nodiscard]]
inline uint8_t
attempts_that_fit(uint32_t budget_ms, uint32_t attempt_tx_ms, uint32_t early_window_ms,
520 uint32_t last_window_ms, uint8_t cap) {
521 const uint32_t early_ms = attempt_tx_ms + EXCHANGE_ATTEMPT_OVERHEAD_MS + early_window_ms;
522 const uint32_t last_ms = attempt_tx_ms + EXCHANGE_ATTEMPT_OVERHEAD_MS + last_window_ms;
523 if (budget_ms == 0 || early_ms == 0)
525 if (last_ms >= budget_ms)
527 const uint32_t fitting = 1 + ((budget_ms - 1 - last_ms) / early_ms);
528 return static_cast<uint8_t
>(std::max<uint32_t>(1, std::min<uint32_t>(fitting, cap)));
537 plan.
max_attempts = std::max<uint8_t>(1, std::min<uint8_t>(max_tries, EXCHANGE_RETRY_COUNT));
572 uint32_t attempt_tx_ms, uint16_t last_window_ms = 0,
573 std::optional<uint32_t> short_lead_tx_ms = std::nullopt) {
581 plan.
window_ms = LOW_POWER_RESPONSE_WAIT_MS;
583 plan.
window_ms, LOW_POWER_SETTLING_ATTEMPTS);
584 if (short_lead_tx_ms.has_value() && plan.
max_attempts > 1) {
588 const bool all_fit_with_lead =
589 budget_ms == 0 || (budget_ms > lead_cost_ms &&
630 bool settles_a_position) {
657 request.
kind = read_kind;
749 return request.
cmd == CMD_EXECUTE && request.
data_len > EXECUTE_MAIN_BYTE_OFFSET &&
750 request.
data[EXECUTE_MAIN_BYTE_OFFSET] == POS_STOP;
766 bool is_status_read =
false,
bool state_unknown =
false) {
768 if (is_stop || (moving != 0 && (now - moving) < LOW_POWER_MAX_TRAVEL_MS))
771 if (is_status_read && (state_unknown || reported == 0 || (now - reported) >= LOW_POWER_MAX_TRAVEL_MS))
788 uint16_t wake_preamble) {
789 const uint8_t try_1based = std::max<uint8_t>(1, std::min<uint8_t>(try_index, EXCHANGE_RETRY_COUNT));
791 return short_preamble;
792 return wake_preamble;
823 if (std::memcmp(dst, BROADCAST_DISCOVER_ALT, NODE_ID_SIZE) != 0)
825 return cmd == CMD_WRITE_PRIVATE || cmd == CMD_ONEWAY_REMOVE || cmd == CMD_DISCOVER_ALT_REQ;
836 std::array<uint32_t, 3> order{FREQ_CH1, FREQ_CH2, FREQ_CH3};
837 if (receive_freq_hz != FREQ_CH1 && receive_freq_hz != FREQ_CH2 && receive_freq_hz != FREQ_CH3)
839 std::array<uint32_t, 3> result{receive_freq_hz, 0, 0};
841 for (
const uint32_t freq : order) {
842 if (freq != receive_freq_hz)
843 result[next++] = freq;
869 return last_1w_activity_ms == 0 || (now - last_1w_activity_ms) >= quiet_ms;
896 (
static_cast<uint32_t
>(node_id[1]) << BITS_PER_BYTE) |
static_cast<uint32_t
>(node_id[2]);
949 for (
size_t i = 0; i < len && bytes[i] != 0; i++) {
951 name.push_back(printable ?
static_cast<char>(bytes[i]) :
'?');
953 while (!name.empty() && name.back() ==
' ')
981inline bool next_node_id(
const uint8_t node[NODE_ID_SIZE], uint8_t next[NODE_ID_SIZE]) {
982 uint8_t up[NODE_ID_SIZE];
983 memcpy(up, node, NODE_ID_SIZE);
985 for (
size_t i = NODE_ID_SIZE; i-- > 0;) {
987 memcpy(next, up, NODE_ID_SIZE);
1041 return "rain_originator";
1043 return "clamped_command";
1061 "the raw rain threshold must be an exact multiple of one percent");
1070 if (value_raw > STATUS_POS_MAX)
1107 return (status_byte & STATUS_STOPPED) != 0;
bool is_repeatable_execute(const IoFrame &request)
True for a CMD_EXECUTE that has the same effect sent twice as sent once.
static constexpr uint16_t POSITION_RAW_PER_PERCENT
Raw position units per percent (512).
bool stopped_report_at_rest(StoppedReport report)
True when a verdict means the device is at rest.
bool pairing_node_has_heater_at_next_address(DeviceType type, uint8_t manufacturer)
Does a pairing node that answered discovery this way control a heater at the next address?
bool moving_until_holds(const TargetEvidence &evidence, uint32_t now)
True while the remaining time the receiver last reported has not run out: by its own account it is st...
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.
RainSensorState rain_state_from_limitation_reply(const LimitationStatus &status)
Rain state from a decoded limitation reply: the one place that knows which reply fields decide.
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 ...
AttemptPlan attempt_plan(ReadKind kind, bool low_power, const TuningConfig &tuning, uint32_t attempt_tx_ms, uint16_t last_window_ms=0, std::optional< uint32_t > short_lead_tx_ms=std::nullopt)
The attempt plan for a request, from what it is for and who it asks.
static constexpr uint16_t POSITION_FULL_PERCENT
Percent value of a fully limited position, the divisor of the raw position coding.
bool is_repeat_safe_command(const IoFrame &request)
Whether a request may be sent again to a device that said nothing, without a second action.
StoppedReport classify_stopped_report(const StoppedReportInput &in)
Decide whether a "stopped" report means at rest.
bool scheduled_poll_in_retry_band(uint8_t status_poll_failures, uint8_t auth_poll_failures)
True when a scheduler-owned status poll sits in the middle band of the backoff ladder.
bool next_node_id(const uint8_t node[NODE_ID_SIZE], uint8_t next[NODE_ID_SIZE])
The node address one above node, as a 24-bit number (B6BDFB -> B6BDFC).
bool attempt_fits(uint32_t elapsed_ms, uint32_t gap_ms, uint32_t tx_ms, uint32_t window_ms, uint32_t auth_tail_ms, uint32_t budget_ms)
The attempt budget rule: an attempt starts only if the whole attempt, up to the end of the last wait ...
static constexpr uint16_t RAIN_MINIMUM_LIMIT_RAW
RAIN_MINIMUM_LIMIT_PERCENT in the raw position coding (0xB200).
const char * rain_limitation_rule_name(RainLimitationRule rule)
Name of a rain-limitation rule for log lines.
bool stopped_report_keeps_candidate(StoppedReport report)
True when a verdict leaves an off-target report to remember for the next one.
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.
std::string format_heater_model_name(const uint8_t *bytes, size_t len)
Render a heater's model-name register (0x0119, ASCII) for a log line.
bool is_device_event_report(const IoFrame &frame)
Returns true for a frame that reports a device event rather than a status: a CMD_SET_CONFIG1_RESP,...
ExchangeFirstResponseDisposition classify_exchange_first_response(const IoFrame &request, const IoFrame &candidate)
Decide how to handle the first response packet in an authenticated exchange.
AttemptPlan attempt_plan_for_tries(uint8_t max_tries, uint16_t window_ms)
The plan for a caller that names only a number of attempts: that many (at least one,...
constexpr uint8_t MODEL_NAME_PRINTABLE_LAST
See MODEL_NAME_PRINTABLE_FIRST.
ReadKind scheduled_poll_read_kind(uint8_t status_poll_failures, uint8_t auth_poll_failures, bool settles_a_position)
What a cover's status poll is for, from where it sits and what it settles.
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.
uint16_t low_power_try_preamble(WakeBelief belief, uint8_t try_index, uint16_t short_preamble, uint16_t wake_preamble)
Start-preamble length for one try of a directed exchange to a low-power receiver.
bool is_subscription_answer(const IoFrame &request, const IoFrame &response)
Returns true when response is the answer to the subscription request: the request is a CMD_SUBSCRIBE_...
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).
HeaterModelHint
What the model check suggests about a heater's declared io_model:.
@ LOOKS_LIKE_TOWEL_DRYER
Declared as no towel dryer, but the towel-dryer mode register holds a value.
@ LOOKS_LIKE_RADIATOR
Declared a towel dryer, but the towel-dryer mode register holds no value.
@ NONE
The answer fits the declaration: nothing to say.
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_...
HeaterModelHint classify_heater_model_hint(DeviceModel declared, bool towel_mode_has_value)
Compare a heater's declared model with whether it answered the towel-dryer mode register (0x0136) wit...
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.
bool overheard_command_writes(uint8_t cmd)
Returns true for the commands that write to a device: an execute, a mode activation or a private regi...
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.
ReadKind
What a request is for, which decides how its attempts are spent.
@ SETTLING
A read that decides where a device ended up: as many attempts as fit the budget.
@ COMMAND
A request that is harmless to repeat (is_repeat_safe_command()): to a mains device as many attempts a...
@ PROGRESS
A look at a device that is probably still busy (a mid-travel poll): one attempt, the backoff ladder i...
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.
uint8_t attempts_that_fit(uint32_t budget_ms, uint32_t attempt_tx_ms, uint32_t early_window_ms, uint32_t last_window_ms, uint8_t cap)
How many attempts to a silent target fit the budget when none waits a gap, capped at cap.
uint32_t short_lead_cost_ms(uint32_t short_tx_ms)
What the short lead attempt costs the exchange: its transmission, its brief listen and the attempt ov...
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
Whether a low-power receiver is believed to be travelling right now, judged from what this hub has se...
@ ASLEEP
Not known to be moving — lead with the wake-up preamble.
@ UNKNOWN
A status read to a receiver the hub has no recent news of (no moving evidence, and either no status r...
@ AWAKE
Moving, or about to be stopped — the short preamble is the one it hears.
static constexpr uint8_t RAIN_MINIMUM_LIMIT_PERCENT
A window with a wired rain sensor limits its own opening while it rains.
constexpr uint32_t REMOTE_POLL_TIMER_ID_TAG
Namespace tag for remote_poll_timer_id() below: the node address occupies the low 24 bits,...
uint32_t heating_poll_timer_id(const uint8_t node_id[NODE_ID_SIZE])
Numeric set_timeout() id for a heater's regular-cadence poll timer (IOHomeControlComponent::schedule_...
bool is_rain_limited(const RainLimitationInput &in)
True when rain_limitation_rule() finds evidence of a rain limitation.
WakeBelief wake_belief(const TargetEvidence &evidence, uint32_t now, bool is_stop, bool is_status_read=false, bool state_unknown=false)
Judge whether a low-power receiver is travelling.
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.
constexpr uint32_t HEATING_POLL_TIMER_ID_TAG
Namespace tag for heating_poll_timer_id(): the same node-address scheme under its own bit,...
uint8_t scheduled_poll_max_tries(uint8_t status_poll_failures, uint8_t auth_poll_failures)
Transmit-attempt budget for a scheduler-owned status poll of a heater, by ladder position.
RainSensorState rain_state_from_minimum_limit(uint16_t value_raw)
Rain state from the raw value of a minimum-limitation reply.
std::array< uint32_t, 3 > status_update_ack_channel_order(uint32_t receive_freq_hz)
The order in which a 0x72 status-update acknowledgement is sent on the three channels: the channel th...
constexpr uint8_t MODEL_NAME_PRINTABLE_FIRST
First and last byte format_heater_model_name() prints as itself: printable ASCII, space to '~'.
StoppedReport
What the hub makes of one status report's movement state.
@ STOPPED
Stopped on its target (within STATUS_POS_TOLERANCE_RAW), or with no known target or position to compa...
@ SETTLING
Stopped off its target for the first time: treated as moving until the next report confirms it.
@ STOPPED_UNCONFIRMABLE
Stopped off its target and no follow-up poll will run, so the device's own flag stands.
@ MOVING
The device reports itself moving.
@ STOPPED_CONFIRMED
Stopped off its target, as in the remembered report: at rest there.
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 ...
RainLimitationRule rain_limitation_rule(const RainLimitationInput &in)
Decide which rain-limitation evidence, if any, a status reply provides.
bool is_executing(ExecutionState state)
True when a device in this state is moving or about to: waiting for its turn (2), waiting for power (...
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,...
bool frame_issue_is_warning(bool dst_registered, bool dst_is_us)
Whether a frame issue (a frame the hub did not act on) is logged at WARN rather than DEBUG.
bool reads_stopped_by_status_byte(uint8_t status_byte)
Does byte 0 of a status reply say the device is at rest?
RainLimitationRule
Which evidence path labelled a device as limited by rain.
@ RAIN_ORIGINATOR
The device names the rain sensor as the originator of its last command.
@ NONE
No evidence of a rain limitation.
@ CLAMPED_COMMAND
A position command was accepted but the stopped device ended elsewhere, while rain protection was rec...
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.
constexpr uint8_t HEATER_MODEL_CHECK_MAX_RUNS
Runs of the one-off heater model check before the hub gives up on it for this boot.
ExecutionState execution_state_of(uint8_t status_byte)
Read the execution state out of byte 0 of a status reply.
RainSensorState
What the hub's latest minimum-limitation read says about a rain-protection limit.
@ UNKNOWN
No usable reading: never read, or the last reads went unanswered.
@ DRY
The device answered and reports no rain-sized opening limit.
@ RAIN
The device answered and reports an opening limit of rain size.
bool decode_status_update_event(const IoFrame &frame, StatusUpdateEvent &out)
Decode the event header of a CMD_STATUS_UPDATE.
DeviceModel
A product model below a (node type, subtype): for products that report the same wire type but need di...
@ TOWEL_DRYER
Towel dryer under the electrical heater with an adjustable setpoint (52/1).
DeviceType
IO-Homecontrol node types.
@ REMOTE_CONTROLLER
Remote controller (all ten type bits set, packed bytes FF C0).
ExecutionState
What a device says it is doing, from bits [2:0] of byte 0 of a status reply (CMD_PRIVATE_RESP,...
@ WAITING_FOR_POWER
Accepted, waiting for power (queued behind another device on a shared supply).
@ UNDEFINED
Bits [2:0] hold 6 or 7, which no source defines.
@ WAITING_FOR_EXECUTING
Accepted, waiting for its turn to run.
bool has_reached_target_position(float target, float position)
Has the device reached its target within tolerance?
Device-name, address-classification and 1W-frame codecs.
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 moving_until_ms
millis() at which the remaining time of the last trusted moving report has run out (plus PREDICTED_EN...
uint32_t last_status
millis() timestamp of last received status.
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.
One decoded CMD_LIMITATION_STATUS_RESP payload.
uint8_t parameter_id
data[0]; LIMITATION_PARAM_MP for the main parameter.
uint16_t value_raw
data[1..2], big-endian, position coding (STATUS_POS_MAX = 100 %).
The event header of a well-formed CMD_STATUS_UPDATE payload.
uint16_t event_id
EVENT_STATUS_CHANGE for a position update, any other id for a device event report.
The last "stopped" report a device sent away from its own target, kept to confirm the next.
float position
Current position it reported with it.
bool armed() const
True when a report is remembered.
float target
Target the device reported with the off-target "stopped".
All runtime tunable parameters for pairing and radio diagnostics.
uint16_t exchange_early_response_wait_ms
Reply window of every attempt before the last when a mains target is asked more than once (decisions:...
uint16_t exchange_start_response_wait_ms
Reply window for a start frame (wakes a sleeping device).
uint16_t exchange_total_budget_ms
Ceiling on one whole exchange, retries included.
How one exchange spends its attempts. Handed to ExchangeEngine::send_and_receive().
uint16_t window_ms
How long the last attempt listens for the first reply.
bool short_lead
True when the first attempt is a short lead to a low-power receiver in an unknown state (WakeBelief::...
uint16_t early_window_ms
How long every attempt before the last listens; 0 means the same as window_ms.
uint16_t gap_ms
Wait before each attempt after the first; 0 for none.
uint32_t budget_allowance_ms
Time the exchange may run past exchange_total_budget_ms.
bool state_unknown
True when the caller asked for the read to treat the receiver's state as unknown (AttemptRequest::wit...
uint8_t max_attempts
Most transmissions of the request, [1, EXCHANGE_MAX_ATTEMPTS].
uint16_t window_for_attempt(uint8_t attempt, uint8_t attempts) const
bool state_unknown
True for a read whose evidence about the receiver may be stale (with_state_unknown()).
bool wake_preamble_first
True for the read at a moving cover's predicted end of travel (with_wake_preamble_first()).
ReadKind kind
What the read is for; only read when planned.
bool is_progress_look() const
True for a look at a device that is probably still busy: one attempt, the ladder is the retry (ReadKi...
AttemptRequest with_state_unknown() const
Mark a status read as made without trustworthy news of the receiver: activity the hub may have missed...
AttemptRequest(uint8_t tries)
static AttemptRequest of_kind(ReadKind read_kind)
bool planned
True: build the plan for kind instead of using a count.
AttemptRequest with_wake_preamble_first() const
Mark the read as aimed at the moment the receiver has just come to rest: to a low-power target every ...
uint8_t max_tries
Cap on attempts; ignored once kind is set.
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_status_ms
Last status report from the receiver (millis()), 0 = never.
uint32_t last_seen_ms
Last frame received from the receiver, any command.
uint32_t moving_until_ms
When the remaining time of its last trusted moving report has run out (millis()), 0 = unknown (see mo...
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()).
Runtime tuning configuration for pairing and radio diagnostics.