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>
Forwards CRC-valid RF APRS packets to APRS-IS via plain TCP using the
TNC2 line format, making them visible on aprs.fi and other APRS-IS
consumers. Mirrors the pskreporter module in structure.
- New aprsfi.rs: IGate task with reconnect loop (exponential backoff
1s→60s), login/logresp, 60s keepalive, 60s stats, passcode
auto-computation from callsign (standard APRS hash algorithm)
- config.rs: AprsFiConfig struct with enabled/host/port/passcode fields
and validation; default host rotate.aprs.net:14580
- main.rs: mod aprsfi; spawn task inside audio block when aprsfi.enabled
- trx-server.toml.example, CONFIGURATION.md: document [aprsfi] section
- Remove APRSFI_IMPLEMENTATION.rs planning artifact
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After ALSA POLLERR the existing cpal Device handle can be stale, causing
repeated stream build failures with no path to recovery. Re-acquire host
and device inside the outer recreation loop so each attempt gets a fresh
handle. Device-not-found is now a warn+retry rather than a fatal error,
allowing recovery from transient USB audio device disappearances.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
CPAL error callbacks can fire millions of times per second on ALSA
EPIPE (errno -32). Previously each invocation did a string allocation
and mutex lock, saturating CPU and eventually crashing the server.
- Use atomic swap in both input and output error callbacks so only the
first error fires the expensive log+notify path; all subsequent hits
cost a single atomic op and return immediately.
- Replace stream.play()? with explicit error handling in run_playback
so a play failure triggers stream recreation instead of permanently
terminating the playback thread.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <stanislawgrams@gmail.com>
Change JSON TCP listener default from 4532 to 4530 and audio
streaming default from 4533 to 4531.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <stanislawgrams@gmail.com>
Add compile-time build dates for trx-server and trx-frontend-http, propagate server build metadata through rig state/snapshot, and render both versions + build dates in the HTTP footer.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Improve rigctl interoperability with hamlib/WSJT-X and stabilize FT-817 PTT handling.\n\n- support extended '+' command replies\n- accept decimal and MHz-style frequency inputs\n- retry set_freq rounded to 10 Hz on CAT alignment errors\n- add compatibility handling for get_level probes\n- broaden PTT command parsing and aliases\n- derive PTT capability dynamically from snapshot data\n- improve dump_state/dump_caps compatibility behavior\n- move temporary rigctl diagnostics to debug level\n- make FT-817 set_ptt more reliable with unlock/clear and double-send\n\nCo-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Add min_freq_step_hz to RigCapabilities, set backend values, and make HTTP frontend parse suffix-less frequency input using the selected unit while snapping set/jog frequencies to rig step granularity.
Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Require a receiver locator source when PSK Reporter is enabled,
show inactive reason in status text, and add periodic uplink runtime
counters (received/sent/skipped/errors).
This makes missing-spot issues visible instead of silently dropping
all decode events.
Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Implement ALSA/CPAL stream auto-recovery by recreating input/output
streams after backend callback failures with bounded retry delay.
Also improve HTTP frontend resilience by polling /status on reconnect
and after SSE errors to refresh snapshot state after broken pipes.
Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Pass pskreporter_status through RigTaskConfig and apply it to rig_task
state initialization so snapshot updates keep the About-tab value.
Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Fix server config discovery to include ~/.trx-server.toml and update
example/print-config output to explicitly include general latitude and
longitude fields.
Also update config docs and add a test for the legacy search path.
Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Add pskreporter_status to shared rig snapshots and display it in the
HTTP frontend About tab.
Also include audio stream error log throttling to avoid repetitive ALSA
error flooding in backend logs.
Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Add a PSK Reporter uploader task that subscribes to decoded FT8/WSPR
messages and sends spots over UDP when [pskreporter].enabled is true.
Include new [pskreporter] server config options and example config docs,
and hardcode software identification as 'trx-server v<version> by SP2SJG'.
Co-authored-by: Codex <codex@openai.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>
Add semantic validate() checks for server/client config models and fail fast on invalid ranges, field combinations, and auth token values before runtime startup.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Add coordinated shutdown signaling and task supervision for long-running server and client tasks to avoid detached runtimes on Ctrl+C.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Replace legacy global BackendRegistry with bootstrap context adapter that
maintains backward compatibility while delegating to explicit context.
Changes:
- Create BOOTSTRAP_CONTEXT: OnceLock<Arc<Mutex<RegistrationContext>>>
- register_backend(): delegates to bootstrap context
- is_backend_registered(): reads from bootstrap context
- registered_backends(): reads from bootstrap context
- build_rig(): reads from bootstrap context
Result: Plugins continue calling global functions, but all operations
now route through the bootstrap context instead of a separate global
registry. This completes the de-globalization while maintaining full
backward compatibility with existing plugins.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Add register_builtin_backends_on(context: &mut RegistrationContext) function
to allow explicit backend registration on a context instead of always using globals.
This enables proper initialization sequencing where backends are registered
on a specific context that can be passed through bootstrap.
The global register_builtin_backends() still works for plugin compatibility,
delegating to the new context-based approach.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Create explicit RegistrationContext type for backend factory registration
instead of relying solely on global mutable state.
New RegistrationContext:
- register_backend(name, factory) - register a backend
- is_backend_registered(name) - check if registered
- registered_backends() -> Vec<String> - list all backends
- build_rig(name, access) -> DynResult - instantiate a rig
Maintains global API for plugin compatibility, delegates to context.
Paves way for threading context through bootstrap in Phase 3B.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Replace server's local implementations with unified trx-app utilities.
Changes:
- Use trx_app::normalize_name() instead of local fn
- Depend on trx-app crate
This eliminates the server's copy of the normalize_name logic and ensures
both server and client use the same implementation.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Replace duplicated RigState initialization with new constructor methods.
This fixes a critical bug where rig_task was hardcoding 144.3 MHz/USB
instead of respecting config.initial_freq_hz and config.initial_mode.
Changes:
- Use RigState::new_with_metadata() in main.rs
- Use RigState::new_with_metadata() in rig_task.rs (FIX: now respects config)
- Remove 45-line build_initial_state() helper function
The rig_task bug fix ensures the configured initial frequency and mode
are applied when the rig task starts, instead of always using defaults.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Use centralized trx-protocol crate for:
- parse_mode and mode string parsing
- parse_envelope with fallback behavior
- command mapping (ClientCommand -> RigCommand)
- token validation with SimpleTokenValidator
Removes 116 lines of duplicate code. Wraps validator in Arc for
safe sharing across async tasks. No behavior changes, all tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Process decoder commands as early returns in rig_task (no CAT needed).
Check aprs_decode_enabled/cw_decode_enabled flags in decoder tasks
alongside mode. Track reset_seq to trigger decoder.reset() on clear.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
The APRS info field is raw AX.25 bytes, not valid UTF-8.
from_utf8_lossy inserts multi-byte replacement characters, causing
panics when the position parser sliced by byte index. Switch
parse_aprs_position, parse_aprs_compressed, parse_aprs_lat, and
parse_aprs_lon to operate on &[u8] instead of &str.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
The capture thread only checked Opus broadcast subscribers to decide
whether to start cpal input. PCM tap subscribers (APRS/CW decoders)
were not considered, so decoding never started without a browser
audio client. Include pcm_tx receiver count in the has_receivers
check.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Port Bell 202 AFSK demodulator (correlation detector, PLL clock
recovery, NRZI+HDLC, AX.25/APRS parser) and Goertzel CW decoder
(auto tone scan, auto WPM via k-means, Morse lookup) from browser JS
to Rust.
Add PCM tap to audio capture thread, spawn APRS/CW decoder tasks
gated by rig mode (PKT for APRS, CW/CWR for CW). Forward decoded
messages over the audio TCP wire using new message types 0x03/0x04.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Add latitude and longitude Option<f64> fields to [general] config
section and propagate through ResolvedConfig, RigTaskConfig, and
build_initial_state into the RigState/RigSnapshot pipeline.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Add fast path in the TCP listener to serve GetSnapshot requests
directly from the state watch channel, so clients get a response
even while the rig task is initializing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>