Skip to content

Theseus RSSI recorder ​

Added: 11 October 2026

The RSSI recorder is a tool on the start screen. A tester picks one or more watches, starts a recording, and tells Theseus which zone each watch is in while walking the site. The site's server records every RSSI report its gateways deliver for those watches, together with the tester's labels.

The phone is the remote control and the labelling device. It does not scan Bluetooth and it does not store the recording.

A finished recording can be:

  • replayed offline through any tracking algorithm (tools/rtls-sim), and
  • installed as the site's fingerprint survey (used by fingerprint_nb).

Where the recording lives ​

On the site's server, as one file per recording. Consequences:

  • Recording continues when the phone sleeps, loses Wi-Fi, or Theseus is closed. Only one recording runs at a time per server.
  • Any signed-in Theseus phone can join the running recording, label and stop it.
  • The server stops a recording by itself after 2 hours, or when the file reaches its size cap.
  • Nothing is written to the phone. Labels that could not be sent yet are held in memory by the live screen only.
  • While the live screen is on screen the phone's screen stays on: a tester stands in a zone for a minute without touching the phone and must be able to tap Moving the moment they leave. Leaving the screen, or the app, gives the normal screen timeout back.

Before you start ​

  • Sign in to the site: scan the site QR. The card is disabled until the session is ok.
  • Wear each watch you record, or place it where the test happens. The server records what the gateways hear from the watch, not what the phone hears.
  • Tracking does not have to run. When it is stopped, the recording still captures readings; the screen then has nothing to compare your labels with.

Walk-through ​

  1. On the start screen, tap RSSI recorder.
  2. If a recording is already running on the site, the screen says Recording in progress and names the watches it records (Watch 1 (SN-W-0001)). Tap Join recording and continue at step 6. Otherwise tap New recording.
  3. Pick watches. The list shows what the server can record, watches first: name, asset uid, type, Heard 2 s ago or Not heard, and the zone tracking currently predicts. Search by name, asset uid or MAC. Select 1 to 10 (2 selected (max 10)).
  4. Optionally tap Add a note in the bar at the bottom and type a note (at most 200 characters), for example the ward.
  5. Tap Start recording. If someone started a recording in the meantime, Theseus says so, names its watches and offers to join it.
  6. The live screen opens. The app bar and the heading above the zones say which watch is recorded (Where is Watch 1 now? SN-W-0001). Walk into a zone and tap its button. The button is highlighted and counts how long the label has held: for 00:00.
  7. When you leave the zone, tap Moving / between zones. Tap the next zone when you are inside it. A label holds until the next label for the same watch.
  8. Watch the top of the screen: Match or Mismatch says whether tracking agrees with your label. The list below shows which gateways hear the watch.
  9. When you are done, tap Stop and confirm. The summary opens.
  10. To use the recording as the fingerprint survey, tap Use as fingerprint survey and confirm. Then restart tracking on the site.

You can leave the live screen at any time (back). The recording continues on the server; join it again from the recorder's first screen.

The live screen ​

PartWhat it shows
App barRecording and, under it, the watch: Watch 1 · SN-W-0001, or 3 watches
HeaderTime recorded so far, RSSI reports recorded, time until the server stops the recording by itself, and the connection: Live, Connecting, Offline, Site error
Watch bar (several watches only)One chip per watch with its current label. The highlighted chip is the watch you carry. Tap another watch to give it a fixed zone, or to say you carry that one now
VerdictYour label · for 00:42 and the zone on its own line (never cut to one line); what tracking predicts and with which algorithm; and the verdict block (below)
GatewaysThe gateways that hear the watch right now, strongest first: signal bars, tag, the zone the gateway is in, RSSI, reports in the last 10 s (9 in 10 s), age of the latest report. Tap to show all (at most 12)
ZonesThe heading with the watch, then (sites with several maps) the map selector, then one large button per zone of that map
Moving / between zonesIts own button, fixed at the bottom of the screen at every screen size and text size. On a short screen or with very large text the header and the verdict scroll with the zones instead
StopTop right. Asks first, and names the watch(es)

Durations are written for 00:42. since is only used for times of day (since 14:15 on the start screen card).

Map selector ​

With several maps one full-width control says which map's zones are shown (Map 1 of 3 · Ward 4C). A tap opens the list of maps with the number of zones of each. The control is pinned above the zone buttons: it stays on screen however far the zones are scrolled.

Zone names ​

A zone name is never cut. A name gets up to three lines on its button; if a name of the map still does not fit, the grid uses fewer columns (three, two, one). Two zones that differ only at the end (… Bed 12, … Bed 13) can therefore always be told apart. A scrollbar shows when zones continue below Moving.

Verdict block ​

BlockMeaning
MatchTracking predicts the zone you labelled
MismatchTracking predicts another zone
UnknownYou labelled a zone, tracking runs, but it does not place the watch in any zone
MovingYour label is "moving": nothing is compared
No labelYou have not labelled this watch yet. The panel asks for it (Tap the zone the watch is in), in the attention colour: a recording without labels has no ground truth
No trackingTracking is stopped on the site
OfflineThe connection is down: the last prediction is too old to judge
Site errorThe site answers, but with an error (HTTP 500, an unreadable answer). Not a Wi-Fi problem: the panel shows the site's message

Match and mismatch differ by colour, icon and word.

Zone buttons ​

LookMeaning
Highlighted, for mm:ssThe watch's current label and how long it has held
Small badge in the corner, tintedNo gateway inside this zone. These are the zones a survey is most needed for
Clock-and-arrow icon instead of the check markThe label is not on the server yet

Tapping a zone sends the label at once and the phone vibrates briefly. Tapping the zone that is already the label does nothing.

Gateways ​

  • No gateway hears this watch in red: check that the watch is switched on and that you picked the right one.
  • A gateway whose zone name is highlighted is inside the zone you labelled (screen readers hear "in your labelled zone").
  • A zone name that does not fit beside the numbers moves to a second line instead of being cut.
  • On a short screen the list is one line (6 gateways · best GW-14 −58 dBm); tap it to open the list.

When the connection drops ​

The screen polls the server about every 2 seconds while you look at it. It stops polling when the app is in the background or another page covers the screen. None of this affects the recording.

  • The verdict area says Connection lost — the recording continues on the server. The header says Offline. The gateway list is dimmed and says how old it is.
  • Keep labelling. Every tap is highlighted at once, kept in order with the time of the tap, and sent when the connection returns. The server stamps each label with the moment you tapped, not the moment it arrived. The screen shows how many labels are waiting.
  • A label is never given up on while the recording runs. The server accepts a label at most 10 minutes late (age_ms up to 600 s). A label that waited longer is still sent, as 10 minutes old: the server then holds the right zone from 10 minutes ago on, and the screen says that the time before that keeps the previous label. (Dropping it would leave the previous zone on the server for the rest of the recording.)
  • A label the server refuses (for example a zone that was deleted) is dropped and the screen says which one and why.
  • If the recording stopped while labels were waiting, the screen says how many labels were not recorded.
  • If the site reports another label for a watch than the one this phone last delivered, the screen says so (The site now says Room 12 for Watch 1, not Room 13 as you last tapped…): another phone labelled the watch, or a delayed label arrived. Tap the right zone again if it is wrong.
  • Leaving the live screen with labels still waiting asks first: they are only in this screen's memory.
  • Stop first gives labels that are still on their way up to 5 seconds to reach the server (Sending…), then stops. What could not be sent is named on the summary. If the stop itself fails, the screen says so and the recording continues.

When the site answers with an error (HTTP 500, an answer Theseus cannot read), the screen says Site error and shows the site's message instead of "Connection lost": the tester should not go looking for Wi-Fi.

If the site answers 401, the field token was revoked: the screen says Session no longer valid and sends you back to the start screen, which then shows the session as revoked. Scan a new site QR (the old QR carries the revoked token).

Why a label cannot arrive late ​

A label means "this watch is in this zone from now on" and is stamped when it arrives (minus age_ms). A request Theseus gave up on must therefore never reach the server later, after newer labels. The recorder's HTTP transport (rtls_transport.dart) aborts a request when its time limit passes (10 s; 5 s for the status poll; 60 s for installing a survey): the connection is destroyed and nothing more is sent on it. The connection timeout (8 s) is shorter than the request limit. All recorder requests share one connection.

The contract has no sequence number on labels, so the server cannot reject a late or duplicate label itself; the notice above is the safety net.

The phone clock ​

age_ms is the difference of two readings of the phone clock. When the server's clock (from the status answers around the tap and at delivery) says something more than 10 s different, the phone clock was changed while the label waited (a time sync when the network returned), and the server's view is used.

Summary ​

For a stopped recording: start and stop time, duration, samples, labels, file size, why it stopped (Stopped by a tester, time limit, file size limit, server restart), the note, and the samples per watch (with a bar per watch when there are several). A watch with 0 samples was never heard. Labels the live screen could not deliver before the stop are named at the top until dismissed.

Use as fingerprint survey asks first. It replaces the site's fingerprint survey with the labelled data of this recording, and the new survey takes effect when tracking is restarted. The answer shows:

  • how many zones the survey covers out of all zones,
  • how many samples it holds and whether it replaced an earlier survey,
  • whether tracking must be restarted,
  • the server's warnings (for example a zone with very little data).

The server refuses a recording without any labelled zone time, and a recording that is still running.

Installing reads the whole recording on the server, so this request may take up to 60 seconds. When no answer arrives, Theseus does not say "not installed": the survey may have been installed, and the message says to check on the site before installing again.

Card on the start screen ​

Card statusWhen
ReadySigned in, no recording known to run
Recording…, badge REC, 2 watches · since 14:15The last thing the site said is that a recording is running, and its auto_stop_ms has not passed

The start screen asks the site once each time the session becomes ok (one GET /theseus/rtls/recordings). It does not poll. Inside the tool every screen updates what the card shows. The card drops Recording… by itself at the time the server said it would stop the recording (auto_stop_ms). A recording that stopped earlier for another reason (size limit, another phone) shows as running until the tool is opened or the session is checked again. The recorder's first screen loads its list again when the app comes back from the background.

HTTP contract ​

The app follows Theseus RTLS recorder: HTTP contract (v1). The server side publishes it with the field APIs. In short:

MethodPath under /api/v1Used for
GET/theseus/rtls/layoutMaps, zones, gateways per zone
GET/theseus/rtls/watchesAssets that can be recorded
POST/theseus/rtls/recordingsStart (409 carries the running recording)
GET/theseus/rtls/recordingsList, newest first
GET/theseus/rtls/recordings/:idStatus: the live screen's poll
POST/theseus/rtls/recordings/:id/labelsLabel, with age_ms
POST/theseus/rtls/recordings/:id/stopStop
POST/theseus/rtls/recordings/:id/surveyInstall as fingerprint survey

All with Authorization: Bearer th_…, like the other field routes. The download route (…/download) is not used by the app.

Times on screen never use the phone clock as an absolute time: every answer carries server_time_ms, and the phone only measures how much time passed since the last answer or since a tap.

Where the contract does not say what "empty" looks like, the app accepts every form for values it only displays: a missing or null name, tag, note, mac, type or algorithm reads as empty (a gateway without a tag is shown by its MAC), a missing rssi_avg as rssi, missing bytes and zone geometry as 0, empty warnings are skipped. Ids, states, times, counts, zone ids and RSSI stay strict: a wrong one is an error that names the field.

A POST the server answers with HTTP 200 and type: success was done, even when the rest of the answer cannot be read: the label is not sent again, a started recording is joined, a stopped one is shown as stopped.

The contract's examples are in theseus/test/fixtures/rtls/*.json as complete HTTP bodies. The app's client tests read them; the server side can validate the same files.

Code ​

theseus/lib/tools/rssi_recorder/:

FileContent
rtls_models.dartThe contract's objects. What the app acts on is parsed strictly (a wrong field is an error state that names the field, never a crash); what it only shows may be empty
rtls_client.dartOne method per route. Returns a value or a typed error (401, 400, 404, 409, server, network, timeout, malformed)
rtls_transport.dartSends the requests on one shared connection and aborts a request at its time limit
recorder_service.dartApp-level, the tool's own state (TheseusTool.createState): the shared client and the last known recording (for the card)
live_controller.dartThe live screen's logic: status poll, label queue, verdict
recorder_home_screen.dartJoin / new recording, previous recordings
watch_picker_screen.dartPick watches, note, start
live_screen.dartThe live screen: visibility, polling on and off, stop, leave
live_widgets.dartWhat the server says: header, verdict, gateways, notices
live_zone_widgets.dartWhat the tester taps: zone buttons, Moving, the sheet of another watch
recorder_widgets.dart, recorder_format.dartShared pieces: error and 401 views, signal bars, clocks and counts as text
summary_screen.dartSummary and survey

The registry entry is theseus/lib/tools/rssi_recorder_tool.dart. Keeping the screen on is theseus/lib/platform/keep_awake.dart with a method channel (theseus/keep_awake) in MainActivity.kt (FLAG_KEEP_SCREEN_ON) and AppDelegate.swift (isIdleTimerDisabled); no pub dependency.

Tests and screenshots ​

  • test/rtls_client_test.dart: every route against the fixtures, and every error path.
  • test/rtls_transport_test.dart: a timed-out request is aborted (also on a real loopback socket) and requests share one connection.
  • test/rssi_recorder_live_controller_test.dart: polling, the label queue (order, age_ms, labels older than 600 s, a label that arrives late, clock changes), verdicts, stop.
  • test/rssi_recorder_live_test.dart: the live screen, including that Moving is on screen at every size and text scale, that no zone name is cut, and that the screen is kept on.
  • test/rssi_recorder_test.dart: first screen, watch picker, summary, survey, card.
  • test/support/fake_rtls_server.dart: the recorder routes in memory.

Screenshots (see start screen for the command): recorder_home_*, recorder_watch_picker_*, recorder_note_dialog_light, recorder_live_*, recorder_map_sheet_light, recorder_summary_*, recorder_survey_confirm_light, recorder_stop_confirm_light, and start_recording_running_light.

Non-goals ​

Scanning Bluetooth on the phone. Storing or sharing the recording file from the phone. Recording while signed out. Background polling or notifications from the recorder. Starting or stopping tracking from the phone. More than one recording per server at a time.