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>
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>
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>
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>
Fix logout flow to properly show auth gate and clear form. Also
disable additional controls for rx role:
- Jog wheel (faded out)
- Jog up/down buttons
- VFO selector buttons
For rx-authenticated users, all frequency/mode adjustment controls
are now properly disabled to prevent accidental changes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Replace location.reload() with disconnect() + auth gate to prevent
browser hang when logging out. The full page reload was causing
issues with resource loading and event source reconnection timers.
Changes:
- Add disconnect() function to cleanly close EventSource connections
and clear all timers (esHeartbeat, reconnectTimer)
- authLogout() now disconnects locally and shows auth gate instead
of reloading the page
- Faster logout experience without full page reload
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Add a Login/Logout button in the header next to the theme toggle,
styled consistently with the theme button. Button behavior:
- When logged in: Shows "Logout" with confirmation
- When not logged in: Shows "Login" to open auth gate
- Visible when on main app (not in auth gate)
- Same theme-toggle-btn styling
Provides quick access to authentication controls without needing to
navigate to the About tab.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Do not auto-connect with guest role. Always show the auth gate when
there's no valid session, allowing the user to choose between:
- Login: Enter passphrase for control access
- Continue as Guest: Proceed with read-only access (if available)
This allows users to enter their control passphrase instead of being
forced into guest mode.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
When RX access is unrestricted (no rx_passphrase required), show
a "Continue as Guest" button on the auth gate to allow immediate
access without requiring login. The button is only shown when guest
mode is available.
Guest mode:
- No authentication required
- Grants rx role immediately
- Can monitor radio but cannot transmit
Login path still available for full control access.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
When a user is authenticated as 'rx' role (read-only), disable all
TX/PTT control buttons and frequency/mode inputs to prevent accidental
attempts to transmit. This provides clear visual feedback that these
controls are not available.
Controls disabled for rx role:
- PTT button
- Power button
- Lock button
- TX Audio button
- Frequency input
- Mode select
- TX Limit input/button
- Jog up/down buttons
- Jog step buttons
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
When HTTP auth is enabled but rx_passphrase is not configured, allow
unauthenticated users to access read-only endpoints (status, events,
decode, audio) without authentication. This enables monitoring-only
access while protecting TX control with a passphrase.
Changes:
- AuthMiddleware: Skip auth check for read routes when rx_passphrase is None
- session_status: Grant rx role to unauthenticated users when no rx passphrase required
Use case: Set only control_passphrase to protect TX/PTT while allowing
anyone on the network to monitor the radio.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
The HTTP server was hardcoding auth config with enabled=false,
ignoring the actual configuration from trx-client.toml. This prevented
authentication enforcement even when enabled with passphrases.
Solution: Store auth config values in FrontendRuntimeContext during
initialization in main.rs, then extract and use them in server.rs
build_server() instead of hardcoding.
Fixes auth bypass where unauthenticated users could access the web UI.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Update example_toml() to include example values for rx_passphrase and
control_passphrase in the printed config output, so users can see what
these configuration fields should look like.
Now --print-config shows:
[frontends.http.auth]
enabled = false
rx_passphrase = "rx-passphrase-example"
control_passphrase = "control-passphrase-example"
tx_access_control_enabled = true
session_ttl_min = 480
cookie_secure = false
cookie_same_site = "Lax"
This helps users understand all available auth parameters.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>