Skip to content

[modem] AetherModem: Phase 0 bring-up#2753

Merged
ten9876 merged 3 commits into
mainfrom
aether/ax25-hf-packet-decode
May 16, 2026
Merged

[modem] AetherModem: Phase 0 bring-up#2753
ten9876 merged 3 commits into
mainfrom
aether/ax25-hf-packet-decode

Conversation

@jensenpat

@jensenpat jensenpat commented May 16, 2026

Copy link
Copy Markdown
Collaborator
Screenshot 2026-05-16 at 12 56 03 PM

Summary

This adds a Phase 0 receive-only vertical slice for native AX.25 packet decoding in AetherSDR. The feature is intentionally scoped to an experimental packet decoder window and a small internal libmodem-core subset/shim. It does not add TX, KISS, APRS-IS, maps, beaconing, digipeating, connected mode, Smart Beaconing or a new audio backend.

The decoder can be opened from View as Packet Decoder..., under Propagation Conditions. It attaches to the active AetherSDR RX slice/audio stream, taps the existing 24 kHz post-demod PC RX audio non-destructively, and displays accepted AX.25 UI frames in an AetherModem-style terminal/log window.

What Changed

  • Added an isolated third_party/libmodem_core subset with MIT license preservation and AetherSDR refresh notes.
  • Added AetherAx25LibmodemShim, which owns the modem config, demodulator, HDLC/AX.25 bitstream decode path, diagnostics, and Aether-owned frame conversion.
  • Added Aether-owned decoded frame and display formatting support for source, destination, digipeater path, control/PID, UI detection, payload text/hex, FCS acceptance, and quality/confidence.
  • Added an experimental AetherModem - Packet Decoder window with 300/1200 baud radio buttons, polarity persistence, enable/clear controls, hidden future Terminal/Mailbox/send scaffolding, a packet activity graph, and a persisted click-to-toggle diagnostics debug mode.
  • Added app-log category support for packet diagnostics instead of writing ad hoc /tmp logs.
  • Added bounded receive diagnostics: sample rate, RMS/peak/clipping, tone power, receive gate state, HDLC candidates, accepted frames, rejects, FCS failures, malformed candidates, and last reject preview.
  • Added a 30-second float32 WAV capture helper in the packet window for field debugging.
  • Added tests for frame formatting, callsign/SSID/path display, UI detection, payload text/hex handling, known-good frame decode, bad-FCS rejection, polarity behavior, synthetic loopback, and capture replay support.

Third-Party Scope

Copied into third_party/libmodem_core:

  • LICENSE.libmodem
  • README.aethersdr.md
  • bitstream.h
  • bitstream.cpp
  • demodulator.h
  • demodulator.cpp

Intentionally avoided:

  • libmodem application/pipeline/audio/network/PTT/TUI/config/test layers
  • Boost, fmt, ftxui, nlohmann_json, ALSA, WASAPI, CoreAudio, libsndfile, cxxopts, Python, scipy, numpy, Dire Wolf, linuxax25/libax25, and libcorrect
  • FX.25 / Reed-Solomon / IL2P paths
  • TX modulation UI or runtime TX path

Radio Settings Used During Field Test

The first successful over-the-air decodes were observed with Dire Wolf transmitting APRS AX.25 AFSK at 1600/1800 Hz, A+, 300 baud.

Recommended starting receive setup for this branch:

  • Mode: USB for the current testing path. DIGU can work conceptually, but USB made the slice squelch and post-demod path easier to reason about during the field tests.
  • Frequency: tune TX and RX to the same dial frequency for the local test path used here, for example 14.241.000.
  • Filter: approximately 3 kHz, with an audio passband covering 1600 Hz and 1800 Hz.
  • Packet Decoder profile: 300 baud.
  • Tone polarity: start with Normal for Dire Wolf AFSK 1600 & 1800 Hz, A+. Use Reverse if sideband/audio inversion is suspected.
  • AGC mode: Slow worked well in testing.
  • AGC-T: around 95 was the successful field-test setting.
  • AF level: around 70 was the successful field-test setting.
  • RF-side gain: keep the receiver below clipping and avoid using RF gain as the primary packet-level control unless front-end overload/noise floor behavior requires it.
  • Target decoder audio: observed successful captures around -22.6 dBFS RMS, -10.5 dBFS peak, 0.00% clipping.
  • Squelch: off. The decoder uses its own receive gate and should not depend on slice squelch to pass packet audio.

Field Test Notes

Successful live decodes were observed in the packet window from the fixed Dire Wolf packet:

KI6BCJ-1>APDW18:!3644.00N\11947.00W-KI6BCJ HF APRS test via Direwolf 300 baud

The latest 30-second capture replay decoded one valid frame on Normal polarity and zero on Reverse polarity. Bad-FCS candidates were not displayed as valid frames.

Observed diagnostics at the end of one live run were intentionally noisy but useful: hdlc=842, ok=8, reject=834, badFcs=28, malformed=375. That means the first vertical slice is successfully accepting real frames while still producing many rejected HDLC candidates from noisy HF audio.

Current Limitations

  • RX only. No TX, no KISS server, no APRS parser/map, no APRS-IS, no beaconing, no BBS/connect mode.
  • The 1200 baud VHF button is UI scaffolding for a future profile; 300 baud HF is the implemented receive path.
  • The decoder assumes the AetherSDR RX audio tap provides 24 kHz post-demod audio.
  • Demod/HDLC timing is still a first-pass implementation and is not yet production-grade for weak/noisy HF packet.
  • The receive gate currently improves candidate reset behavior, but HDLC candidate detection is still permissive and produces expected noisy rejects.
  • Hidden Terminal/Mailbox/send controls are preserved as inactive scaffolding for later UX plumbing.

Next Steps

  • Improve RX robustness by tightening HDLC candidate gating and timing recovery.
  • Tune receive gate open/close behavior so packet bursts reset decoder state without holding too much noise.
  • Add better symbol clock/tone confidence handling for marginal HF signals.
  • Improve candidate rejection before FCS so malformed/noisy frames produce less diagnostic churn.
  • Add a visible debug indicator/UX polish around diagnostics capture if field testing shows the graph click affordance is too subtle.
  • Promote VHF 1200 from UI scaffold to a real profile only after the HF path stabilizes.
  • Keep TX, KISS, APRS-IS, maps, and beaconing as separate follow-on work.

Validation

  • Merged latest upstream main into this branch before PR creation.
  • Built app target after merge:
    • cmake --build build --target AetherSDR -j22
  • Built all targets after upstream added new tests:
    • cmake --build build -j22
  • Ran full merged test suite:
    • ctest --test-dir build --output-on-failure -j22
    • Result: 13/13 tests passed.

👨🏼‍💻 Generated with OpenAI Codex (GPT-5.5 Pro 4/23) and tested by @jensenpat

jensenpat and others added 3 commits May 15, 2026 21:31
@jensenpat jensenpat changed the title Add experimental AX.25 packet decoder window [modem] AetherModem: Phase 0 bring-up May 16, 2026
@jensenpat jensenpat marked this pull request as ready for review May 16, 2026 20:12
@ten9876 ten9876 merged commit e0618ee into main May 16, 2026
5 checks passed
@ten9876 ten9876 deleted the aether/ax25-hf-packet-decode branch May 16, 2026 22:31
@ten9876

ten9876 commented May 16, 2026

Copy link
Copy Markdown
Collaborator

Claude here — landed at 22:31 UTC. Massive contribution, jensenpat — thank you. 🎉

Phase-0 scoping is the right call: RX-only, single 300 baud HF profile, vertical slice through the audio tap → demod → HDLC → AX.25 → UI pipeline. The non-destructive audio tap pattern (m_tncRxTapEnabled atomic, gated emitTncRxTapFromFloat32Stereo) mirrors the existing m_cwDecodeTxTapEnabled shape exactly, so this slots into AudioEngine without disturbing anything else. The LIBMODEM_NAMESPACE=aether_libmodem_core isolation of the third-party static lib was also exactly right.

13/13 tests passing including the synthetic loopback + the real over-the-air KI6BCJ-1>APDW18 capture replay is a strong evidence base for a Phase-0 landing.

Filed two minor follow-ups (cosmetic only, not blockers):

  • lcAx25 / lcAetherAx25Shim consolidate to one Q_LOGGING_CATEGORY declaration
  • Surface an "Experimental — Phase 0 / RX only" indicator in the dialog title or banner so the "first-pass demod" caveat from the PR description is visible to users without reading the issue

Looking forward to Phase 1 (improved HDLC gating + 1200 baud VHF profile + the next set of items from your Next Steps list).

73, Jeremy KK7GWY & Claude (AI dev partner)

aethersdr-agent Bot added a commit that referenced this pull request May 16, 2026
). Principle III.

PR #2753 introduced two local Q_LOGGING_CATEGORY("aether.ax25") declarations
alongside the centralized lcAx25 in LogManager. Both lcAetherAx25Shim and
lcAetherAx25Dialog resolved to the same underlying QLoggingCategory at
runtime, so behavior was identical, but the duplication sat outside the
LogManager registry and would silently drift if the central category's
metadata (default level, toggle hook) ever changed.

Replace both local declarations with #include "core/LogManager.h" and
update all qCDebug callsites to use lcAx25 directly. The <QLoggingCategory>
include is dropped from both files since LogManager.h pulls it in.

Logging behavior is unchanged: same messages at same levels, same single
"aether.ax25" toggle in Help → Support — which is the user-visible label
this consolidation protects (Principle III: user-facing names match the
visible UI labels, and the Help → Support toggle is the authoritative
surface for this category).

Blast radius: risk_score=0.228, 7 high-risk affected (top: MainWindow,
MainWindow::wirePanadapter, MainWindow::registerShortcutActions). The
dialog is structurally central to MainWindow, but the edit only swaps
include directives and renames a local logging-category symbol — the
dialog's public API (constructor, signals, slots) is untouched.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants