FieldSurvey Sidekick
FieldSurvey Sidekick is the Raspberry Pi companion daemon for the native iOS FieldSurvey app. The iPhone remains responsible for ARKit/LiDAR pose tracking, visualization, offline bundles, and ServiceRadar uploads. The Pi supplies high-fidelity Wi-Fi observations from Linux monitor-mode USB radios.
The RF data plane uses one WebSocket per radio with binary Apache Arrow IPC record batches. SDR spectrum sweeps use a separate binary Arrow IPC WebSocket so Wi-Fi BSSID observations and interference/channel-energy observations remain distinct. Control and status endpoints remain JSON.
Hardware Requirements
The Sidekick daemon runs on a Raspberry Pi (64-bit ARM) running a current 64-bit Debian release. You will need:
-
A management interface — the Pi's built-in Wi-Fi (
wlan0) or wired Ethernet, used for connectivity to the iPhone and ServiceRadar. Do not use the management interface for capture. -
One or more monitor-mode USB Wi-Fi adapters — used for survey capture. Each adapter must expose Linux monitor mode and radiotap metadata through
iw. Verified chipsets:- Ralink RT5572 (
rt2800usbdriver) - MediaTek MT7612U (
mt76x2udriver)
Prefer a USB3 adapter for the 5 GHz survey plan.
- Ralink RT5572 (
-
(Optional) A HackRF One SDR — used for channel-energy/interference spectrum sweeps.
Treat the HackRF as receive-only unless the hardware modification history is
known. The Sidekick spectrum path starts hackrf_sweep with the RX amp disabled
and antenna power disabled.
Connectivity
Treat the iPhone-to-Pi connection as IP transport. USB networking is the
preferred field transport when it is available; LAN reachability works for lab
and bench setups. The daemon listens on port 17321 by default and serves both
HTTP control endpoints and WebSocket observation streams:
http://<PI_HOST>:17321— control and statusws://<PI_HOST>:17321— RF and spectrum streams
Replace <PI_HOST> with the Pi's address on whichever transport you use.
Installation
The repository includes a systemd install path under
build/packaging/fieldsurvey-sidekick/.
Build the daemon for the Pi (64-bit ARM). Install the Linux ARM64 Rust target once:
rustup target add aarch64-unknown-linux-gnu
The repo-local Cargo config uses aarch64-linux-gnu-gcc for that target. Build
the daemon:
cargo build -p serviceradar-fieldsurvey-sidekick --target aarch64-unknown-linux-gnu
Copy the binary and packaging assets to the Pi, then install the systemd service
(replace <pi-user> and <PI_HOST>):
scp target/aarch64-unknown-linux-gnu/debug/serviceradar-fieldsurvey-sidekick \
<pi-user>@<PI_HOST>:/tmp/serviceradar-fieldsurvey-sidekick.new
scp build/packaging/fieldsurvey-sidekick/config/fieldsurvey-sidekick.toml \
build/packaging/fieldsurvey-sidekick/config/fieldsurvey-sidekick.env.example \
build/packaging/fieldsurvey-sidekick/systemd/serviceradar-fieldsurvey-sidekick.service \
<pi-user>@<PI_HOST>:/tmp/
ssh <pi-user>@<PI_HOST> '
sudo install -m 0755 /tmp/serviceradar-fieldsurvey-sidekick.new \
/usr/local/bin/serviceradar-fieldsurvey-sidekick
sudo install -d -m 0755 /etc/serviceradar
sudo install -m 0644 /tmp/fieldsurvey-sidekick.toml \
/etc/serviceradar/fieldsurvey-sidekick.toml
sudo install -m 0600 /tmp/fieldsurvey-sidekick.env.example \
/etc/serviceradar/fieldsurvey-sidekick.env
sudo install -m 0644 /tmp/serviceradar-fieldsurvey-sidekick.service \
/etc/systemd/system/serviceradar-fieldsurvey-sidekick.service
sudo systemctl daemon-reload
sudo systemctl enable --now serviceradar-fieldsurvey-sidekick.service
'
Before field use, set a strong SERVICERADAR_SIDEKICK_API_TOKEN in
/etc/serviceradar/fieldsurvey-sidekick.env, replacing the change-me
placeholder.
Runtime paths:
- Binary:
/usr/local/bin/serviceradar-fieldsurvey-sidekick - Config:
/etc/serviceradar/fieldsurvey-sidekick.toml - Environment/token file:
/etc/serviceradar/fieldsurvey-sidekick.env - Persisted runtime config:
/var/lib/serviceradar/fieldsurvey-sidekick/runtime-config.json - Logs:
/var/log/serviceradar/fieldsurvey-sidekick.logand/var/log/serviceradar/fieldsurvey-sidekick-error.log
Check service status:
ssh <pi-user>@<PI_HOST> \
'sudo systemctl status serviceradar-fieldsurvey-sidekick.service --no-pager'
curl -s http://<PI_HOST>:17321/healthz
curl -s http://<PI_HOST>:17321/status
Pairing Workflow
SERVICERADAR_SIDEKICK_API_TOKEN is the setup/admin token. Use it to claim a
paired device token from the iOS app or with curl:
curl -s -X POST http://<PI_HOST>:17321/pairing/claim \
-H "authorization: Bearer <admin-token>" \
-H "content-type: application/json" \
-d '{"device_id":"iphone-field-unit-1","device_name":"FieldSurvey iPhone"}'
The response includes a one-time visible token. Store that token in the
FieldSurvey Sidekick settings and use it for normal config, control, RF stream,
and spectrum stream requests. The daemon stores only the token hash in
runtime-config.json; /config returns paired device metadata without token
hashes.
The setup token still works as an admin credential. Rotate it in
/etc/serviceradar/fieldsurvey-sidekick.env after provisioning if the field
unit should rely only on paired-device tokens.
Verifying the Daemon
Mutating endpoints and observation streams require a bearer token — the admin token or a paired-device token.
Check radio inventory and stream state:
curl -s http://<PI_HOST>:17321/status
/status includes capture_running and active_streams, so the app can see
whether RF or spectrum WebSockets are currently registered.
Read the persisted runtime config:
curl -s http://<PI_HOST>:17321/config \
-H "authorization: Bearer <token>"
Update non-secret runtime config values:
curl -s -X PUT http://<PI_HOST>:17321/config \
-H "authorization: Bearer <token>" \
-H "content-type: application/json" \
-d '{"sidekick_id":"fieldsurvey-sidekick","radio_plans":[{"interface_name":"wlan2","frequencies_mhz":[5180,5200,5220],"hop_interval_ms":250}]}'
Plan a Pi Wi-Fi uplink configuration without changing the Pi. Responses redact the passphrase:
curl -s -X POST http://<PI_HOST>:17321/wifi/uplink-plan \
-H "authorization: Bearer <token>" \
-H "content-type: application/json" \
-d '{"interface_name":"wlan0","ssid":"<ssid>","psk":"<wifi-password>","country_code":"US","dry_run":true}'
Apply the uplink with dry_run:false only when the iPhone-to-Pi management link
does not depend on the target interface. The daemon uses iw reg set and
nmcli device wifi connect, then persists only the SSID/interface/country and a
psk_configured flag.
Stop active RF and spectrum streams through the authenticated control plane:
curl -s -X POST http://<PI_HOST>:17321/capture/stop \
-H "authorization: Bearer <token>"
Monitor-Mode Capture
Generate the monitor-mode command plan for a radio (here wlan2 on 5180 MHz):
curl -s -X POST http://<PI_HOST>:17321/radios/monitor-plan \
-H "content-type: application/json" \
-d '{"interface_name":"wlan2","frequency_mhz":5180}'
Dry-run the authenticated monitor preparation endpoint:
curl -s -X POST http://<PI_HOST>:17321/radios/prepare-monitor \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{"interface_name":"wlan2","frequency_mhz":5180,"dry_run":true}'
Set dry_run:false to prepare the interface for real monitor-mode capture. Keep
the management interface out of the capture set so the Pi stays reachable, and
prepare each USB radio you intend to capture with.
Streaming Observations
Open the observation WebSocket — one per radio. The iOS app uses the same
endpoint with an Authorization: Bearer <token> header. Frames with opcode 2
are binary Arrow IPC streams containing RF observation record batches.
ws://<PI_HOST>:17321/observations/stream?interface_name=wlan2&sidekick_id=sidekick-1&radio_id=wlan2
To let the daemon hop channels for a stream, add frequencies_mhz and
hop_interval_ms. The hopper is tied to that WebSocket and stops when the
stream closes.
ws://<PI_HOST>:17321/observations/stream?interface_name=wlan2&sidekick_id=sidekick-1&radio_id=wlan2&frequencies_mhz=5180,5200,5220,5240&hop_interval_ms=250
A quiet channel can correctly produce no batches until a beacon or probe-response appears.
In iOS auto mode, FieldSurvey ignores the management interface, chooses
monitor-capable USB radios, sorts them by USB link speed, assigns the fastest
radio to the 5 GHz survey plan, and assigns the next radio to the 2.4 GHz
non-overlapping channel plan. Explicit settings can override this, for example
wlan1:2412|2437|2462,wlan2:5180|5200|5220|5240.
Open the HackRF spectrum WebSocket for channel-energy/interference data. Frames
with opcode 2 are binary Arrow IPC streams containing spectrum sweep record
batches with power_bins_dbm list columns.
ws://<PI_HOST>:17321/spectrum/stream?sidekick_id=sidekick-1&sdr_id=hackrf-0&serial_number=<hackrf-serial>&frequency_min_mhz=2400&frequency_max_mhz=2500&bin_width_hz=1000000&lna_gain_db=8&vga_gain_db=8&sweep_count=1024
For unknown or modified HackRF hardware, keep lna_gain_db and vga_gain_db
conservative, keep amp off, and do not use transmit tooling. The Sidekick
passes an explicit sweep_count to hackrf_sweep because some Pi/HackRF
toolchains complete zero sweeps when no count is provided.
Adapter and Monitor-Mode Notes
Supported survey adapters must expose Linux monitor mode and radiotap metadata
through iw. The verified adapters are:
- Ralink RT5572 with
rt2800usb - MediaTek MT7612U with
mt76x2u
Basic adapter checks:
iw dev
iw phy | sed -n "/Supported interface modes:/,/Band /p"
lsusb -t
The service runs as root with CAP_NET_ADMIN and CAP_NET_RAW because it
configures Wi-Fi interfaces, opens AF_PACKET sockets, and may configure
NetworkManager Wi-Fi uplink state.
No udev rule is required for the current root-owned service, but production units should add stable interface naming or serial-based inventory rules if USB enumeration order changes between boots.
If monitor setup fails:
- Confirm the interface is not the management uplink.
- Confirm NetworkManager is not reconnecting the capture interface.
- Confirm the requested frequency is valid for the adapter and regulatory country.
- Check
journalctl -u serviceradar-fieldsurvey-sidekick.serviceand/var/log/serviceradar/fieldsurvey-sidekick-error.log.
Kismet is useful as a comparison tool but is not required for the product path. Use it only to compare adapter behavior, visible BSSIDs, or channel activity when validating a new dongle.