Set tx/tx_limit/vfo_switch/filter_controls/signal_meter on all backends:
- FT-817, FT-450D, dummy: tx=true, tx_limit=true, vfo_switch=true,
filter_controls=false, signal_meter=true
- SoapySDR: tx=false, tx_limit=false, vfo_switch=false,
filter_controls=true, signal_meter=true
SoapySDR backend now stores bandwidth_hz and fir_taps fields; overrides
set_bandwidth, set_fir_taps, and filter_state on RigCat to expose live
DSP state in the snapshot.
Add UC-08 unit tests on dummy backend asserting tx capabilities present
and filter_controls absent, and that filter_state returns None.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add SetBandwidth { bandwidth_hz: u32 } and SetFirTaps { taps: u32 } to
ClientCommand with bidirectional mapping to RigCommand variants.
Add UC-09 protocol serialization tests confirming that RigSnapshot
serializes the filter field when Some and omits it when None.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add five new boolean fields to RigCapabilities: tx, tx_limit,
vfo_switch, filter_controls, signal_meter. These drive which controls
the HTTP frontend shows or hides per rig type.
Add RigFilterState struct (bandwidth_hz, fir_taps, cw_center_hz) and
filter: Option<RigFilterState> to both RigState (skip-serialized) and
RigSnapshot (skip_serializing_if = None).
Add SetBandwidth and SetFirTaps to RigCommand; add default not-supported
implementations of set_bandwidth, set_fir_taps, and filter_state to
the RigCat trait.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Keeps README.md, CLAUDE.md, and CONTRIBUTING.md at root as standard
project files. Moves AI-generated design/specification documents
(AGENTS, AUTH, CONFIGURATION, ENHANCEMENT, MULTI, OVERVIEW, SDR,
UI-CAPS) into autogendoc/ to distinguish them from hand-maintained docs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Specifies UC-01 through UC-09: extending RigCapabilities with tx/tx_limit/
vfo_switch/filter_controls/signal_meter flags, RigFilterState struct,
SetBandwidth/SetFirTaps protocol commands, new HTTP endpoints, and
frontend visibility gating via applyCapabilities() in app.js.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Documents architecture, TOML format, protocol wire format, validation
rules, and task completion status (MR-01 through MR-09).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add GetRigs command, rig_id routing fields, and RigEntry type:
- ClientCommand::GetRigs (intercepted in listener before rig_task)
- rig_id: Option<String> on ClientEnvelope (absent = first rig)
- rig_id: Option<String> and rigs: Option<Vec<RigEntry>> on ClientResponse
- RigEntry { rig_id, state } for GetRigs aggregated response
- Sentinel unreachable!() arm for GetRigs in client_command_to_rig()
- MR-09 codec tests: rig_id parsing, GetRigs round-trip, serde omit-when-None
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
- 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>
Adds 12 unit tests for ServerConfig::validate_sdr() covering: minimal
valid config, non-SDR skip, empty/missing args, zero sample_rate,
channel IF out-of-range (positive, negative, exactly at Nyquist), dual
stream_opus, tx_enabled with SDR backend, duplicate decoder, and
multiple simultaneous errors. Marks SDR-11 complete in SDR.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Replace the stub SoapySdrRig with a full implementation: wire up SdrPipeline
from dsp.rs, implement AudioSource::subscribe_pcm on the primary channel,
add gain control (manual/auto with fallback warning), and track primary
channel freq/mode so set_freq/set_mode update the live DSP pipeline.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add 9 unit tests covering all demodulators in demod.rs: USB/LSB/Passthrough
real-part extraction, AM DC removal and varying-envelope, FM tone frequency
and silence, CW peak normalisation, mode mapping, and empty-input safety.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add dsp.rs with IqSource trait abstraction, MockIqSource, windowed-sinc
FIR low-pass filter, ChannelDsp (mixer/decimate/demod/frame-accumulator),
and SdrPipeline which spawns a dedicated IQ read thread. No soapysdr crate
dependency; the real device will be wired in SDR-07.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add [sdr], [sdr.gain], and [[sdr.channels]] sections to CONFIGURATION.md,
extend [rig.access] with type = "sdr" / args, add CLI override note, and
append a commented SoapySDR example block to trx-server.toml.example.
Mark SDR-09 as complete in SDR.md.
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>
Add SdrConfig, SdrGainConfig, and SdrChannelConfig structs to config.rs,
extend AccessConfig with an args field for the "sdr" access type, wire
SdrConfig into ServerConfig, and implement validate_sdr() with all
startup validation rules from SDR.md §11. Marks SDR-03 complete.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add AudioSource trait to trx-core rig module providing subscribe_pcm()
for demodulated PCM audio. Add opt-in as_audio_source() default method
to RigCat returning None; SDR backends will override to return Some(self).
Re-export AudioSource from the crate root. Marks SDR-01 complete.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add `RigAccess::Sdr { args: String }` to the backend access enum, register
a feature-gated `soapysdr` factory stub in `register_builtin_backends_on`,
handle the new variant in all existing `match` arms, and add the `soapysdr`
feature flag (no dep yet; implementation lands in SDR-04). Mark SDR-02 done.
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>
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 default remote connection port from 4532 to 4530 and audio
server_port from 4533 to 4531.
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>
Simplify the opening paragraph for better readability and remove the
(work in progress) notice since the project has substantial, documented
features and a stable structure.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <sjg@haxx.space>
Add CORS, referrer policy, and content-type security headers.
Configure logger to track real client IP in reverse-proxy setups
via Forwarded / X-Forwarded-For / X-Real-IP headers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <sjg@haxx.space>
Fix authenticated refresh by restoring tab visibility during startup, and retune dark mode toward deep blue with amber-red accents.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stanislaw 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>
Enable sig-clear-btn for RX users since clearing signal measurements
is a local UI operation that doesn't affect rig state.
Only disable decode history clear buttons:
- aprs-clear-btn
- ft8-clear-btn
- wspr-clear-btn
- cw-clear-btn
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Replace separate theme and auth buttons with a visually connected
button group. Buttons are grouped with no gap between them and
rounded corners only on the outer edges, creating a cohesive control.
Features:
- First button: rounded left edges
- Last button: rounded right edges
- Middle buttons: sharp edges (if more added)
- Negative margin to overlap borders smoothly
- Hover effect for feedback
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
After logging in, automatically show the Main tab-panel and mark
the Main tab button as active. Previously, users had to click the
Main tab to see content after login.
hideAuthGate() now:
- Shows the Main tab-panel
- Hides all other tab-panels
- Marks the Main tab button as active
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
When logout is clicked from the About tab, hide all tab panels to
prevent tab content from showing behind the login window.
Also ensures auth gate is displayed cleanly without any stray content
visible behind it.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Disable all decode history clear buttons for rx-authenticated users:
- APRS clear
- FT8 clear
- WSPR clear
- CW clear
- Signal clear
These are control operations that should be restricted to full access.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
After logout, check if guest mode is available and show the
"Continue as Guest" button if no rx_passphrase is configured.
Previously, authLogout() always passed false to showAuthGate(),
hiding the guest button.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
When rx_passphrase is not set, RX users have an implicit role without
a session. They should get 403 on control endpoints, not 401.
Previously, unrestricted RX users (with no session) trying control
endpoints would get 401 Unauthorized, triggering login redirect.
Now they get 403 Forbidden with "Insufficient permissions" hint.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Fix auth middleware to return correct HTTP status codes:
- 401 Unauthorized: No session (not authenticated)
- 403 Forbidden: Has session but insufficient role
Previously, all auth errors returned 401, which caused the frontend
to redirect rx users to login when they tried control endpoints.
Now rx users scrolling jog wheel/frequency will get a "Insufficient
permissions" hint instead of being redirected to login.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Remove duplicate logout button from About tab. Use only the header
Login/Logout button for unified authentication control.
The About tab now shows the authentication badge (when logged in)
without the redundant logout button.
Single login view:
- Auth gate with Login form + Continue as Guest button (when no rx pass)
- Header Login/Logout button for quick access
- Auth badge in About tab showing current role
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Only redirect to login on 401 (unauthenticated). For 403 errors
(authenticated but insufficient role), let the caller handle the error.
This prevents rx-authenticated users from being redirected to login
when they attempt to scroll the jog wheel or frequency input, which
tries to call /set_freq (a control-only endpoint).
RX users will now see "Insufficient permissions" hint instead of
being sent to login screen.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>