Commit Graph
68 Commits
Author SHA1 Message Date
ClaudeandStan Grams 28769d01d0 [docs](trx-rs): document UI/UX design guidelines
Add docs/UX_Guidelines.md covering web frontend patterns (theming, responsive
design, accessibility, real-time data), REST API conventions, CLI interface,
configuration wizard, error handling, branding, security UX, and inferred
design principles.

https://claude.ai/code/session_01LC9Yp36ARX47bmKavNPTcB
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 14:09:24 +01:00
ClaudeandStan Grams 27117a8de5 [feat](trx-core): add periodic TLE refresh from CelesTrak
Fetch fresh weather satellite TLEs from CelesTrak on startup and then
once every 24 hours. The dynamic TLE store is checked first in
tle_for_satellite(), falling back to the existing hardcoded TLEs when
the fetch has not yet completed or fails.

- Add global TLE_STORE (RwLock<HashMap<norad_id, (line1, line2)>>)
- Add parse_tle_response() to parse 3-line TLE format
- Add refresh_tles_from_celestrak() async fetch + store update
- Add spawn_tle_refresh_task() for startup + daily refresh loop
- Refactor tle_for_satellite() into norad_id lookup + store check
- Spawn refresh task in trx-server alongside wxsat decoder tasks
- Add reqwest (rustls-tls) dependency to trx-core

https://claude.ai/code/session_01RB19i93dnemDYLcfrhyhqc
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 12:41:31 +01:00
ClaudeandStan Grams 929f1d3fab [fix](trx-server): remove unused pass_start_ms variable in wxsat decoder
The variable was assigned in six places but never read, producing
compiler warnings. The other `pass_start_ms` in `run_wxsat_image_task`
is unaffected and still used.

https://claude.ai/code/session_01SWe5x4CWy1q6BXtX4Gt3Qd
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 12:35:29 +01:00
ClaudeandStan Grams 560b6ec912 [feat](trx-rs): add weather satellite map overlay integration
Add SGP4-based geo-referencing for NOAA APT and Meteor LRPT decoded
satellite images, enabling them to be displayed as semi-transparent
overlays on the Leaflet map module with ground track polylines.

Changes:
- Add sgp4 crate dependency to trx-core for orbital propagation
- New trx-core/src/geo.rs module with TLE-based pass geo-referencing,
  ECI-to-geodetic conversion, and station-location fallback estimation
- Extend WxsatImage and LrptImage structs with geo_bounds and
  ground_track optional fields (backward compatible via serde defaults)
- Compute geo-bounds in finalize_wxsat_pass and finalize_lrpt_pass
  using satellite identity, pass timestamps, and station coordinates
- Add 'wxsat' source filter to the map module (off by default)
- Add L.imageOverlay rendering with popup and ground track polyline
- Add "Show on Map" buttons in wxsat plugin live/history views

https://claude.ai/code/session_01DUCfb9CjGoViwBrznpfWyt
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 12:24:36 +01:00
ClaudeandStan Grams c1b713a5b2 [feat](trx-rs): add GitHub workflow to sync docs/ to wiki
Introduces a GitHub Actions workflow that mirrors the docs/ directory
to the repository wiki on every push to main that touches docs/. Also
supports manual dispatch via workflow_dispatch.

https://claude.ai/code/session_01Hs4BtTczFdXaggpzaHFfen
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 11:29:57 +01:00
ClaudeandStan Grams 3ff848a715 [docs](trx-wxsat): add README with architecture and API documentation
https://claude.ai/code/session_01Cm1JpWMDZanjwKg3r2S3VR
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 11:23:33 +01:00
ClaudeandStan Grams 83a6b27130 [fix](trx-frontend-http): fix missing wxsat/lrpt history functions and lrpt_decode_enabled field
Remove calls to non-existent clear_wxsat_history and clear_lrpt_history
functions from the client-side clear endpoints. These image-based decoders
don't maintain client-side history unlike text decoders. The server-side
reset command (already sent) handles the cleanup. Also add missing
lrpt_decode_enabled field to the fallback RigSnapshot initializer.

https://claude.ai/code/session_019FkSMWpGR3XpWBvUghCybe
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 11:06:20 +01:00
ClaudeandStan Grams 9df71cf36c [docs](trx-rs): deep codebase review with updated architecture and improvement plan
Architecture.md: added detailed component notes covering rig_task internals,
audio pipeline, remote client dual-connection model, FrontendRuntimeContext
field groups, decoder implementation patterns, and FT-817 backend workarounds.

Improvement-Areas.md: added 10 new findings from deep review including LIFO
command batching, unbounded decoder history, missing jitter in backoff, rig
task crash recovery, SoapySdrRig constructor complexity, and protocol versioning.

CLAUDE.md: refreshed review observations with accurate LOC counts, prioritized
improvement items (P1/P2/P3), and new strengths identified.

https://claude.ai/code/session_011aiY4GfrmDUrpYVvEUGNGm
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 10:59:44 +01:00
ClaudeandStan Grams 384e1597f6 [style](trx-wxsat): apply cargo fmt formatting
https://claude.ai/code/session_01JA13DHuzuHUL4nSBBRU83f
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 10:36:34 +01:00
ClaudeandStan Grams 6d0c01c6c4 [feat](trx-frontend-http): add Live/History views to Weather Satellites panel
Replace flat image list with two switchable views:
- Live: decoder state cards (Idle/Listening), descriptions, latest image
- History: filterable table with columns for time, type, satellite,
  channels, lines, and download link. Supports text filter, type filter
  (All/APT/LRPT), and sort order (newest/oldest).

https://claude.ai/code/session_01JA13DHuzuHUL4nSBBRU83f
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 10:36:34 +01:00
ClaudeandStan Grams 4d40c29e49 [refactor](trx-wxsat): unify image encoding to shared PNG module
Extract common image_enc module at crate root with encode_grayscale_png
and encode_rgb_png helpers. Both NOAA APT and Meteor-M LRPT now use PNG
as the output format through the shared encoder. Drop jpeg image feature
dependency.

https://claude.ai/code/session_01JA13DHuzuHUL4nSBBRU83f
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 10:36:34 +01:00
ClaudeandStan Grams 1a3b815ed8 [feat](trx-wxsat): add Meteor-M LRPT decoder and Weather Satellites frontend panel
Restructure trx-wxsat into noaa/ (APT) and lrpt/ (Meteor-M LRPT) submodules
with shared crate base. Add QPSK demodulator, CCSDS CADU framer, MCU channel
assembler for LRPT. Wire LRPT through full stack (core types, protocol, server
decoder task, client). Add Weather Satellites sub-tab in Digital Modes with
toggle buttons for NOAA APT and Meteor LRPT, descriptions, and image history.

https://claude.ai/code/session_01JA13DHuzuHUL4nSBBRU83f
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 10:36:34 +01:00
ClaudeandStan Grams d26ef6ca81 [feat](trx-wxsat): rename trx-noaa to trx-wxsat with full NOAA APT decode
Rename the crate from trx-noaa to trx-wxsat (weather satellite) across
the entire workspace. Add full NOAA satellite decode support:

- Telemetry frame parsing: extract 16-wedge calibration data from the
  128-line telemetry frames embedded in APT lines
- Radiometric calibration: piecewise-linear LUT built from wedges 1-8
  to correct pixel values against known reference levels
- Channel identification: detect AVHRR sensor channels (VIS, NIR, MIR,
  TIR) from wedge 9 values per APT sub-channel
- Satellite identification: heuristic NOAA-15/18/19 detection from
  channel A/B sensor pairings
- Histogram equalisation: per-channel contrast enhancement for improved
  image output
- WxsatImage now carries satellite name and channel labels in decoded
  message broadcasts

https://claude.ai/code/session_01JA13DHuzuHUL4nSBBRU83f
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 10:36:34 +01:00
ClaudeandStan Grams a8b19227d5 [docs](trx-rs): codebase review - architecture docs, improvement plan, CLAUDE.md observations
Deep review of all 22 workspace crates (~52k LOC across 117 files).

- docs/architecture.md: system design, crate map, data flow, concurrency model
- docs/improvement-plan.md: 19 prioritized improvements (P0-P3)
- CLAUDE.md: updated crate layout (added missing crates), added review observations
  documenting strengths and areas for improvement

https://claude.ai/code/session_01CtmH5WraR6fjmt5Rx7ooEv
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 22:12:58 +01:00
ClaudeandStan Grams cf79df110a [fix](trx-frontend-http): map filter affects statistics, tooltips show receiver rig
Statistics panels (longest paths, strongest/weakest signals) now respect
all active map filters — source type, rig selector, band, search, and
history. Locator tooltips display which rig received each decoded frame.

https://claude.ai/code/session_01LT7zBnb2kQiYpeTuWNXHsT
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 22:01:43 +01:00
ClaudeandStan Grams 12726f3d2c [feat](trx-frontend-http): add R hotkey to round frequency up to next step
Remap retune from R to T, and add a new R hotkey that rounds the
current frequency forward to the next jog-step boundary. Both the
new hotkey and the remapped retune are documented in the F1 help
overlay.

https://claude.ai/code/session_017neG2jL9uXFSRpmhyS1EqG
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 21:52:28 +01:00
ClaudeandStan Grams 7c33af3d83 [feat](trx-frontend-http): add keyboard hotkeys for radio controls
Map R=retune, B=previous state, [/]=bandwidth ±10kHz, arrows=tune/center,
M=mode picker, Z=mono/stereo, N=noise blanker, Q=squelch toggle.
Document all shortcuts in the F1 help overlay.

https://claude.ai/code/session_01WDC889uQGW9XoPQqSZ6bVt
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 21:44:05 +01:00
ClaudeandStan Grams 71fe884ae6 [fix](trx-frontend-http): show all rigs data on map panel regardless of active rig
Remove rig_id filtering from dispatchDecodeMessage and dispatchDecodeBatch
so that decode data from all rigs (including remote/non-primary) flows into
the map. Also remove the rig_filter query param from decode history fetch
so all history is loaded. The existing map rig filter dropdown handles
visibility filtering via marker.__trxRigIds.

https://claude.ai/code/session_01GGvdXKdEbRBnJa2BjAQuVB
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 21:21:25 +01:00
Claudeandsjg 0905360ba5 [fix](trx-backend-soapysdr): remove noise/pilot dependency from WFM signal strength
The noise floor subtraction was over-aggressive: the bandwidth ratio
scaling between the 67 kHz baseband probe and the IQ domain amplified
the noise estimate excessively, causing weak stations to be subtracted
to nothing.  The pilot-referenced correction only worked for stereo
stations.

Strip the signal strength path back to what actually works universally:
mean IQ envelope power with asymmetric attack/decay smoothing.  This
always produces a reading for any FM signal — mono, stereo, with or
without RDS.

The baseband noise probe, CNR estimation, and pilot metrics remain in
the WfmStereoDecoder for their existing uses (RDS quality weighting,
CCI/ACI estimation) but no longer feed into the S-meter.

https://claude.ai/code/session_017URSDqSJ8TyZpDhV2vKZUe
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 20:50:06 +01:00
ClaudeandStan Grams 41fa9dc242 [feat](trx-backend-soapysdr): proper WFM signal strength algorithm
Replace the simple IQ power averaging with a proper WFM signal
strength measurement algorithm based on established RF engineering
practice:

1. Asymmetric attack/decay smoothing (τ_attack=2ms, τ_decay=300ms)
   per IARU Region 1 Technical Recommendation R.1 for professional
   S-meter behaviour.  Fast attack catches signal increases
   immediately; slow decay provides stable, readable meter movement.

2. Baseband noise floor estimation via a 67 kHz probe in the
   demodulated FM baseband.  FM demodulation noise follows an f²
   spectral shape, so energy above the useful baseband (audio +
   RDS ≤ 57 kHz) is dominated by channel noise and independent of
   program content.  Subtracting this noise estimate in the linear
   domain reveals the carrier-only power, preventing the meter from
   reading the noise floor on empty/weak channels.

3. Pilot-referenced quality correction.  The 19 kHz stereo pilot
   has a known fixed amplitude at the transmitter (±7.5 kHz
   deviation, 10% of ±75 kHz).  Near the FM threshold (~10 dB CNR)
   where noise dominates the IQ reading, the pilot tone power
   provides an independent quality-weighted correction.  The blend
   factor scales from 0.3 at low CNR down to 0 at high CNR where
   the raw IQ measurement is already accurate.

4. CNR estimation from the ratio of total baseband power to the
   above-band noise probe, enabling adaptive pilot correction and
   providing a signal quality metric for future use.

https://claude.ai/code/session_017URSDqSJ8TyZpDhV2vKZUe
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 20:33:09 +01:00
ClaudeandStan Grams 3ca3836702 [fix](trx-backend-soapysdr): measure WFM signal strength in IQ domain, not power domain
The previous carrier power IIR filtered |IQ|² (power), which only smoothed
temporal fluctuations but still integrated noise across the full 180 kHz WFM
channel bandwidth. This caused background noise to read ~-78 dBFS instead of
the expected ~-110 dBFS (~32 dB too high ≈ 10·log₁₀(180kHz/500Hz)).

Move the single-pole IIR lowpass to the IQ domain (filter I and Q separately
at ~500 Hz cutoff), then compute power from the filtered output. This rejects
out-of-band noise before the power measurement, so the meter reads true
carrier level rather than total wideband noise.

https://claude.ai/code/session_01W4WPMB2Lg3hgaY6opsk25f
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 18:28:35 +01:00
ClaudeandStan Grams 0ad3440a2d [fix](trx-backend-soapysdr): use narrow carrier IIR for WFM signal strength
Replace peak |IQ|² measurement with a per-sample single-pole IIR lowpass
on the instantaneous power (~500 Hz cutoff).  FM has constant envelope so
the IIR converges to the true carrier power A², rejecting wideband noise
that previously inflated the peak reading and masked actual signal level.

Other modes keep the existing peak + EMA approach.

https://claude.ai/code/session_01X6tedMVpjX3DEqLFDBR7FK
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 17:35:08 +01:00
ClaudeandStan Grams cf82c853cf [fix](trx-frontend-http): stabilize signal strength field width and BW overlay positioning
Increase sig-strength-display min-width to 7.5rem so the field no longer
resizes when the value switches between two-digit and three-digit numbers.

Reposition the fast BW overlay immediately when bandwidth changes arrive
via SSE, and force-display on bookmark apply so freq+bw render atomically
instead of the BW bars wiggling from a stale intermediate state.

https://claude.ai/code/session_01R2XBFEBL8CrsTx5inu25MA
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 17:18:45 +01:00
ClaudeandStan Grams 434a7f899d [fix](trx-frontend-http): stop SSE updates from resetting frequency input and causing redundant spectrum redraws
SSE status updates called applyLocalTunedFrequency with forceDisplay=true,
clearing the freqDirty flag on every update and overwriting user input mid-
typing. Remove forceDisplay from SSE path so the dirty flag is respected.

Skip applyLocalTunedFrequency entirely when frequency hasn't changed to
avoid redundant spectrum redraws and overlay repositioning on every SSE
frame. Only trigger scheduleSpectrumDraw when frequency actually changes.

Add blur and Escape handlers on frequency inputs to cleanly exit editing
mode when the user abandons input.

https://claude.ai/code/session_01H2VMATj29FPgR64t9YMdSR
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 17:02:57 +01:00
ClaudeandStan Grams ec0e41fe29 [refactor](trx-rds): remove Gardner TED to fix decoder freeze
The closed-loop Gardner Timing Error Detector was causing decoder
freezes under real-world conditions.  Remove all TED state and logic,
reverting to the simpler open-loop fixed clock_inc approach.  The
8-candidate parallel architecture already provides adequate timing
coverage via phase offsets without needing closed-loop tracking.

All other improvements (adaptive Costas bandwidth, syndrome-based OSD,
OSD(3/4), PI LLR accumulation) are retained.

https://claude.ai/code/session_01FsK5hZWGpAaaCpmWupN5AD
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 16:44:11 +01:00
ClaudeandStan Grams a387047464 [fix](trx-backend-soapysdr): widen RDS bandpass Q from 5.0 to 3.5 to reduce in-band distortion
The 8th-order (4×biquad) RDS bandpass at Q=5 per stage produced a
composite −3 dB bandwidth of ±2480 Hz, but the steep 8th-order roll-off
tapered the RDS signal edges (±1544 Hz at α=0.30) by −1.2 dB.  This
distorted the RRC matched filter's expected flat spectrum, causing ISI
and degrading soft-decision confidence — directly hurting PS/RT decode
on weak signals.

Q=3.5 widens the composite passband to ±3560 Hz, reducing band-edge
attenuation to −0.59 dB while still providing ≈−4 dB rejection at the
stereo difference signal edge (53 kHz) and steep 8th-order far-out
roll-off.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>

https://claude.ai/code/session_01Sw9esAuic8KHP1t8nZgvH2
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 16:28:39 +01:00
ClaudeandStan Grams dd1af5aa94 [fix](trx-rds): revert RRC span to 10 chips to restore stopband rejection
The span-5 reduction passed synthetic tests because both the TX and RX
filters used the same truncated pulse shape (perfect matched filtering).
On real signals, the transmitter uses a full RRC pulse, and our truncated
RX filter couldn't match it — the weaker stopband rejection (~25% less
than pre-TED at α=0.30) allowed adjacent-channel interference through,
degrading soft confidence values and block decode rate, which caused
poor PS accumulation.

Span 10 at α=0.30 gives 50% better stopband rejection than the pre-TED
α=0.50/span=4 configuration, at the cost of 2048 vs 1024 FFT size.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>

https://claude.ai/code/session_01Sw9esAuic8KHP1t8nZgvH2
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 16:28:39 +01:00
ClaudeandStan Grams c92e53c3d3 [fix](trx-rds): restore pre-TED decode quality with tighter OSD, higher TED gate, and syndrome-based search
Three root causes for the post-TED decode quality regression:

1. OSD(4) at cost ceiling 0.60 produced excessive false positives at
   marginal SNR.  Tightened to OSD(2)/0.45 baseline, OSD(3)/0.50 only
   after 2+ successful groups.

2. Gardner TED activated after just 1 group (score >= 1), but a single
   false OSD match could trigger timing adjustments that injected jitter
   into soft values.  Raised lock gate to score >= 3 so the TED only
   engages after the candidate has proven itself on a real signal.

3. RRC filter span of 10 chips doubled FFT size to 2048 with negligible
   sensitivity gain over span 5 at α=0.30 (sidelobes beyond ±2.5 chips
   contribute <5% energy).  Reduced to span 5 → FFT 1024, matching
   pre-TED efficiency.

Additional optimizations (no quality impact):
- Syndrome-based OSD: replaces per-trial CRC recomputation with a single
  XOR per trial (CRC linearity), and sorts bit positions by ascending
  soft confidence so inner loops break early instead of continuing.
- Pre-allocated FFT scratch buffer: eliminates ~234 heap allocations/sec
  in the overlap-save convolution.
- PI_ACC_THRESHOLD reduced from 8 to 5 for faster acquisition while
  retaining reliable majority voting.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>

https://claude.ai/code/session_01Sw9esAuic8KHP1t8nZgvH2
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 16:02:45 +01:00
ClaudeandStan Grams d864969742 [feat](trx-frontend-http): add clickable Sig Strength field between Wavelength and Frequency
Add a new "Sig Strength" display field in the freq row that shows
the measured signal strength. Clicking the field cycles through
three units: dBFS (default), dBf, and dBm. The selected unit is
persisted in localStorage.

https://claude.ai/code/session_01EvRV8UgsVtbrcH4t2hmFBF
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 13:37:47 +01:00
ClaudeandStan Grams 54ca86a93d [fix](trx-rds): stabilise Gardner TED to fix PI fluctuation and weak-signal regression
The Gardner TED (Tech 11) caused PI instability and worse weak-signal
pickup due to three issues:

1. Loop gains too aggressive: noise×noise error products at low SNR
   injected sub-chip jitter that degraded OSD soft confidence and PI
   LLR accumulation.  Reduced Kp from 4e-4→1.5e-4, Ki from 8e-8→2e-8
   (loop BW 0.11→0.053 Hz).

2. TED active during acquisition: before any group is decoded, the
   error signal is unreliable.  Now lock-gated (score >= 1) so the
   TED only engages after the first successful group decode, when
   timing is already close.  During acquisition, the 8-candidate
   architecture with fixed clocks provides adequate timing coverage.

3. Slow power estimate convergence: ted_power_est took ~420 ms to
   settle (0.999 alpha), causing the TED to over-steer during startup.
   Now uses 0.995 alpha (~84 ms convergence).

Additionally, when TED is gated off, the integrator decays toward zero
so stale corrections from a previous strong-signal period don't persist.

https://claude.ai/code/session_01KcVUcQQXrFyFA9NEjLhr9J
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 11:51:08 +01:00
ClaudeandStan Grams e44e616ab8 [fix](trx-backend-soapysdr): fix ACI/CCI always reading 0% in WFM
ACI: the hard limiter in channel.rs normalised IQ samples to unit
magnitude *before* the CMA equalizer, making the signal perfectly
constant-modulus so the CMA never adapted and tap deviation stayed
at zero.  Fix by moving the hard limiter inside process_iq (after
the CMA) and replacing the CMA-based metric with IQ envelope
coefficient of variation, computed on the raw samples.

CCI: the pilot coherence has a theoretical maximum of π/4 ≈ 0.785
(not 1.0), so coherence_penalty was always ~0.215 even for a clean
signal.  The Q/I ratio also depended on the arbitrary NCO-pilot
phase offset rather than actual interference.  Fix by normalising
coherence by its theoretical max and dropping the phase-dependent
Q/I ratio.  Gate CCI on pilot detection so mono signals read 0%.

https://claude.ai/code/session_01PUXWNMRGfrWYH56k2DLmen
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 11:38:03 +01:00
ClaudeandStan Grams d4d852456f [fix](trx-rds): stay locked between groups to prevent decoder freeze
After completing a group (Block D), the decoder dropped lock and
reverted to search mode which only uses hard CRC. On weak signals,
Block A frequently has bit errors that OSD could correct but hard
decode cannot, causing the decoder to freeze after 2-3 successful
groups. Stay locked with ExpectBlock::A so the next Block A benefits
from OSD soft decoding.

https://claude.ai/code/session_015Ds9dxpeyFimYHySBuzbFw
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 10:41:56 +01:00
ClaudeandStan Grams 4da4d8ec66 [feat](trx-rs): add CCI/ACI bars to WFM panel with RDS mitigation
Estimate Co-Channel Interference (CCI) from pilot tone quadrature
leakage and coherence degradation.  Estimate Adjacent Channel
Interference (ACI) from CMA equalizer tap deviation from identity.
Both metrics (0-100 scale) are surfaced through RigFilterState and
displayed as colour-coded bars in the WFM control panel.

The RDS decoder quality parameter is now adaptively penalised when
CCI/ACI levels are elevated, reducing block-error rate under
interference conditions.

https://claude.ai/code/session_016EKzep42RCvE4GxvvRaCwu
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 09:44:28 +01:00
Claudeandsjg 6b33550116 [fix](trx-rds): add staleness timeout to prevent decoder freeze
If the incumbent candidate has not produced a state update in 2 seconds,
clear its score advantage so any candidate can take over.  This prevents
the decoder from "freezing" on stale data when the incumbent's timing or
carrier tracking degrades — particularly important for dynamic PS where
the station rotates program service text.

Signed-off-by: Claude <noreply@anthropic.com>

https://claude.ai/code/session_0136sPdLUpYgvskrzbi2Epkv
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 08:46:29 +01:00
ClaudeandStan Grams 104629e373 [feat](trx-rds): push RDS decoding to 5 dB SNR
Fix Gardner TED loop structure bug (type-3 → type-2 PLL) and tune
gains for ζ=0.707 damping.  Add adaptive Costas loop bandwidth that
narrows from ~22 Hz to ~5.5 Hz once carrier is locked, reducing phase
noise at low SNR.  Narrow RRC matched filter (α=0.30, span=10 chips)
for ~0.6 dB noise BW gain.  Add OSD(4) for locked-mode blocks after
first successful group, and increase PI accumulation threshold to 8.

TED bug details: the original code used `clock_inc += correction`
which added the full integrator value at every chip, creating an
extra integration (type-3 loop) that is unconditionally unstable.
Fixed to `clock_inc = nominal + correction` (standard type-2 PLL).
Gains retuned: Kp=4e-4, Ki=8e-8 for ζ≈0.707 and loop BW≈0.11 Hz.

Signed-off-by: Claude <noreply@anthropic.com>

https://claude.ai/code/session_0136sPdLUpYgvskrzbi2Epkv
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 08:28:39 +01:00
ClaudeandStan Grams 2ba942f33b [refactor](trx-frontend-http): simplify auto-squelch to single-click action
Revert toggle approach back to a simple button: click sets squelch to
noise floor + 6 dB when spectrum data is available, or Off otherwise.

https://claude.ai/code/session_01TDQyrZiPKfWGATVWPsLmHT
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 07:49:18 +01:00
ClaudeandStan Grams 0e5410c0c5 [feat](trx-frontend-http): make auto-squelch a toggle, default to Off
The Auto button now toggles between Off and Auto states. Default is Off.
First click sets squelch to noise floor + 6 dB; second click resets to
Open (0%). Button shows active state with green highlight when engaged.

https://claude.ai/code/session_01TDQyrZiPKfWGATVWPsLmHT
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 07:49:18 +01:00
ClaudeandStan Grams 7edc8b7bfe [fix](trx-frontend-http): auto-squelch defaults to Off without spectrum data
When no spectrum data is available, the Auto button now sets squelch
to 0% (Off) instead of silently doing nothing.

https://claude.ai/code/session_01TDQyrZiPKfWGATVWPsLmHT
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 07:49:18 +01:00
ClaudeandStan Grams 2d8dfb1a3d [feat](trx-frontend-http): add auto-squelch button to Audio panel
Add an "Auto" button next to the SQL slider that sets the squelch
threshold to the current noise floor (estimated from spectrum bins)
plus a 6 dB margin. Uses the existing estimateNoiseFloorDb() heuristic.

https://claude.ai/code/session_01TDQyrZiPKfWGATVWPsLmHT
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 07:49:18 +01:00
ClaudeandStan Grams 5d6b9a4d94 [fix](trx-rds): reduce false decodes with OSD cost ceiling and PI consistency
Add OSD_MAX_FLIP_COST (0.45) to reject OSD corrections where the flipped
bits had high confidence — a strong false-decode indicator. Genuine errors
at 9-10 dB SNR have cost ≲0.3; noise matches cost 0.6-1.2.

Add PI consistency gate in process_group: reject groups whose Block A PI
differs from the candidate's established PI, preventing noise from
polluting accumulated PS/RT/PTYN text fields.

Raise PI_ACC_THRESHOLD from 2 to 3 for stronger PI voting.

Extend noise rejection test from 0.5s to 2s. Add 9 dB SNR sensitivity
test (all 16 tests pass).

https://claude.ai/code/session_01GYax4BQ9ZV9ZZfMjmmzgbh
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 07:20:22 +01:00
ClaudeandStan Grams 54b1f20ea4 [fix](trx-wspr): reduce false positives in WSPR decoder
The WSPR decoder was producing many false positive decodes due to
several overly permissive thresholds that allowed noise to reach the
Fano sequential decoder, which could then converge on random data:

- Raise normalized sync score threshold from 0.10 to 0.20 to reject
  noise candidates before attempting expensive Fano decoding
- Add minimum SNR gate (-20 dB) to skip candidates where the signal
  is indistinguishable from noise
- Return and check the Fano decoder's cumulative path metric, rejecting
  low-confidence decodes (metric < 20) that are likely noise artifacts
- Raise RMS threshold from 0.0005 to 0.005 to reject near-silent audio
- Add near-frequency deduplication to prevent the same signal decoded
  at slightly different (freq, dt) offsets from appearing multiple times
- Add noise-only regression test to verify no false positives on random
  input

https://claude.ai/code/session_01HTBoEsD1hp99TiYMSaHMVG
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 02:06:56 +01:00
ClaudeandStan Grams 06796342e7 [fix](trx-rds): apply RRC pulse shaping in test signal generator
The `chips_to_rds_signal` test helper was generating rectangular chip
pulses, but the receiver expects RRC-shaped transmit pulses so that
RRC(tx) × RRC(rx) = raised cosine with zero ISI. The rectangular
pulses caused ISI that drifted the symbol clock sampling point,
consistently skipping PS segment 2 in the end-to-end test.

Replace rectangular pulses with an impulse train convolved with the
same RRC taps used by the receiver. All 15 tests now pass including
`end_to_end_clean_signal_decodes_ps`.

https://claude.ai/code/session_01N2UcGaLDzYiM3gNrZ6kFBj
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 01:53:29 +01:00
ClaudeandStan Grams 8e6623b39e [fix](trx-rs): use per-message rig_id for map marker tagging
The map module was tagging all decode markers (APRS, AIS, VDES,
FT8/FT4/FT2/WSPR locators) with the global rig picker's active rig
instead of the actual source rig. This made the map's own rig filter
dropdown ineffective in multi-rig setups.

- Add rig_id field to all decode message structs (AisMessage,
  VdesMessage, AprsPacket, CwEvent, Ft8Message, WsprMessage)
- Set rig_id on messages in audio_client before broadcasting, using
  the actual rig connection identifier
- Update history collector to prefer message rig_id over the global
  active rig fallback
- Pass rig_id through plugin normalize functions (AIS, APRS, VDES,
  HF-APRS) so it reaches the map add functions
- Update all map marker functions (aprsMapAddStation, aisMapAddVessel,
  vdesMapAddPoint, mapAddLocator) to use the message's rig_id with
  fallback to the global picker for backward compatibility

https://claude.ai/code/session_015gC7axHk2jmp7HbFPdbivN
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 16:29:53 +01:00
ClaudeandStan Grams a63f27971d fix(trx-server): remove unused RigSdr import in rig_task.rs
Also run cargo fmt to fix formatting issues across trx-server,
trx-frontend-http, and trx-configurator.

https://claude.ai/code/session_01RsHUyVz2wjQjsEsxJo5owt
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 14:01:44 +01:00
ClaudeandStan Grams c8f33b8939 [refactor](trx-rs): remove shared-library plugin system
Drop the plugin loading infrastructure (libloading-based dynamic .so/.dylib/.dll
loading) from both trx-server and trx-client. The feature was unused and posed an
unnecessary security risk by executing arbitrary native code from disk.

Removed:
- src/trx-app/src/plugins.rs (plugin discovery, validation, FFI registration)
- examples/trx-plugin-example/ (cdylib example plugin)
- libloading dependency from trx-app
- load_backend_plugins / load_frontend_plugins calls from server and client
- Plugin documentation from README.md and CLAUDE.md

https://claude.ai/code/session_01DTEUpz3XPUeWmz74NeaFgb
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 12:44:56 +01:00
ClaudeandStan Grams 9692e31c8c [docs](trx-rs): update Improvement Areas wiki — mark resolved items
Update the Improvement Areas audit document to reflect all fixes
implemented across PRs #58, #59, and #60. 22 items now marked as
resolved; 5 remaining items reorganized by priority.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 11:42:25 +01:00
ClaudeandStan Grams 3e0169b91f [refactor](trx-backend-soapysdr): implement RigSdr trait for SoapySdrRig
Move 13 SDR-specific methods from impl RigCat into a new impl RigSdr
block. Add as_sdr()/as_sdr_ref() overrides returning Some(self) so the
SDR extension is accessible via the RigCat trait object.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 08:47:17 +01:00
ClaudeandStan Grams 748d26f47d [refactor](trx-server): route SDR commands through RigCat::as_sdr()
Update all SDR command handlers in rig_task to access SDR methods via
ctx.rig.as_sdr() instead of calling them directly on RigCat. Query-only
SDR operations (filter_state, get_spectrum, get_vchan_rds) use
as_sdr_ref(). Non-SDR rigs now get proper not_supported errors.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 08:47:17 +01:00
ClaudeandStan Grams 74749bc6de [refactor](trx-core): split RigCat into base trait + RigSdr extension
Extract 13 SDR-specific methods (set_center_freq, set_bandwidth,
set_sdr_gain/lna/agc/squelch/nb, set_wfm_*, filter_state, get_spectrum,
get_vchan_rds) into a new RigSdr trait. RigCat retains core CAT
operations and gains as_sdr()/as_sdr_ref() for optional SDR access.

Non-SDR backends no longer see SDR methods in their trait impl.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 08:47:17 +01:00
ClaudeandStan Grams aa3ed81786 [fix](trx-server): replace all history mutex .expect() with poison recovery
Replace 25 .expect("X history mutex poisoned") calls in DecoderHistories
with .unwrap_or_else(|e| e.into_inner()) to gracefully recover from
poisoned locks instead of crashing the server.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 08:47:17 +01:00
ClaudeandStan Grams 337cb72974 [refactor](trx-server): use state data constructors for pub(crate) fields
Migrate ready_data_from_state and transmitting_data_from_state to use
the new ReadyStateData::new() and TransmittingStateData::new()
constructors instead of direct struct field initialization.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 07:29:55 +01:00
ClaudeandStan Grams 8ce2e4ed08 [refactor](trx-core): restrict state data fields to pub(crate) with accessors
Make ReadyStateData and TransmittingStateData fields pub(crate) to
prevent external mutation that could bypass state machine invariants.
Add constructors and getter methods for external consumers.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 07:29:55 +01:00
ClaudeandStan Grams 71c23f0895 [refactor](trx-frontend-http): pre-allocate spectrum encoding output
Replace format! with pre-allocated String::with_capacity for spectrum
frame encoding, reducing allocation overhead in the hot SSE path.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 07:29:55 +01:00
ClaudeandStan Grams adf65ae56d [fix](trx-frontend-http): warn when auth enabled but cookie_secure is false
Log a startup warning when HTTP auth is active but cookie_secure remains
false, alerting operators that session cookies will be sent unencrypted.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 07:29:55 +01:00
ClaudeandStan Grams 9b6c845fa8 [fix](trx-frontend-http): recover from poisoned locks and document ordering
Replace all .unwrap() on RwLock/Mutex acquisitions with
.unwrap_or_else(|e| e.into_inner()) to gracefully recover from poisoned
locks instead of panicking. Add lock ordering documentation to the
module header to prevent deadlocks.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 07:29:55 +01:00
ClaudeandStan Grams c5320ca2fb [refactor](trx-server): add AtomicUsize counter and recover from poisoned locks
Add an AtomicUsize total_count field to DecoderHistories, maintained by
record/prune/clear methods, so estimated_total_count() avoids 9 separate
mutex acquisitions. Also replace audio ring buffer .unwrap() calls with
.unwrap_or_else(|e| e.into_inner()) to recover from poisoned locks.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 07:29:55 +01:00
ClaudeandStan Grams 74eb755858 [chore](trx-rs): update Cargo.lock
https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 07:02:46 +01:00
ClaudeandStan Grams 22ad90b2ba [fix](trx-server): release mutex before serialization in flush_all
Clone history data out under the lock, then drop the guard before
calling save_key, so serialization never blocks concurrent readers.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 07:02:46 +01:00
ClaudeandStan Grams d7c8eed44f [feat](trx-frontend-http): add per-IP login rate limiting
Implement LoginRateLimiter that tracks failed login attempts per IP,
enforcing a cooldown (10 attempts per 60s window) to mitigate brute-
force attacks on the /auth/login endpoint.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 07:02:46 +01:00
ClaudeandStan Grams c299e9a2d2 [fix](trx-server): truncate raw JSON in error logs to 128 chars
Prevent potential information disclosure by truncating raw client input
in log messages instead of logging the full payload.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 07:02:46 +01:00
ClaudeandStan Grams edf16b63d6 [fix](trx-core): log invalid state machine transitions
Add debug-level tracing for rejected state transitions instead of
silently returning false, aiding debugging of unexpected rig behavior.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 07:02:46 +01:00
ClaudeandStan Grams 9cf66fc04a [fix](trx-app): add plugin loading validation and disable toggle
Reject world-writable plugin files on Unix to prevent loading tampered
libraries. Add TRX_PLUGINS_DISABLED env var to disable plugin loading
entirely.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 07:02:46 +01:00
ClaudeandStan Grams cbe22bd7b6 [refactor](trx-frontend-http): replace string-level JSON splice with serde(flatten)
Use a StateWithMeta wrapper struct with #[serde(flatten)] for merging
rig state with frontend meta, replacing the manual string manipulation.
Also add Serialize derive and skip_serializing_if to FrontendMeta.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 07:02:46 +01:00
ClaudeandStan Grams 635a1214d0 [refactor](trx-client): switch VChanAudioCmd to bounded channels (cap 256)
Replace unbounded_channel with channel(256) for VChanAudioCmd to prevent
unlimited memory accumulation under backpressure. Use try_send in
synchronous contexts.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 07:02:46 +01:00
ClaudeandStan Grams c3abc5ff5b [refactor](trx-frontend): add DecodeHistory type alias and use bounded channels
Introduce DecodeHistory<T> alias for the repeated
Arc<Mutex<VecDeque<(Instant, Option<String>, T)>>> pattern (9 fields).
Also switch VChanAudioCmd channel senders from UnboundedSender to Sender
to prevent unbounded memory growth under backpressure.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 07:02:46 +01:00
ClaudeandStan Grams 449b877694 [refactor](trx-ftx): use HashSet for candidate deduplication
Replace Vec::contains() with HashSet::insert() for O(1) dedup lookups
instead of O(n), significantly reducing comparisons during decode.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 07:02:46 +01:00
ClaudeandStan Grams 99d95c8eb6 [refactor](trx-frontend-rigctl): adapt to Cow-returning mode_to_string
Update rig_mode_to_str to call .into_owned() on the new Cow return.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 07:02:46 +01:00
ClaudeandStan Grams 019f12e3fc [refactor](trx-protocol): return Cow<'static, str> from mode_to_string
Eliminates per-call String allocations for standard modes by returning
borrowed static strings. Only the Other variant allocates.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 07:02:46 +01:00