feat(modem): APRS message-services picker on the Recipient field (#3569)#3828
Conversation
…rsdr#3569) Add a small ▾ caret button next to the APRS "To" field that addresses the well-known APRS gateway services without operators having to memorize their callsigns and message-text syntax. Selecting an entry sets the destination callsign, seeds the message text with the correct command template (cursor after the prefix), and prints a one-line hint with the per-service ~67-char limit to the system log. Services (grouped by category in the menu): - SMS — SMSGTE ("@<10-digit#> <msg>") - Email — EMAIL-2 ("<email> <msg>") - Winlink — WLNK-1 help ("?") and one-line email/SMS ("SMS <to> <msg>") - Weather — WXBOT ("City,ST" / "KICAO" / grid / blank) - Satellite/ISS — routing-only: shows an ARISS/RS0ISS path hint, does NOT mutate the To field or the TX path (less surprising to undo, per the issue's path-coupling note) All changes are local to Ax25HfPacketDecodeDialog; no encoder/protocol change. The send path keeps NOT uppercasing the body, so case-sensitive EMAIL-2 addresses and SMSGTE aliases survive intact. Closes aethersdr#3569.
…thersdr#3569) The original SMSGTE service has been temporarily shut down over spam, and the current global APRS↔SMS gateway is NA7Q's, addressed to "SMS" (body format is unchanged: "@<number> <message>"; aliases via "#alias #add <name> <number>"). This is exactly the "confirm current callsigns/SSIDs at ship time" check the issue called for. Bridge-verified: empty → To=SMS / text=@, crash-free.
There was a problem hiding this comment.
Thanks for this, @jensenpat — nicely scoped (two files, no protocol/encoder changes), the routing-only ISS handling and the "don't uppercase the body" reasoning are well thought through, and the comments documenting the aprsService… objectName choice and the InstantPopup/bridge tradeoff are genuinely helpful for the next reader. One real bug though:
QString::fromLatin1 on UTF-8 literals → mojibake (blocking)
The kAprsServices table fields are const char* literals in a UTF-8 source file, but they're decoded with QString::fromLatin1, which maps each byte 1:1 onto U+0000–00FF. Any multi-byte UTF-8 character will therefore render as garbage:
- Every menu label uses an em dash
—(U+2014, UTF-8E2 80 94) — e.g."SMS — text an SMS to a phone". ViafromLatin1this displays asSMS â€" text an SMS to a phonein the dropdown. - Hints contain
→(ICAO→METAR),≤and°(≤~60 chars,passes >30°), which corrupt in both the tooltip and theappendSystemLinesystem-log output.
QAction* act = serviceMenu->addAction(QString::fromLatin1(e.label));
act->setToolTip(QString::fromLatin1(e.hint));
...
const QString hint = QString::fromLatin1(e.hint);Fix: use QString::fromUtf8 for label, section, and hint (the addressee/body fields are pure ASCII so they're unaffected, but switching them too keeps it uniform).
Worth noting this is exactly the class of bug the bridge proof couldn't catch: the verification triggers actions by objectName and reads back the To/message fields (all ASCII), so the corrupted label and hint text never showed up in the before→after dumps.
Otherwise looks good
- Menu ownership is correct —
serviceMenuis parented tomsgFrameandQToolButton::setMenudoesn't take ownership, so no leak/double-free. seedAprsServicenull-safety is fine (members are built before the menu can be triggered), the routing-only early-out correctly avoids clobbering an in-progress compose, andsetCursorPosition(body.length())is safe for the empty-body entries.appendSystemLinealready guards against a nullm_log.
Just the encoding fix and this is good to go.
🤖 aethersdr-agent · cost: $4.3274 · model: claude-opus-4-8
ten9876
left a comment
There was a problem hiding this comment.
Clean, correct, low-risk APRS convenience feature. Verified the two correctness-critical items: seedAprsService gates all field mutation on if (!addressee.isEmpty()), so the ISS routing-only entry leaves To/message untouched (matching the maintainer triage); and it does a plain setText(body) with no uppercasing, so case-sensitive EMAIL-2/templates survive (the send path only uppercases the addressee). Good details — cursor lands after the seeded prefix, no TX surface (seeds + logs a hint, never sends), and the pure-InstantPopup with no synthetic-click affordance deliberately avoids the macOS Cocoa re-entrancy segfault while menu objectNames let the bridge verify seeding via closed-menu trigger. The bridge gaps it surfaced (unanchored cwx deny-word, QToolButton popup driving) are real and being consolidated into a follow-up. Thanks @jensenpat.
What & why
Closes #3569. Adds an APRS message-services picker — a small ▾ caret button
next to the "To" field on the AetherModem APRS tab. It addresses the
well-known APRS gateway/auto-responder services without operators having to
memorize their callsigns and message-text syntax. These services already work
by manually typing the right destination + body; the picker just makes them one
tap away (the discoverability/usability win the issue asks for).
Selecting an entry:
cursor right after the prefix, and
Services (grouped by category)
SMS@@<10-digit#> <msg>; replies return as APRS (SMSGTE is shut down)EMAIL-2<email> <msg>; register by sending your address onceWLNK-1?WLNK-1SMSSMS <email-or-call> <msg>WXBOTCity,ST/KICAO/ grid / blank = last positionPer the maintainer triage on the issue, the ISS/satellite entry deliberately
does not mutate the To field or the TX
PATH— changing the path is moreintrusive and easy to forget to revert — it only surfaces a hint.
Scope
All changes are local to
Ax25HfPacketDecodeDialog(.cpp+.h); noencoder/protocol change. The send path keeps not uppercasing the message
body (
sendAprsMessageFromUionly uppercases the addressee), so case-sensitiveEMAIL-2addresses andSMSGTEaliases survive intact — the service templatesrely on that.
How the agent automation bridge proved it
Built
RelWithDebInfo, launched withAETHER_AUTOMATION=1, and drove thepicker entries by
objectName(RX-only — no TX; MODEM stayed in Standby).The bridge triggers each menu action and reads the resulting
To/ messagefields back via
dumpTree. Before→after, all crash-free on one runninginstance:
Hints reached the system log (
lcAx25), e.g.:WLNK-1 APRSLink: send "?" for the command list (L, R#, SP, SMS, …)andISS/ARISS digi (145.825 MHz): set TX PATH to "ARISS" (RS0ISS)….Screenshot of the new ▾ picker in the APRS MESSAGE row (fields seeded from
the bridge; MODEM Standby):
Agent automation bridge — two gaps found (and worked around)
Dogfooding the bridge surfaced two real issues; the feature is shipped to dodge
both, and they're worth fixing in the bridge so the next feature doesn't have to:
TX-guard deny-word
cwxis an unanchored substring match. The guardflagged a weather (RX-only) menu entry as transmit-keying because the
abbreviated objectName
aprsSvcWXBOT.toLower() =aprssvcwxbotcontains the trigram
cwx(thecfromsvc+wxfromwxbot).Workaround: objectNames use the spelled-out
aprsService…prefix.Proposed fix: anchor the
cwx/mox/ptt/transmitdeny-words to wordboundaries (or require an explicit TX marker) instead of bare
contains().The bridge can't safely open a
QToolButtonpopup menu on macOS. Aprogrammatic
click()runs synchronously inside the socket-read callback;showing the menu's native popup window from there re-enters the Cocoa GUI
stack and segfaults intermittently (
QMenu::popup → QCocoaWindow::setVisible → QWindow::geometry). Deferring the popup withsingleShot(0)did not help— showing the native window is the hazard, not the call site.
Workaround: ship pure
InstantPopup(human press uses Qt's built-in path)with no synthetic-click affordance; the bridge still verifies seeding by
triggering the menu actions while the menu is closed
(
triggerMenuActionthen skips the popup path and just callsaction->trigger()— crash-free, which is exactly how the proof above runs).Proposed fix: an
openMenu/showMenuverb for button popups that poststhe show onto the GUI event loop, and/or defer widget
click()like QActiontriggers are already deferred.
💻 Generated with Claude Code (Opus 4.8) with architecture by @jensenpat