Bridges an FTC Scorekeeper event stream to the hackrf-logger capture API and serves the capture review dashboard. It listens for match-transition WebSocket events and, on each match start, marks a channel-focused capture window on the logger spanning a little before the start, through the match, and a little after the end — so the RF around every match is densely recorded. It then provides the UI to list, display, and review those windows.
All FTC-specific logic lives here; the logger itself is domain-agnostic. This is a standalone client: it talks to the logger only over HTTP and shares no code with the capture system. It needs no HackRF and no cgo, so it can run anywhere — including on the scorekeeper host itself.
This repository was split out of
hackrf-logger. The logger is the RF capture system; this is the FTC-aware layer on top of its generic capture API. The two are deployed and versioned independently.
- Go 1.26+ (pure Go, no cgo, no hardware)
- A reachable hackrf-logger instance (its
/api/v1capture API) - A reachable FTC Scorekeeper (its REST API +
/api/v2/stream/WebSocket)
make build # -> ./hackrf-ftc-logger and ./mock-ftc-stream
hackrf-ftc-logger -ftc-host 10.0.0.5 -event USCMP -logger http://localhost:8080make all # gofmt check + vet + test + build
make test # unit tests
make build-app # just the app
make build-mock # just the dev mock streamWith -addr set (default :8090) it serves a web dashboard at http://localhost:8090:
- Capture list — every marked window, with match name, status, channels, and duration.
- Review a selected window: a waterfall and an avg+peak spectrum restricted to the match teams' 5 GHz channels (toggle full band to see everything), a teams table (alliance / station / team / channel), and a per-channel activity summary (avg/peak dB with the teams on each channel).
The dashboard reaches the logger through this app (read-only proxy that injects the logger's bearer token), so the browser only ever talks to it.
- On startup it fetches the event's match schedule and 5 GHz Wi-Fi channel assignments
from the scorekeeper REST API:
GET /api/v1/events/<event>/matches/— team lineup per matchGET /api/v1/events/<event>/wifi/— each team'sassignment5channel
- Connects to
ws[s]://<ftc-host>/api/v2/stream/?code=<event>and reads events, ignoring theping/pongkeepalives (it also sends periodicpings to keep the socket alive). - On a
MATCH_STARTevent it looks up the match (byshortName, falling back to number+field), finds the four teams, maps each to its 5 GHz channel, and converts those to frequency bands (center = 5000 + 5×channelMHz, ±½ ×-channel-width). - It POSTs a one-shot, channel-focused capture to the logger:
start = matchStart − preroll,end = matchStart + match-duration, pluspostroll, withfocusRangesset to the match's channel bands and metadata describing the teams/channels. The logger backfills the pre-roll from its in-memory ring and records the (full) band at full rate, then its finalizer seals the window — so it fires once per match and is resilient to its own disconnects. Retrieving the capture's frames returns only those channel bands (the whole band is still on disk; add?full=1to get it).
If the schedule/assignments are unavailable or the match/team isn't found, it falls back to a
full-band capture (and lazily re-fetches on the next miss, to tolerate mid-event updates).
Because the sample stream has no explicit MATCH_END, the window end is derived from
-match-duration (FTC matches are 2:30 on average, some years change this). MATCH_ABORT (configurable) closes a capture early.
hackrf-ftc-logger -ftc-host 10.0.0.5 -event USCMP -logger http://localhost:8080-ftc-host string FTC Scorekeeper host or host:port (required)
-ftc-tls use wss:// (TLS)
-event string FTC event code (required); used in the URL and as the capture code
-logger string hackrf-logger base URL (default "http://localhost:8080")
-api-token string bearer token, if the logger requires one
-preroll dur record this long before match start (default 5s)
-postroll dur keep recording this long after match end (default 10s)
-match-duration dur assumed match length (default 2m30s)
-start-event string updateType that triggers a capture (default "MATCH_START")
-abort-event string updateType that ends a capture early (default "MATCH_ABORT"; empty disables)
-ping-interval dur keepalive ping interval (default 10s; 0 disables)
-field int only capture matches on this field (0 = all)
-channel-width int assumed 5 GHz channel width in MHz for the focus bands (default 20)
-no-focus capture the full swept band instead of the match teams' channels
-addr string address for the review dashboard (default ":8090"; empty disables)
The scorekeeper REST API is reached on the same host as the stream (http, or https with
-ftc-tls).
A mock stream is included:
make run-mock # serves ws://localhost:8099/...
hackrf-ftc-logger -ftc-host localhost:8099 -event TEST # connects and fires on MATCH_STARTBuild is pure Go (no cgo): go build ./cmd/hackrf-ftc-logger (or make build-app). Run it as a
service alongside — or independently of — the logger; it reconnects to the stream with backoff
and tolerates the logger being temporarily unavailable. A sample systemd unit is in
deploy/hackrf-ftc-logger.service.
cmd/hackrf-ftc-logger/ main: wires the bridge together from flags (no cgo)
cmd/mock-ftc-stream/ dev-only mock FTC stream for testing offline
internal/ftc/ FTC Scorekeeper protocols: event stream + schedule/wifi REST
internal/capture/ hackrf-logger capture API client (domain-agnostic)
internal/match/ resolves matches to 5 GHz channels; events -> capture calls
internal/web/ capture review dashboard (serves + proxies to the logger)
deploy/ systemd sample unit