openaliro
Aliro reader: UWB/CCC core and ESP32-S3/C5/C6 port
Loading...
Searching...
No Matches
aliro_rssi_gate.h
1// BLE-RSSI ranging power gate: decides when the phone is close enough that arming
2// UWB ranging is worth the radio's RX power. Pure sample-in/state-out logic (EWMA
3// smoothing, open/close hysteresis with a close hold-off, optional rise-rate fast
4// open for fast approaches) so it host-tests without a radio; the reader feeds it
5// connection RSSI samples and defers Reader-Status-AP-Completed until it opens.
6/*
7 * Copyright (c) 2026 asxeem
8 * SPDX-License-Identifier: ISC
9 */
10#pragma once
11#include <stdbool.h>
12#include <stdint.h>
13
14#ifdef __cplusplus
15extern "C" {
16#endif
17
18/* Tuning knobs. Kconfig supplies the build defaults (ALIRO_RSSI_GATE_CFG_DEFAULT).
19 * The thresholds come from a measured curve on one rig (docs/power-profile.md);
20 * they are per-room and per-handset, so re-run the calibration for a new one. */
21struct aliro_rssi_gate_cfg {
22 int16_t open_dbm; /* smoothed RSSI at/above this -> open */
23 int16_t close_dbm; /* smoothed RSSI at/below this (sustained) -> close */
24 uint16_t close_hold_ms; /* how long below close_dbm before closing */
25 uint8_t ewma_shift; /* smoothing alpha = 1/2^shift */
26 uint8_t slope_open_db; /* fast-open on this much rise per window; 0 = off */
27 uint16_t slope_window_ms; /* rise-rate reference window */
28 uint16_t max_hold_ms; /* longest AP-Completed hold before opening anyway; 0 = off */
29};
30
31/* Kconfig-tunable defaults, with host-build fallbacks (no sdkconfig there). */
32#ifndef CONFIG_WOZ_RSSI_GATE_OPEN_DBM
33#define CONFIG_WOZ_RSSI_GATE_OPEN_DBM (-55)
34#endif
35#ifndef CONFIG_WOZ_RSSI_GATE_CLOSE_DBM
36#define CONFIG_WOZ_RSSI_GATE_CLOSE_DBM (-65)
37#endif
38#ifndef CONFIG_WOZ_RSSI_GATE_CLOSE_HOLD_MS
39#define CONFIG_WOZ_RSSI_GATE_CLOSE_HOLD_MS (3000)
40#endif
41#ifndef CONFIG_WOZ_RSSI_GATE_SLOPE_DB
42#define CONFIG_WOZ_RSSI_GATE_SLOPE_DB (8)
43#endif
44#ifndef CONFIG_WOZ_RSSI_GATE_MAX_HOLD_MS
45#define CONFIG_WOZ_RSSI_GATE_MAX_HOLD_MS (0)
46#endif
47
48#define ALIRO_RSSI_GATE_CFG_DEFAULT \
49 { \
50 .open_dbm = CONFIG_WOZ_RSSI_GATE_OPEN_DBM, \
51 .close_dbm = CONFIG_WOZ_RSSI_GATE_CLOSE_DBM, \
52 .close_hold_ms = CONFIG_WOZ_RSSI_GATE_CLOSE_HOLD_MS, \
53 .ewma_shift = 2u, \
54 .slope_open_db = CONFIG_WOZ_RSSI_GATE_SLOPE_DB, \
55 .slope_window_ms = 1500u, \
56 .max_hold_ms = CONFIG_WOZ_RSSI_GATE_MAX_HOLD_MS, \
57 }
58
59/* HCI "RSSI not available" sentinel (Core spec Read RSSI); such samples are ignored. */
60#define ALIRO_RSSI_UNAVAILABLE 127
61
62/* Gate state. All-zeroes == reset/closed, so a zeroed session slot needs no init
63 * call. Internal values are Q4 fixed point (dBm * 16). */
64struct aliro_rssi_gate {
65 bool primed; /* EWMA seeded by a first sample */
66 bool open;
67 /* AP-Completed hold, started by aliro_rssi_gate_hold_begin. `capped` records
68 * that max_hold_ms opened the gate rather than the level doing it. */
69 bool holding;
70 bool capped;
71 uint32_t hold_since_ms;
72 int32_t ewma_q4;
73 /* close hold-off tracking */
74 bool below;
75 uint32_t below_since_ms;
76 /* rise-rate reference: `old` is between window/2 and ~window old */
77 bool old_valid;
78 bool mid_valid;
79 int32_t old_q4;
80 int32_t mid_q4;
81 uint32_t old_ms;
82 uint32_t mid_ms;
83};
84
85/* Return the gate to its zeroed (closed, unprimed) state. */
86void aliro_rssi_gate_reset(struct aliro_rssi_gate *g);
87
88/* Feed one connection RSSI sample (dBm, ALIRO_RSSI_UNAVAILABLE ignored) stamped
89 * with a monotonic ms clock (wrap-safe). Returns the resulting open state. */
90bool aliro_rssi_gate_feed(struct aliro_rssi_gate *g, const struct aliro_rssi_gate_cfg *cfg,
91 int8_t rssi_dbm, uint32_t now_ms);
92
93/* Current open state without feeding a sample. */
94bool aliro_rssi_gate_is_open(const struct aliro_rssi_gate *g);
95
96/* Start the AP-Completed hold clock. Called when the reader defers AP-Completed on a
97 * closed gate. From here a feed opens the gate once cfg->max_hold_ms has elapsed even
98 * if the level never qualifies. Off by default (max_hold_ms 0): the phone stops
99 * waiting at around 1.9 s and reconnects, and that reconnect costs one fast re-auth
100 * where capping costs seconds of DW3000 RX aimed at a phone still metres away. A
101 * capped open is a normal open in every other respect, so the ordinary close path
102 * still powers the radio back down when the peer leaves.
103 *
104 * Only evaluated on a feed, so a cap ends up to one sample period late: budget
105 * max_hold_ms + the caller's poll interval against the phone's patience. */
106void aliro_rssi_gate_hold_begin(struct aliro_rssi_gate *g, uint32_t now_ms);
107
108/* True when the gate's current open state came from the hold cap rather than from the
109 * level qualifying. Diagnostics only (the power study separates the two). */
110bool aliro_rssi_gate_was_capped(const struct aliro_rssi_gate *g);
111
112/* Smoothed level in whole dBm (truncated), 0 before the first sample. Logging. */
113int16_t aliro_rssi_gate_level_dbm(const struct aliro_rssi_gate *g);
114
115#ifdef __cplusplus
116}
117#endif
static struct @5 g
The bound session's key material, schedule anchor, and dURSK cache.