New binary crate that generates trx-server.toml, trx-client.toml, or
trx-rs.toml via interactive prompts or --defaults mode. Produces
commented TOML using toml_edit with per-field descriptions.
Supports server config (general, rig, listen, audio, behavior) and
client config (general, remote, frontends). Hardware detection is
stubbed for future iteration.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Delete trx-ft8 (C wrapper around ft8_lib + ft2_ldpc) and update
trx-server to depend on trx-ftx (pure Rust) directly. Removes
~2,900 lines of C code and all unsafe FFI.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Replace the C FFI-based trx-ft8 with a pure Rust implementation
supporting FT8, FT4, and FT2 protocols. Eliminates cc/libc build
dependencies and all unsafe FFI code while providing the same
Ft8Decoder/Ft8DecodeResult public API.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Serve decode history as gzipped CBOR and decode it in the frontend.
Defer map materialization until replay completes to avoid replay-time stutter,
and include the pending longest-QSO style adjustment.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Move aprsfi and pskreporter modules from trx-server into a new
standalone trx-reporting library crate. Config types (AprsFiConfig,
PskReporterConfig) move to trx-reporting and are re-exported from
trx-server::config for backwards compatibility.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add VirtualChannelManager trait in trx-core::vchan with types VChannelInfo,
VChanError, and SharedVChanManager alias. Re-export from trx-backend::vchan.
Implement SdrVirtualChannelManager in trx-backend-soapysdr:
- Wraps Arc<SdrPipeline> + shared AtomicI64 center_hz
- add_channel / remove_channel / set_channel_freq / set_channel_mode
- Slot-stability: on remove, shifts pipeline_slot for surviving channels
- update_center_hz: recomputes IF offsets for all virtual channels on retune
- update_primary_meta: keeps channel-0 freq/mode in sync for API consumers
Wire into SoapySdrRig (holds Arc<SdrVirtualChannelManager>, exposes
channel_manager()), SdrPipeline (shared_center_hz AtomicI64), and RigHandle
(vchan_manager: Option<SharedVChanManager>). main.rs extracts the manager
before boxing the SDR rig and stores it in the handle.
Add max_virtual_channels to SdrConfig (default 4, TOML-configurable).
Add 5 unit tests: add, remove, permanent guard, cap, out-of-bandwidth.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
State deduplication via PartialEq + send_if_modified:
Derive PartialEq on the full RigState / RigSnapshot type tree
(Freq, Band, RigInfo, RigCapabilities, RigStatus, RigTxStatus,
RigRxStatus, RigControl, RigVfo, RigVfoEntry, RigFilterState,
RdsData, SpectrumData, RigState, RigSnapshot). Use
state_tx.send_if_modified() in refresh_remote_snapshot() so
WatchStream only wakes SSE /events subscribers when state
actually changed; with a stable rig this eliminates ~1.3
spurious JSON serialisations per second per connected client.
Cache-remote-rigs skip on unchanged list:
cache_remote_rigs() was rebuilding the Vec and cloning every
field on every 750 ms poll. Add a structural check (rig_id,
display_name, initialized, audio_port) and return early when
nothing has changed — the common steady-state case.
RDS JSON pre-serialised at ingestion:
SharedSpectrum.replace() now serialises the optional RDS object
once and stores it alongside the Arc<SpectrumData> frame.
Each /spectrum SSE client's 40 ms tick reads the cached string
instead of calling serde_json::to_string() per-client per-tick.
Add serde_json to trx-frontend Cargo.toml to support this.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Three causes of >30 s SSE stalls:
1. SPECTRUM_IO_TIMEOUT was 300 ms — transient server jitter triggered
false-positive spectrum failures and immediate TCP disconnects.
Raised to 1 s to tolerate brief load spikes.
2. reconnect_delay was never reset after a successful TCP connect, so
after a few spectrum-induced disconnects the backoff reached 10 s.
Each new disconnect then cost 10 s of stale SSE state, and several
cycles accumulated to >30 s. Reset to 1 s on every successful
TCP connect so recovery stays fast.
3. SSE pings were emitted as comments (": ping"), which EventSource
never exposes to onmessage. lastEventAt was therefore never updated
by pings, causing the JS heartbeat to force-reconnect every ~20 s
even on healthy, stable connections. Changed to a named "ping"
event and added es.addEventListener("ping", …) to update lastEventAt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Move persistent history from trx-client to trx-server where decode
events originate. History for AIS, VDES, APRS, CW, FT8, and WSPR is
loaded from ~/.local/cache/trx-rs/history.db at startup and flushed
to disk every 60 seconds. CW events are now also stored in
DecoderHistories and replayed to connecting clients, consistent with
all other decoder types.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add pickledb-backed persistent history store for all decoder types
(AIS, VDES, APRS, CW, FT8, WSPR). History is loaded from
~/.local/cache/trx-rs/history.db at startup and flushed to disk
every 60 seconds. On load, entry timestamps are reconstructed from
stored Unix ms values so 24h pruning continues to work correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add a dedicated VDES plugin tab and live bar, stop reusing the AIS vessel UI, and serve a separate VDES frontend script. Rework the SDR backend so VDES receives a single 100 kHz IQ tap, then replace the fake AIS-clone decoder path with an early M.2092-1 oriented complex-baseband scaffold using burst detection, coarse pi/4-QPSK slicing, and TER-MCS-1.100 frame heuristics.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Add a new trx-vdes decoder path alongside AIS, wire VDES through the server/frontend decode pipeline, and fix the web map so AIS vessel symbols load correctly and the TRX receiver marker appears when location data arrives.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add dual-channel AIS decode support across the SoapySDR backend, server decode pipeline, and frontend plugins, including the new AIS tab, live bar, and map filtering.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add pickledb and dirs dependencies. Introduce BookmarkStore wrapping
PickleDb behind Arc<RwLock<>> with list/get/insert/upsert/remove ops.
Keys stored as "bm:{id}" in ~/.config/trx-rs/bookmarks.db; falls back
to ./bookmarks.db when config dir is unavailable.
Wire BookmarkStore into build_server() as actix-web app_data so all
request handlers can share the store.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Replace the per-sample ring-buffer FIR convolution with block-level
overlap-save convolution using rustfft. For a block of M samples and
N taps the old approach costs O(N·M); the new one costs O(M log M),
with rustfft using SIMD (AVX2/SSE4) internally.
Key changes:
- Add rustfft = "6" dependency
- Add BlockFirFilter: overlap-save filter with pre-computed H(f) and
a single forward+inverse FFT pair per block (no per-sample multiply)
- ChannelDsp.process_block() now:
1. Batch-mixes entire block to baseband in one vectorisable loop
2. Applies BlockFirFilter to I and Q (one FFT pair each)
3. Decimates and demodulates as before
- Keep the old FirFilter for unit tests (sample-by-sample interface)
- Add BlockFirFilter unit tests (DC passthrough, length preservation)
- IQ_BLOCK_SIZE promoted to pub const for use in filter sizing
For the default config (4096-sample blocks, 64 taps, decim=40):
Old: ~262144 multiply-adds per FIR × 2 components = ~524k per block
New: ~2 × (3 × 8192 × log2(8192)) ops, all SIMD-vectorised by rustfft
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Remove direct dependency on trx-backend-soapysdr from server Cargo.toml.
Re-export SoapySdrRig from trx-backend instead so server accesses it
through the proper facade. This keeps sub-backends internal to trx-backend
and maintains proper module organization.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Call validate_sdr() at startup and abort on errors (SDR.md §11)
- Build SoapySdrRig with full [[sdr.channels]] config when access_type is
"sdr"; subscribe to its primary-channel PCM sender before handing the
pre-built rig to the rig task via RigTaskConfig.prebuilt_rig
- Skip cpal capture when an SDR audio source is available; bridge the
SdrPipeline PCM broadcast into pcm_tx so all decoder tasks are unchanged
- Add trx-backend-soapysdr optional dep and soapysdr feature to trx-server
- Add prebuilt_rig field to RigTaskConfig so rig_task skips the registry
factory when a pre-built rig is supplied by main
Marks SDR-08 complete.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Introduces the trx-backend-soapysdr crate with a compilable SoapySdrRig
struct that satisfies the Rig + RigCat trait bounds. RX methods
(get_status, set_freq, set_mode, get_signal_strength) are implemented;
TX-only methods return RigError::not_supported. as_audio_source returns
None for now (overridden in SDR-07). Wires the crate into the workspace
and trx-backend (feature "soapysdr"), and fixes the non-exhaustive match
on RigAccess::Sdr in trx-server main.rs and rig_task.rs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Move DecoderLoggers and DecodeLogsConfig out of trx-server into a
dedicated src/decoders/trx-decode-log crate, giving file logging the
same standalone crate treatment as the four decoder crates.
- src/decoders/trx-decode-log/ (new — DecodeLogsConfig + DecoderLoggers)
- trx-server/config.rs: re-exports DecodeLogsConfig from trx-decode-log
so ServerConfig field references and all tests compile unchanged
- trx-server: drop decode_logs module, use trx_decode_log directly
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move the Goertzel-based CW decoder out of trx-server::decode::cw into
a dedicated src/decoders/trx-cw crate, matching the layout of trx-aprs,
trx-ft8, and trx-wspr. The decode module is now empty and removed.
- src/decoders/trx-cw/ (new — Goertzel + Morse decoder)
- trx-server: drop decode module entirely, use trx_cw::CwDecoder
- CwEvent stays in trx-core (mirrors AprsPacket / Ft8Message / WsprMessage)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move trx-ft8 and trx-wspr into src/decoders/ alongside a new trx-aprs
crate that extracts the Bell 202/AX.25 decoder from trx-server, giving
all three modems a consistent crate-per-decoder layout.
- src/decoders/trx-ft8/ (moved from src/trx-ft8/)
- src/decoders/trx-wspr/ (moved from src/trx-wspr/)
- src/decoders/trx-aprs/ (new — Bell 202 AFSK + AX.25/APRS decoder)
- trx-ft8/build.rs: fix external/ft8_lib relative path after move
- trx-server: drop decode::aprs module, use trx_aprs::AprsDecoder
- AprsPacket stays in trx-core (mirrors Ft8Message / WsprMessage)
- Workspace Cargo.toml updated with new member paths
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add optional passphrase-based authentication with two roles (rx/control),
session management, auth middleware, and protected routes.
Phase 1: Config model with HttpAuthConfig struct, CookieSameSite enum,
validation logic for enabled auth requiring at least one passphrase.
Phase 2: Auth module with:
- AuthRole enum (Rx, Control)
- SessionRecord and SessionStore for in-memory session management
- AuthConfig at runtime
- /auth/login, /auth/logout, /auth/session endpoints
- Constant-time passphrase comparison for timing attack mitigation
Phase 3: Integration with:
- AuthMiddleware for route protection with public/read/control classification
- Server-side AuthState setup with cleanup task for expired sessions
- Auth endpoints registered in api.rs configure()
Sessions use 128-bit random IDs (hex-encoded), HttpOnly cookies, configurable
SameSite attribute. Auth is disabled by default to preserve current behavior.
All unit and integration tests passing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Add a new trx-wspr crate that wraps wsprd slot decoding and parsed
results, wire it into the server audio pipeline, and emit WSPR decode
events to clients.
Also add frontend event routing for WSPR decode messages and temporary
rendering in the FT8 table until a dedicated WSPR panel is introduced.
Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Update workspace Cargo.toml to include new trx-protocol crate
and update Cargo.lock with new dependencies.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Remove macOS AppKit frontend (trx-frontend-appkit) and related code:
- Delete appkit crate directory
- Remove appkit dependency and feature from Cargo.toml
- Remove appkit imports, main thread handling, and config from main.rs
- Remove AppKit config struct from config.rs
- Remove appkit section from example config
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Add AudioConfig to server configuration with support for RX capture
and TX playback via cpal and Opus encoding. Run a dedicated TCP
listener (default port 4533) that sends StreamInfo on connect, streams
RX Opus frames to clients, and receives TX frames back.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Add a JSON-over-TCP listener so trx-client can connect to trx-server.
Speaks the ClientEnvelope/ClientResponse protocol from trx-core::client.
- New listener.rs module with per-client connection handling
- ListenConfig/AuthConfig in config.rs (default: 127.0.0.1:4532)
- CLI args --listen and --port for override
- Optional token-based authentication
- Updated example config with [listen] section
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Add a new trx-frontend-appkit crate using objc2 + AppKit as a
replacement for the removed Qt/QML frontend. The frontend provides
the same feature set: frequency/mode/band display, PTT/power/VFO/lock
controls, signal/TX metering, and frequency/mode/TX-limit input.
Architecture splits platform-agnostic model (model.rs) from AppKit
UI (ui.rs) to facilitate future UIKit porting. State flows from the
async tokio watcher via std::sync::mpsc to the AppKit main thread;
button actions flow back through a channel to stay on the UI thread.
Feature-gated behind `appkit-frontend` cargo feature.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Remove the Linux-only Qt/QML frontend (trx-frontend-qt) crate and all
references to it from the workspace, trx-client binary, configuration,
and documentation. This prepares for replacement with a native macOS
AppKit frontend.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Delete trx-bin (all-in-one) and trx-bin-common (shared lib). Each binary
now has its own config, plugins, and helper modules inlined.
- trx-server: backend-only daemon with ServerConfig (general, rig, behavior)
no frontend dependencies
- trx-client: remote client with ClientConfig (general, remote, frontends)
includes all frontend support (http, rigctl, http-json, qt)
- Dedicated config files: trx-server.toml / trx-client.toml
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>