Appearance
Tracking System
The tracking system predicts which zone a beacon is in from the RSSI of its adv4 advertisements at the gateways. It lives in server/src/tracking.ts; the algorithms are in server/src/algs/.
What is still open after the October 2026 algorithm work, and how to test it on site, is in Tracking: next steps.
Overview
When tracking is active, the system:
- Receives adv4 RSSI data from MQTT (beacon signal strength at each gateway)
- Maintains in-memory caches of zones, gateways, and maps
- Runs the selected algorithm to decide which zone each beacon is in
- Publishes updates to WebSocket subscribers
- Logs tracking intervals to protobuf files
Button-press adv4 UUIDs (single/double/triple click and long press) are excluded from the RSSI input so trigger TX power can differ from idle tracking TX without mixing scales. Idle and motion advertisements feed RTLS. SOS/watch notifications use the last predicted zone.
Algorithms
zone_voting is the default: the server starts it at boot and the admin page preselects it. Its file, tracking_alg_zone_voting.ts, is unchanged. The other algorithms were added in October 2026 and are selectable for testing. Brutus and trilateration were removed; an unknown name is rejected and leaves tracking stopped.
| Name | What it does | Needs |
|---|---|---|
zone_voting | Each gateway votes for its zone with the beacon's signal strength; the best zone wins every 2 s. | Nothing |
zone_voting_max | Today's zone voting with one repair: a zone scores its best gateway instead of the sum of its gateways (the sum penalises a zone that has several gateways). | Nothing |
zone_voting_v2 | Repaired scoring, a signal window that persists across cycles, and a settled zone that is protected by a margin, so a still watch stops jumping. | Nothing |
zone_voting_v2_fast | zone_voting_v2 with a flat 2 dB margin: follows a move sooner, changes zone more often at rest. | Nothing |
zone_voting_v3 | zone_voting_v2 with an approach test and a decision every second: the quickest of the stable ones. | Nothing |
geo_bayes | Probability filter over all zones, including zones without a gateway, from the positions on the map. | Gateway icons placed correctly |
fingerprint_nb | Recognises each zone by an RSSI signature learnt from a survey walk. Without a usable survey it answers like zone_voting. | rtls/fingerprint_survey.json |
ref_beacons | Stabilised gateway proximity, plus fixed reference beacons that mark rooms without a gateway. Without the file it runs on gateway proximity alone. | rtls/reference_beacons.json |
centroid_snap | Estimates a position between the gateways that hear the beacon, then picks the zone; decides every second. | Gateway icons placed correctly |
zone_voting_motion_gate | Today's zone voting, with zone changes held back while the watch reports no motion. Without the watch trigger it answers like zone_voting. | Watch motion trigger |
Choosing one for a test
All figures come from the simulator in tools/rtls-sim, not from a real site. No RSSI recording of a worn watch existed when they were produced, so treat them as a ranking to confirm on site.
Zone jumping at rest.
zone_voting_v2had the fewest zone changes for a still watch in 38 of 40 simulated radio models. It makes the zone calm; at the boundary of two open-area zones it is not more often right than today.Following a real move. Pooled over eight held-out conditions:
zone_voting_v2zone_voting_v2_fastzone_voting_v3loudest gateway, no stabilising Median delay until the new zone is shown 6.6 s 6.0 s 4.6 s 4.6 s Entries into a zone not shown within 60 s 17.8 % 15.1 % 15.7 % 12.1 % Zone changes per hour while still 11.8 22.5 14.5 173 The gateway upload setting limits every algorithm: with one report per 5 s, the median delay is 8 to 16 s whatever is selected.
Zones without a gateway. Only
geo_bayes,fingerprint_nbandref_beaconscan report them, and in the simulator only when neighbouring gateways hear the watch reliably.geo_bayesloses accuracy in zones that do have a gateway.Known failures.
centroid_snapbreaks when a zone rectangle encloses other zones ("whole floor") and when gateways upload less often than every 7 s.fingerprint_nbwith a partial survey shows a watch in the nearest surveyed zone.
Details: tools/rtls-sim/results/EVALUATION.md (comparison), tools/rtls-sim/results/SNAPPINESS.md (delay study), tools/rtls-sim/reports/candidates/ (one report per algorithm).
Optional site inputs
Read from <server root>/rtls/ each time a new algorithm starts. A missing file is normal. An invalid file is reported once in the server log and ignored; it never stops tracking from starting.
fingerprint_survey.json, for fingerprint_nb:
json
{ "version": 1, "samples": [ { "zone_id": 12, "t": 1760000000000, "gmac": "AC233FC0A1B2", "rssi": -71 } ] }One entry per RSSI report of the survey watch: the zone the tester was in, the server arrival time in ms, the gateway MAC exactly as in the gateways table, and the RSSI in dBm. There is no tool yet that produces this file; see the next-steps page.
reference_beacons.json, for ref_beacons:
json
{ "version": 1, "beacons": [ { "mac": "BC:57:29:00:11:22", "zone_id": 12 } ] }One entry per fixed beacon and the zone it is mounted in. The beacon must be a registered, active asset so its advertisements reach tracking. While ref_beacons runs, listed beacons are used as references and are not tracked themselves.
Watch motion trigger, for zone_voting_motion_gate: the server reads motion from the advertisement UUID (last digit 2). The watch profile does not enable that trigger today, so the gate stays off. Bench-test the trigger on one watch first (tools/rtls-sim/reports/field_plan.md, section 6). On a bridge (Zabbix) site, server/src/bridge.ts must first stop treating the motion UUID as a button press.
How the new algorithms run
Each new algorithm is a pure module (tracking_alg_<name>.ts): a function of the map layout and timestamped RSSI samples, with no timers, clock or I/O. tracking_alg_catalog.ts lists the configured instances that are registered, and tracking_alg_runner.ts drives the selected one: it feeds samples, steps every beacon each cycle and performs the same side effects as zone_voting (prediction cache, interval log, WebSocket pushes, diagnostics).
- A beacon that delivers no sample for 60 s is dropped, as with
zone_voting. Its algorithm state is dropped with it:geo_bayes,ref_beaconsand the motion gate start fresh when the beacon is heard again. The simulator figures were produced without this. - The new algorithms own their stabilising; their answers do not pass through the zone-graph smoothing that
zone_votinguses. - The simulator runs the same modules (
tools/rtls-sim/algs/prod_<name>.ts), so what is evaluated there is what the server registers.
To add an algorithm: write the pure module, add an entry to the catalog, add tools/rtls-sim/algs/prod_<name>.ts, and add tests (server/tests/tracking_alg_<name>.test.ts; server/tests/tracking_registry.test.ts covers the registry).
Control API
All routes are GET and admin-guarded.
| Endpoint | Description |
|---|---|
/api/v1/tracking/start/:alg | Start tracking with a named algorithm. An unknown name fails and tracking stays stopped. Does nothing while tracking is already running. |
/api/v1/tracking/stop | Stop tracking |
/api/v1/tracking/status | Whether tracking is running |
/api/v1/tracking/algorithms | List available algorithms (zone_voting first) |
/api/v1/tracking/algorithms/current | The running algorithm, or none |
/api/v1/tracking/:id | Tracking data for a beacon (:id = asset_uid) |
In the admin UI, Zones → Overview has the algorithm selector. "Choose Algorithm" stops tracking and starts the selected one; predictions are empty for the first seconds after a switch, so do not switch while staff rely on alerts.
The selection is not stored: after a server restart tracking runs zone_voting again.
WebSocket Updates
The tracking system pushes updates to three types of WebSocket subscribers:
track-{asset_uid}: Position update for a specific assettrack-debug-{asset_uid}: Debug data of the running algorithm for that asset. The new algorithms send one message per cycle with their reasoning (top zones, margins, per-gateway levels, why a change was or was not made); the Zones → Debug tab prints it.overview-{map_id}: All beacon positions on a specific map
Subscribe messages send data.mac as asset_uid (field name kept for schema stability).
Tracking Logs
Tracking intervals (beacon enters/exits a zone) are logged to protobuf files under the logs/ directory. These can be queried via the /api/v1/beacon/:id/intervals endpoint and, for a time window with zone/map geometry, /api/v1/tracking/:id/history?from=&to= (:id = asset_uid, max 7 days). The history response also carries presses[]: button presses from beacon_events in the window, with the zone recorded at press time (capped at 1000, presses_truncated when cut).
In-Memory Caches
The tracking module maintains caches loaded from the database:
| Cache | Contents | Purpose |
|---|---|---|
| Zones | All zones with coordinates | Zone lookup for predictions |
| Gateways | Gateways with map/zone assignments | Map RSSI to gateways |
| Maps | Map metadata | Zone-to-map mapping |
| RSSI | Recent RSSI readings per beacon | Algorithm input data |
The caches are loaded when tracking starts. After editing maps, zones or gateways, stop and start tracking for the change to take effect.