The panel listed the same bookmarks twice: a status card at the top naming
the selected ones and what each was doing, and a checklist below naming all
of them with the checkboxes. Neither list said which row in the other it
meant, so choosing what to decode and reading what was being decoded were two
passes over the same names. The checklist itself sat squeezed against the
right-hand edge, because the row it lives in sets `align-items: flex-end` and
an inline `flex-direction: column` turned that into "push everything right".
It is one list now. A row carries its checkbox, its name, its frequency,
mode and decoder, and its own state, so picking and watching happen in the
same place. What the rig can hear moves up beside the switch, where it
explains why a selected bookmark reads out of span, and the selection adds
itself up under the list. The states lose the ✓/△/✗ they carried next to a
dot that already said as much, and say what they mean: "Out of span",
"Scheduler has it", "Nobody listening", each with the reason on hover. The
empty list now says which of the two reasons it is empty for, and what to do.
Save was live from the moment the panel opened, with nothing to save; it now
offers itself only when something has changed.
Two races behind it, both of which left the panel useless rather than ugly:
* The rig was whatever the panel was handed at load. Loading before the rig
list arrived handed it null, and the next telling only came when the
operator switched rigs — so the panel stayed empty and silent. Every rig
list refresh now passes the rig on, and both panels ignore one they have.
* The settings panels are wired once the session is up, but their modules
import asynchronously and the wiring skipped whichever had not arrived.
A panel that missed it kept dead buttons for the rest of the session: no
filter, no Select All, no Save. Wiring runs again when the modules land.
Closes #52
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SyX26FCpMQxiBoC7r5K1A7
Signed-off-by: Stan Grams <sjg@haxx.space>
trx-rs splits radio hardware access from user-facing interfaces so you can run
rig control, SDR DSP, decoding, audio streaming, and web access as separate,
composable pieces.
| Backends | Yaesu FT-817, Yaesu FT-450D, SoapySDR |
| Frontends | Web UI, rigctl-compatible TCP, JSON-over-TCP |
| Decoders | AIS, APRS, CW, FT8, RDS, VDES, WSPR |
| Audio | Opus streaming between server, client, and browser |
Quick Start
1. Install dependencies
Debian / Ubuntu
sudo apt install build-essential pkg-config cmake libopus-dev libasound2-dev
# Optional — SDR support
sudo apt install libsoapysdr-dev
Fedora
sudo dnf install gcc pkg-config cmake opus-devel alsa-lib-devel
# Optional — SDR support
sudo dnf install SoapySDR-devel
Arch Linux
sudo pacman -S base-devel pkgconf cmake opus alsa-lib
# Optional — SDR support
sudo pacman -S soapysdr
macOS (Homebrew)
brew install cmake opus
# Optional — SDR support
brew install soapysdr
See Build Requirements in the wiki for details on each library.
Note:
cmakeis required even when a system Opus library is installed. Theaudiopus_syscrate probes for Opus viapkg-config; if it is not found (orpkg-configis unavailable), it falls back to compiling a vendored copy of Opus with CMake. A missingcmaketherefore fails the build withis cmake not installed?rather than a missing-Opus error.
2. Build
cargo build --release
Build without SDR support: cargo build --release --no-default-features
3. Configure
Run the interactive setup wizard to generate config files for your station:
./target/release/trx-configurator
The wizard walks you through rig selection, serial port detection, audio
settings, and frontend options, then writes trx-server.toml and
trx-client.toml.
Alternatively, copy trx-rs.toml.example — a commented example covering every
setting — and edit it by hand:
cp trx-rs.toml.example trx-rs.toml
./target/release/trx-server --check-config --config trx-rs.toml
--check-config reports everything wrong with a config without starting
anything. --print-config prints the same settings without comments.
4. Run
./target/release/trx-server --config trx-server.toml
./target/release/trx-client --config trx-client.toml
A single trx-rs.toml can configure both: the server reads its [trx-server]
section and the client reads [trx-client].
Open the configured HTTP frontend address in a browser (default http://localhost:8080).
How It Works
graph TD
SDR1["SDR #1"] & SDR2["SDR #2"] <-->|USB| S1["trx-server A"]
SDR3["SDR #3"] & FT817["FT-817"] <-->|USB / serial| S2["trx-server B"]
S1 <-->|"JSON-TCP :4530"| C1["trx-client"]
S1 -->|"Opus-TCP per rig"| C1
S2 <-->|"JSON-TCP :4530"| C1
S2 -->|"Opus-TCP per rig"| C1
C1 <-->|internal channels| F1["Web UI :8080"]
C1 <-->|internal channels| F2["rigctl :4532"]
Each trx-server owns one or more rigs and runs DSP, decoding, and audio capture locally.
A trx-client connects to any number of servers over TCP and exposes them through
a unified set of frontends.
Documentation
| Resource | Description |
|---|---|
| User Manual | Configuration, features, and usage |
| Architecture | System design, crate layout, data flow, and internals |
| Optimization Guidelines | Performance guidelines for the real-time DSP pipeline |
| Planned Features | Roadmap and design notes |
| Contributing | Commit conventions, workflow, and code style |
License
GPL-2.0-or-later. See LICENSES for the full license text and
bundled third-party license files. Bundled third-party components retain their
original licenses: Leaflet is BSD-2-Clause, DSEG is OFL-1.1, and opus-decoder
is MIT. The APRS symbol sprites come from
hessu/aprs-symbols; their per-symbol
copyright status is catalogued in
LICENSES/LicenseRef-APRS-Symbols.txt.
