Skip decoded candidates where ftx_message_decode() returns a non-OK
status instead of forwarding a synthetic error string.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
The score from ft8_lib is an averaged uint8 difference between Costas
sync tones and their neighbours (each unit = 0.5 dB). The previous
score * 0.5 gave the signal-above-adjacent-noise in dB relative to a
single 3.125 Hz waterfall bin, yielding values of +5 to +50 dB —
all wrong.
Subtract 10*log10(2500/3.125) ≈ 29 dB to normalise to the 2500 Hz
reference bandwidth used by WSJT-X and expected by PSKReporter:
snr = score * 0.5 - 29.0
This maps score 10 (minimum decodable) → -24 dB and score 60 → +1 dB,
matching typical WSJT-X SNR report ranges.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add [workspace.package] version = "0.1.0" to the root Cargo.toml and
switch all 21 member crates to version.workspace = true so the entire
workspace is versioned from a single place.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Adds a second APRS demodulator path tuned for the HF APRS standard
(300 baud Bell 103-style AFSK, mark=1600 Hz / space=1800 Hz), active
on RigMode::DIG. Shares AX.25 framing, APRS parsing, APRS-IS uplink,
and frontend display with the existing VHF stack.
- trx-aprs: parameterise Demodulator::new(); add AprsDecoder::new_hf()
- trx-core: HfAprs variant in DecodedMessage; hf_aprs_decode_enabled /
hf_aprs_decode_reset_seq in RigState/RigSnapshot; SetHfAprsDecodeEnabled
and ResetHfAprsDecoder commands; handlers.rs fallback arm updated
- trx-protocol: client command variants + bidirectional mapping; test
fixture updated
- trx-server: run_hf_aprs_decoder() task (activates on DIG mode);
hf_aprs history in DecoderHistories; rig_task command dispatch;
aprsfi uplink forwards HfAprs via OR-pattern
- trx-frontend: hf_aprs_history in FrontendRuntimeContext
- trx-frontend-http: prune/record/snapshot/clear helpers; SSE history
replay; toggle_hf_aprs_decode + clear_hf_aprs_decode endpoints;
/hf-aprs.js endpoint; HF APRS tab in web UI
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Replace the free-running phase counter in slice_pi4_qpsk_symbols with
linear interpolation to sample the IQ stream at exact symbol epochs.
Add estimate_differential_cfo() that uses the 4th-power method to
cancel pi/4-QPSK modulation phase, yielding a per-burst CFO estimate
that is removed before differential decoding.
At the ~1.25 samples/symbol IQ rate produced by the current decimation
pipeline, a closed-loop Gardner or Mueller-Müller TED requires at least
2 SPS and cannot be applied; the open-loop linear interpolation is the
best achievable without restructuring the IQ tap.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
- Reduce analysis window from 50ms to 10ms so the decoder can detect
dits at 25+ WPM (at 25 WPM a dit is 48ms, shorter than the old window)
- Fix dot/dash classification threshold from 2.0× to 1.5× unit_ms;
ITU Morse dah = 3× dit, so the midpoint boundary is 1.5×
- Replace O(n) on_durations.remove(0) with drain() to trim the window
- Remove pointless emit_text() wrapper; callers now call emit_event()
directly
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Keep manual CW tone setting range at 100-10000 Hz, but limit auto-tone
scanning to 300-1200 Hz to avoid locking onto high-frequency noise.
Retain Nyquist-safe upper clamping.
Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Expand CW decoder tone scan/control range to 100-10000 Hz and cap the
upper bound by sample-rate Nyquist to keep detection stable.
Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Restore empty events on tone edges so the frontend signal indicator updates again.
Add synthetic-tone tests covering transitions and basic decoding.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Keep geo sanity checks, but treat most marginal VDES decodes as low confidence instead of rejecting them outright.
Co-authored-by: Stan Grams <sjg@haxx.space>
Signed-off-by: Stan Grams <sjg@haxx.space>
Score parsed VDES payloads and fall back to unsynced output for obviously weak or invalid decodes, including invalid geo boxes.
Co-authored-by: Stan Grams <sjg@haxx.space>
Signed-off-by: Stan Grams <sjg@haxx.space>
Keep the current VDES thresholds but restore a maximum burst duration so continuously open detections still produce frames.
Co-authored-by: Stan Grams <sjg@haxx.space>
Signed-off-by: Stan Grams <sjg@haxx.space>
Restore the more permissive intermediate VDES burst thresholds while keeping the current detector reset and close behavior.
Co-authored-by: Stan Grams <sjg@haxx.space>
Signed-off-by: Stan Grams <sjg@haxx.space>
Publish decoded VDES positions into the map and revert the VDES burst detector to its original gating thresholds.
Co-authored-by: Stan Grams <sjg@haxx.space>
Signed-off-by: Stan Grams <sjg@haxx.space>
Raise the VDES burst thresholds and minimum burst length to cut false positives after the recent sensitivity increase.
Co-authored-by: Stan Grams <sjg@haxx.space>
Signed-off-by: Stan Grams <sjg@haxx.space>
Finish the pending MARINE frontend and decoder activation wiring, and lower the VDES detector power floors so weak signals are eligible for burst detection in the same power domain used by the IQ path.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Lower the VDES burst detector thresholds further, shorten the minimum burst and end timing, and add a max-burst timeout so weak or continuous signals are more likely to finalize into diagnostic frames instead of staying invisible.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Reduce the VDES burst detector trigger and sustain thresholds so weaker received bursts enter and finalize more readily instead of being ignored by the IQ-side detector.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Add an FT8 live overlay bar, align APRS top controls with the other decoder tabs, advertise MARINE in the SoapySDR mode list, and make the VDES decoder emit raw unsynced diagnostic frames instead of dropping weak bursts outright.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Keep weak VDES bursts visible by emitting unsynced diagnostic frames instead of dropping them, remove receiver badges from FT8 and WSPR history rows, and carry the current MARINE composite-mode scaffolding through the shared mode enums and backend mappings.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Add a first VDES payload parser on top of the decoded bitstream so the server surfaces message labels, source and destination IDs, session IDs, ASM IDs, ack fields, geographic hints, and payload previews. Update the VDES frontend pane to render those parsed fields in the history and live bar.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Improve the VDES decoder with sync/rotation metadata and a first hard-decision rate-1/2 Viterbi stage after deinterleaving, then surface the extra lock state in the VDES frontend. Also fix the strict clippy findings in AIS, frontend bookmarks, and the server audio stack signature.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Add a dedicated VDES plugin tab and live bar, stop reusing the AIS vessel UI, and serve a separate VDES frontend script. Rework the SDR backend so VDES receives a single 100 kHz IQ tap, then replace the fake AIS-clone decoder path with an early M.2092-1 oriented complex-baseband scaffold using burst detection, coarse pi/4-QPSK slicing, and TER-MCS-1.100 frame heuristics.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Add a new trx-vdes decoder path alongside AIS, wire VDES through the server/frontend decode pipeline, and fix the web map so AIS vessel symbols load correctly and the TRX receiver marker appears when location data arrives.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Bring back the transition-locked AIS sampler with adaptive symbol timing and shaped discriminator filtering while keeping the shorter-frame acceptance path.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Revert the AIS decoder to the simpler sampling path while keeping the valid frame-length fix, and correct frontend frequency-range validation so SDR uses all reported bands and shows an explicit popup when tuning is unsupported.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Improve the AIS decoder timing recovery, add AIS vessel linking and map trails, and make the AIS/APRS decoder panels behave like mode-bound views with full-height history panes.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add dual-channel AIS decode support across the SoapySDR backend, server decode pipeline, and frontend plugins, including the new AIS tab, live bar, and map filtering.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Attach replay timestamps to APRS history and filter the APRS live bar
so it only shows the last hour of valid entries.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Split the SoapySDR backend demod and dsp code into focused modules while keeping behavior stable, and include the resulting formatting updates.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Clean up the workspace so cargo clippy passes across all targets and features.
Co-authored-by: OpenAI Codex <codex@openai.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>