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>
Propagate receiver location (WGS84 decimal degrees) through the state
pipeline so frontends can display the server position on a map.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
- Add APRS symbol icons using hessu/aprs-symbols sprite sheets
- Parse uncompressed and compressed position formats for lat/lon
- Render clickable OpenStreetMap links for position packets
- Replace delay-and-multiply discriminator with mark/space correlation
detector for more robust AFSK decoding
- Reduce PLL gain from 0.7 to 0.4 for stable clock recovery
- Move plugin JS files to plugins/ subdirectory
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Add Auto checkboxes (checked by default) next to WPM and Tone inputs.
Auto Tone uses a multi-bin Goertzel scan across 300-1200 Hz with
stability tracking. Auto WPM collects on-durations in a rolling buffer
and uses k-means-style clustering to separate dits from dahs.
Unchecking Auto allows manual control.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Extract the APRS decoder from app.js into its own aprs.js file and add
a new CW (Morse code) decoder plugin in cw.js. The CW decoder uses a
Goertzel tone detector with configurable WPM, tone frequency, and
signal threshold. The Plugins tab now has three sub-tabs: Overview,
APRS, and CW.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Replace bandpass filter envelope detector with delay-and-multiply
frequency discriminator. Add biquad bandpass pre-filter centered at
1700 Hz to remove out-of-band noise. Replace IIR low-pass with
moving-average LPF over half a bit period, which places a null at
2x mark frequency for clean discriminator artifact removal.
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>
Replace the coherent correlation detector with a non-coherent
bandpass filter + envelope detection approach for significantly
better frequency discrimination between mark (1200Hz) and space
(2200Hz) tones. Uses two cascaded 2nd-order IIR biquad filters
per tone with IIR-smoothed envelope detection.
Replace hard clock reset with PLL-style gradual correction for
smoother bit timing recovery.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Add silence detection that resets the demodulator state when RMS
drops below threshold, so burst-mode APRS packets after squelch
activation start with a clean correlator and clock recovery.
Display CRC-failing frames at reduced opacity with a [CRC] tag to
aid debugging. Enhanced CRC-fail console logging now includes
attempted address decode, bit count, and hex dump.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Assign green to VFO A, yellow to VFO B, and deterministic hues
to additional VFOs. The active VFO color is also applied to the
main frequency display. Rework the header title to show
"trx-rs <version> @ <callsign>'s <rig>" instead of "<rig> status".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Fix three bugs in the Bell 202 AFSK demodulator preventing frame
decoding:
- Separate NRZI state from clock recovery (shared lastBit variable
caused NRZI to always output 1)
- Buffer 1-bits in ones counter instead of pushing to frameBits
immediately, preventing flag/stuff bits from contaminating frame
data and corrupting byte alignment
- Detect flags via ones-count on decoded bits instead of shift
register on raw bits
Also add framed packet log container styling, remove redundant
description text, and add debug counters logging pipeline health
to the browser console.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Replace server-side /frontends endpoint with client-side plugin system.
The Plugins tab now has Overview and APRS sub-tabs. The APRS plugin
decodes packets from RX audio using Bell 202 AFSK demodulation (1200
baud), AX.25 frame decoding with NRZI/HDLC, and CRC-16-CCITT
validation. Decoded packets are displayed in a scrolling log.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Add GET /frontends API endpoint returning registered frontend names as
JSON. Add Plugins tab to the web UI that fetches and displays the list.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Close #tab-main properly so the footer and About tab are not nested
inside it — footer is now visible on all tabs.
Add server address, rig connection method, supported modes, and VFO
count to the About tab.
Move logo from translucent centered overlay to solid image in the
top-right of the header.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Add a tab bar to the HTTP frontend card. All existing controls stay in
the Main tab. A new About tab shows server version, callsign, rig info,
client version, and connected client count, updated live via SSE.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Return 204 for non-WebSocket GET requests to /audio instead of
letting actix-ws reject with 400. This allows the browser's audio
availability probe to work correctly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
The audio listener was ignoring the CLI --listen override and always
binding to the config default (127.0.0.1), making it unreachable
from remote clients.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Start the output stream paused and only play when TX packets
arrive. Pause again when the packet queue drains to prevent
continuous ALSA buffer underruns (EPIPE errno -32) on Linux.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Use subsecond nanosecond jitter to return a varying signal strength
(2-8) from the dummy backend instead of a static value of 5.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Replace the rolling canvas signal graph with a practical signal
measurement feature. The operator can start/stop measurement to
collect signal samples, then view averaged and peak S-unit results.
Also fix signal display to correctly convert dBm wire format to
S-units using standard S-meter scale (S1=-121dBm, S9=-73dBm,
6dB per S-unit).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
- Make server callsign a clickable link to qrzcq.com
- Center frequency input text and use DSEG14 Classic font
- Include callsign in page title (e.g. N0CALL - trx-frontend-http v0.1.0)
- Block jog wheel when rig lock is active
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Load DSEG14 Classic 14-segment LCD font from CDN and apply it to
the frequency input at 2x size for a realistic radio display look.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
- Add RX/TX volume sliders with GainNode audio chain integration,
scroll wheel support, and percentage display
- Display server callsign and version from SSE event data
- Merge TX Limit hint into its label line
- Add copyright footer with link to haxx.space
- Uniform section spacing via flex gap on #content
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Carry server_callsign and server_version through from received
snapshots into the local RigState. Default client callsign is
N0CALL when not configured.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Set server_callsign and server_version on the rig state so they
are included in snapshots sent to clients. Default callsign is
N0CALL when not configured.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Add optional server_callsign and server_version fields to both
RigState and RigSnapshot so that server identity information can
flow through the protocol to clients and frontends.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Reorganize layout: frequency row with jog wheel on top, mode and
transmit/power side by side below. Replace VFO text box with segmented
picker. Add rolling signal history canvas. Track connected SSE clients
and display count in status hint. Unify button heights and add Enter
key support for TX limit input.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
time::interval() fires its first tick immediately. Recreating it on
every loop iteration made the select! always resolve instantly,
turning the main polling loop into a busy-loop (~13% CPU idle).
Replace with a Box::pin(sleep()) that is only reset after it
completes or when the poll duration changes (rx/tx transition).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Instead of running the cpal input stream continuously, start it
paused and only activate when broadcast subscribers are present.
When the last client disconnects, pause the stream and sleep at
100ms intervals polling for new receivers.
This eliminates idle CPU usage from continuous CoreAudio callbacks,
channel allocations, and sample processing when nobody is listening.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Enable audio streaming by default in AudioClientConfig so the
client connects to the server audio port without requiring
explicit configuration.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Enable audio streaming by default in AudioConfig.
Fix panic in audio playback thread caused by calling
tokio::runtime::Handle::current() from a plain std::thread.
Use rx.blocking_recv() instead, which is the correct API for
consuming a tokio mpsc receiver from a synchronous context.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Move window._opusDecoder, window._txEncoder, window._nextPlayTime
into closure-scoped variables to avoid polluting the global namespace.
Add showHint() helper to debounce status hint text, preventing
multiple button handlers from fighting over powerHint.textContent.
Throttle audio level indicator updates to max 10/sec instead of
updating on every Opus packet (~50/sec).
Hide audio controls row if the server has no audio configured
(checks /audio endpoint on load).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Add a 120-second TX safety timeout that auto-releases PTT if no mic
data flows (browser crash, disconnect). Timer resets on each audio
callback. Shows countdown in status when < 10s remaining.
Add beforeunload handler that releases PTT via navigator.sendBeacon
when the browser tab is closed during TX.
Detect WebCodecs support on page load and show "Audio requires
Chrome/Edge" on non-Chromium browsers instead of silently failing.
Remove dead playBuffer/playNode variables that were declared but
never used. Fix TX AudioData to always use mono (channel 0) with
numberOfChannels: 1 matching the f32-planar format.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Replace all hardcoded colors with CSS custom properties on :root for
easier theming. Add focus-visible outlines for keyboard accessibility.
Add mobile breakpoints for screens <= 480px.
Fix PTT button styling to use theme-aware colors (var(--accent-red))
instead of hardcoded light-theme colors (#ffefef, #f3f3f3).
Remove unused .value, .controls, and .section-title CSS classes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Resize favicon from 1024x1024 to 64x64 and logo from 1024x1024 to
256x256, reducing total image size from ~3 MB to ~70 KB.
Fix SSE heartbeat race condition where the 10s ping interval competed
with the 8s stale threshold, causing spurious reconnects. Now pings
every 5s server-side, with a 15s stale threshold and 5s check interval
client-side.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Split the monolithic status.rs string template into three files under
assets/web/ (index.html, style.css, app.js) loaded via include_str!.
Add /style.css and /app.js endpoints with correct content types.
This makes the frontend editable with proper syntax highlighting and
linting support in editors.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Add Audio streaming section and Dependencies table covering system
libraries (libopus, cmake, pkgconf) and new Rust crates (cpal, opus,
bytes, actix-ws).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Add /audio WebSocket endpoint that streams RX Opus frames to the
browser and accepts TX frames back. Browser UI includes RX/TX Audio
toggle buttons with WebCodecs Opus decode/encode and a level indicator.
TX audio automatically engages PTT on start and releases on stop or
WebSocket disconnect.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Connect to the server audio TCP port, relay RX Opus frames into a
broadcast channel and TX frames from an mpsc channel. Pass audio
channels to the HTTP frontend via set_audio_channels. Reconnects
with exponential backoff on disconnect.
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>
Replace #[tokio::main] with a manual fn main() that builds the tokio
runtime explicitly. All async initialization moves into async_init().
When the appkit frontend is requested, the runtime context is entered
on the main thread and run_appkit_main_thread() is called directly,
giving AppKit thread 0 as required by MainThreadMarker. Ctrl+C is
handled via a spawned task that calls process::exit.
When appkit is not requested, behaviour is unchanged: block on Ctrl+C.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Extract the AppKit event loop from FrontendSpawner::spawn_frontend into
a new public run_appkit_main_thread() function that blocks on the
calling thread. This allows the process main thread (thread 0) to drive
the UI, which is required for MainThreadMarker::new() to succeed.
The FrontendSpawner impl now only spawns the async state watcher task.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Allow connecting with just an IP address (e.g. --url 127.0.0.1)
instead of requiring host:port format.
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>