A host application for a TinySA which serves a Web UI for remote control
  • Go 45.8%
  • TypeScript 38.5%
  • HTML 9.5%
  • CSS 5.2%
  • Makefile 1%
Find a file
2026-09-08 16:50:35 +10:00
cmd/server First version, working and tested 2026-08-25 22:35:57 +10:00
internal Shifted to the kkettinger go-tinysa now that TCP forwarding is merged. UI still non-functional 2026-09-08 16:50:35 +10:00
web Added Mutex to fix sweep setting update bug. Still has some time-dependent failures where the value you enter gets reset instead of being applied 2026-08-26 21:25:00 +10:00
.gitignore First version, working and tested 2026-08-25 22:35:57 +10:00
go.mod Shifted to the kkettinger go-tinysa now that TCP forwarding is merged. UI still non-functional 2026-09-08 16:50:35 +10:00
go.sum Shifted to the kkettinger go-tinysa now that TCP forwarding is merged. UI still non-functional 2026-09-08 16:50:35 +10:00
Makefile First version, working and tested 2026-08-25 22:35:57 +10:00
README.md Shifted to the kkettinger go-tinysa now that TCP forwarding is merged. UI still non-functional 2026-09-08 16:50:35 +10:00
UserInterface.md First version, working and tested 2026-08-25 22:35:57 +10:00

tinySA Web

A self-hosted web UI for the tinySA spectrum analyzer. It's a Go backend (using a forked version of go-tinysa to talk to the device) serving a small REST + SSE API, and a plain HTML/CSS/Canvas + TypeScript frontend with no build framework or bundler. It aims to cover the same ground as the various PC control apps listed on the tinySA wiki (sweep control, multi-trace with hold/average calc functions, multi-marker with delta/tracking, waterfall, presets, screenshot capture, battery status, LNA/spur toggles, raw command access) in one live, continuously-updating page.

See UserInterface.md for how to use the running app, the go-tinysa fork, and known device gotchas.

Prerequisites

  • Go 1.25+
  • Node.js + npm (for compiling the TypeScript frontend with tsc)
  • go-tinysa from kkettinger
  • A tinySA, reachable either:
    • directly, as a local serial port (e.g. if running this on the same machine the tinySA is plugged into, perhaps /dev/cu.something), or
    • over TCP, via a serial-to-TCP bridge such as socat (see Connecting to the device below, requires the forked go-tinysa).

Building

make build

This runs make web (installs frontend deps and compiles web/src/*.ts to web/dist/*.js via tsc) and then builds the Go binary to bin/tinysa-server.

To build just the frontend or just the backend:

make web                                # web/dist/*.js
go build -o bin/tinysa-server ./cmd/server

Running

Two Makefile targets cover the two ways to reach the device (edit SERIAL, HOST, and PROXYPORT at the top of the Makefile to match your setup):

make run       # direct serial connection
make run-tcp   # TCP bridge (e.g. socat) - see "Connecting to the device" below

equivalent to:

go run ./cmd/server -serial-port=/dev/cu.usbmodem4001
go run ./cmd/server -bridge-addr=host.docker.internal:9001

If neither -serial-port nor -bridge-addr is given, the server lists the /dev/cu.* devices it finds (most recently created first) and exits, as a hint for -serial-port.

Or run the built binary directly:

./bin/tinysa-server [flags]

Then open the printed listen address in a browser (default http://localhost:9040). If you're running inside a container/devcontainer whose ports aren't published to the host, you can access it through a port-forwarding proxy such as the one provided by VSCode's web interface. For example if you have this running on https://localhost:8190/, you can access the TinySA at https://localhost:8190/proxy/9040/

Just make sure whatever URL you use ends up serving the page from a path that lets relative asset/API requests resolve correctly (a trailing slash after any proxy path prefix as shown in this example).

Flags

Flag Default Description
-addr :9040 HTTP listen address for the web UI and API.
-bridge-addr (empty) host:port of a socat (or similar) TCP bridge to the tinySA's serial port. Mutually exclusive with -serial-port; if neither is set, the server lists available /dev/cu.* serial devices and exits.
-serial-port (empty) Connect directly to this serial port instead of the TCP bridge, e.g. /dev/ttyACM0 or /dev/cu.usbmodem4001. Use this when running the server on the same machine the tinySA is physically attached to. Mutually exclusive with -bridge-addr.
-web-dir web Directory to serve the frontend from (expects index.html, style.css, dist/*.js inside it).
-poll-interval 0 (→ 1.5s) How often to poll the device for live state. Don't set this much lower - the tinySA is bridged over a 115200 baud serial link (~9-11 KB/s effective), and a single 450-point trace response is already ~10-11 KB; polling faster than the link can drain a response backs up the device's command parser until it stops responding entirely.
-debug false Enable debug-level logging (logs every HTTP request and every device command).

Connecting to the device

The included Makefile has two helper targets for the TCP-bridge setup, meant to be run on whichever machine the tinySA is physically plugged into:

make tcp            # socat <serial device>,raw,... TCP-LISTEN:<port>,reuseaddr,fork
make connectivity   # telnet <host> <port>, for manually sanity-checking the bridge

Edit SERIAL, HOST, and PROXYPORT at the top of the Makefile to match your setup (defaults: /dev/cu.usbmodem4001, host.docker.internal, 9001). Once make tcp is running, point tinysa-server at it with make run-tcp (or -bridge-addr=<host>:<port> directly - host.docker.internal if the server runs in a container on the same machine, the bridge machine's LAN address/hostname otherwise).

If the server runs directly on the machine with the tinySA attached, skip the bridge entirely and use make run (or -serial-port directly) instead.

Development

  • make web rebuilds the frontend; the Go server serves web/dist/*.js straight off disk, so no server restart is needed after a frontend-only change.
  • cd web && npm run watch recompiles TypeScript on save.
  • go vet ./... and gofmt -l . before committing Go changes.