Summary
Add an APRS Digipeater tab to the AetherModem TNC (Ax25HfPacketDecodeDialog). The codebase is already wired for it — several files carry // for the future APRS/AX.25 digipeater comments and the AX.25 model already tracks the has-been-repeated (H) bit and the via path. This issue scopes a minimum-viable WIDE1-1 fill-in digipeater first, then a full wide-area digi.
Follow-up to #3530 (AetherModem APRS client).
Background — what a digipeater does (researched + source-verified)
A digipeater receives an AX.25 UI frame, inspects its via path, and if addressed to an alias it handles, retransmits a modified copy. The modern New-N paradigm (Bob Bruninga, aprs.org/newN). Note the APRS Protocol Reference 1.01 deliberately does not specify digipeater behavior, which is why historical implementations diverged — so we follow the New-N docs + reference implementations, not the base spec.
Core algorithm (New-N), for a WIDEn-N via field
N is the SSID in the high bits of the last AX.25 address octet.
- N ≥ 2 → decrement N; insert MYCALL ahead of the decremented entry (subject to the 8-address AX.25 limit); set the H bit only on the inserted MYCALL, leaving
WIDEn-(N-1) unmarked so downstream digis keep relaying.
- N = 1 → replace the field with MYCALL and set its H bit (avoids a useless
WIDEn-0).
- N = 0 → hop count exhausted → do not digipeat.
Two alias flavors:
- TRACE (
WIDEn-N): substitutes/inserts the transmitter callsign + sets H → full path traceability.
- non-trace / FLOOD (state/section
SSn-N): decrements but does not insert a callsign.
MVP vs full-featured
|
MVP — fill-in / home digi |
Full wide-area digi |
| Responds to |
WIDE1-1 + own MYCALL only |
+ WIDEn-N (trace) + SSn-N state/section (flood) |
| Behavior |
one hop out of a dead zone, MYCALL implied, digipeat once |
full decrement/insert + N-trapping |
| Dire Wolf analog |
DIGIPEAT 0 0 ^WIDE1-1$ ^WIDE1-1$ |
DIGIPEAT + ^WIDE[3-7]-[1-7]$ trap |
MVP target = WIDE1-1 fill-in. Genuinely useful, low network risk, exercises the full RX→modify→TX path.
Mandatory correctness features (required even for MVP)
- Duplicate suppression — compare
source-SSID + destination + info payload, ignoring the via path, over a ~30 s window (Dire Wolf DEDUPE 30, KPC-3+ UIDUPE 30). Not optional — RELAY is obsolete precisely because it lacked dupe-elimination. (Caveat: 30 s is the convention but imperfect; delayed packets can exceed it.)
- Hop exhaustion — never retransmit a field at
N=0.
- H-bit correctness — set H on the inserted/substituted MYCALL; never re-process a via field already marked H.
Network-friendliness features (full version / config knobs)
- N-trapping — high-N paths (e.g.
WIDE3-7) repeated once (replaced by MYCALL, no decrement) to kill abusive long paths. Trap boundary is a tunable, commonly N ≥ 3.
- Viscous delay (aprx, 0–9 s; fill-in technique) — hold the packet; if the dupe detector sees another copy before the delay expires, discard (a stronger digi already covered it). Recommend 0 for normal digi, 5 for fill-in.
- No stagger (UIDWAIT off) — let multiple digis transmit simultaneously ("fratricide"); this is the primary channel-load minimizer, not a bug.
- Direct-only mode (aprx
directonly) — fill-in repeats only directly-heard (not already-digipeated) packets.
- Tiered self-beacon — direct posit every 10 min, 1-hop every 30 min, 2-hop every 60 min. Disable any non-APRS HID/ID packet.
Mapping onto AetherModem (architecture is ready)
- AX.25 model already has what we need:
ax25::Address.hasBeenRepeated (H bit) + ssid, and ax25::Frame.via — src/core/tnc/Ax25.h:22 / :62. Encode/decode preserve H bits (Ax25.cpp:53, :124).
- RX hook:
AetherAx25LibmodemShim::frameDecoded → distributed in Ax25HfPacketDecodeDialog.cpp:889. A new AprsDigipeater slot taps the same signal alongside PMS/terminal/MQTT.
- TX hook: emit
transmitFrame(QByteArray rawNoFcs) → shared KISS TX queue (m_kissTxQueue, depth-capped) → buildTransmitAudioFromFrame(). Same contract AprsMessenger uses (AprsMessenger.cpp:168).
- Dedupe helper exists:
AprsStationList already does ~30 s source+payload suppression — reuse/generalize it.
- Tab pattern: add a 5th button to
tabGroup + a buildDigipeaterPage() into m_tabStack (Ax25HfPacketDecodeDialog.cpp:670–755).
- Settings: new
DigipeaterSettings under key "AetherModemDigipeater", mirroring AprsSettings (JSON blob).
Proposed scope
Phase 1 — MVP (this issue)
- New
class AprsDigipeater : public QObject — RX → decode → match alias → modify path → re-TX.
- WIDE1-1 + MYCALL fill-in only; digipeat-once semantics.
- 30 s dedupe (source+dest+payload, ignore path); hop-exhaustion + H-bit correctness.
- New Digipeater tab: enable toggle, MYCALL field, live heard/repeated/dropped counters, recent-repeat log.
DigipeaterSettings persistence ("AetherModemDigipeater").
- Safety: no RF TX without explicit enable and a visible "Digi TX active" indicator. Default off.
Phase 2 — wide-area + polish
WIDEn-N trace + SSn-N flood handling.
- N-trapping (configurable boundary), viscous delay, direct-only mode.
- Tiered self-beacon (10/30/60 min) + ensure no non-APRS HID.
- Per-station digi statistics / map integration.
Open questions
- Preemptive digipeating (Dire Wolf
PREEMPT OFF/DROP/MARK/TRACE) — defer to Phase 2+? When is it network-friendly vs abusive?
- Combined Tx-iGate + digi gating rules (third-party framing, avoid double-injecting RF back to APRS-IS) — out of scope here, but note the interaction.
- HF vs VHF: digipeating convention is a VHF (1200 baud) practice; should the tab gate on the modem profile (
Ax25DemodConfig) to avoid encouraging HF 300-baud digipeating?
References
Design researched via fan-out web search with 3-vote adversarial verification (24/25 claims confirmed). The one refuted claim: WIDEn-N dedup is per-digi, not a network-wide single copy — fratricide intentionally produces multiple simultaneous transmissions.
Summary
Add an APRS Digipeater tab to the AetherModem TNC (
Ax25HfPacketDecodeDialog). The codebase is already wired for it — several files carry// for the future APRS/AX.25 digipeatercomments and the AX.25 model already tracks the has-been-repeated (H) bit and the via path. This issue scopes a minimum-viable WIDE1-1 fill-in digipeater first, then a full wide-area digi.Follow-up to #3530 (AetherModem APRS client).
Background — what a digipeater does (researched + source-verified)
A digipeater receives an AX.25 UI frame, inspects its via path, and if addressed to an alias it handles, retransmits a modified copy. The modern New-N paradigm (Bob Bruninga, aprs.org/newN). Note the APRS Protocol Reference 1.01 deliberately does not specify digipeater behavior, which is why historical implementations diverged — so we follow the New-N docs + reference implementations, not the base spec.
Core algorithm (New-N), for a
WIDEn-Nvia fieldN is the SSID in the high bits of the last AX.25 address octet.
WIDEn-(N-1)unmarked so downstream digis keep relaying.WIDEn-0).Two alias flavors:
WIDEn-N): substitutes/inserts the transmitter callsign + sets H → full path traceability.SSn-N): decrements but does not insert a callsign.MVP vs full-featured
WIDE1-1+ ownMYCALLonlyWIDEn-N(trace) +SSn-Nstate/section (flood)DIGIPEAT 0 0 ^WIDE1-1$ ^WIDE1-1$DIGIPEAT+^WIDE[3-7]-[1-7]$trapMVP target = WIDE1-1 fill-in. Genuinely useful, low network risk, exercises the full RX→modify→TX path.
Mandatory correctness features (required even for MVP)
source-SSID + destination + info payload, ignoring the via path, over a ~30 s window (Dire WolfDEDUPE 30, KPC-3+UIDUPE 30). Not optional —RELAYis obsolete precisely because it lacked dupe-elimination. (Caveat: 30 s is the convention but imperfect; delayed packets can exceed it.)N=0.Network-friendliness features (full version / config knobs)
WIDE3-7) repeated once (replaced by MYCALL, no decrement) to kill abusive long paths. Trap boundary is a tunable, commonly N ≥ 3.directonly) — fill-in repeats only directly-heard (not already-digipeated) packets.Mapping onto AetherModem (architecture is ready)
ax25::Address.hasBeenRepeated(H bit) +ssid, andax25::Frame.via—src/core/tnc/Ax25.h:22/:62. Encode/decode preserve H bits (Ax25.cpp:53,:124).AetherAx25LibmodemShim::frameDecoded→ distributed inAx25HfPacketDecodeDialog.cpp:889. A newAprsDigipeaterslot taps the same signal alongside PMS/terminal/MQTT.transmitFrame(QByteArray rawNoFcs)→ shared KISS TX queue (m_kissTxQueue, depth-capped) →buildTransmitAudioFromFrame(). Same contractAprsMessengeruses (AprsMessenger.cpp:168).AprsStationListalready does ~30 s source+payload suppression — reuse/generalize it.tabGroup+ abuildDigipeaterPage()intom_tabStack(Ax25HfPacketDecodeDialog.cpp:670–755).DigipeaterSettingsunder key"AetherModemDigipeater", mirroringAprsSettings(JSON blob).Proposed scope
Phase 1 — MVP (this issue)
class AprsDigipeater : public QObject— RX → decode → match alias → modify path → re-TX.DigipeaterSettingspersistence ("AetherModemDigipeater").Phase 2 — wide-area + polish
WIDEn-Ntrace +SSn-Nflood handling.Open questions
PREEMPT OFF/DROP/MARK/TRACE) — defer to Phase 2+? When is it network-friendly vs abusive?Ax25DemodConfig) to avoid encouraging HF 300-baud digipeating?References
Design researched via fan-out web search with 3-vote adversarial verification (24/25 claims confirmed). The one refuted claim: WIDEn-N dedup is per-digi, not a network-wide single copy — fratricide intentionally produces multiple simultaneous transmissions.