Add FreeDV Reporter station reporting with RADE integration#2173
Add FreeDV Reporter station reporting with RADE integration#2173ten9876 merged 2 commits intoten9876:mainfrom
Conversation
There was a problem hiding this comment.
Review: FreeDV Reporter station reporting with RADE integration
Nice contribution, @NF0T — this is a well-scoped feature with a thorough PR description and solid test plan. The architecture of routing SNR/sync signals through MainWindow to the FreeDV worker thread is the right approach. A few items to consider:
Design / correctness
-
m_authNeedsRefreshreconnect race — InonWsDisconnected(), the newm_authNeedsRefreshpath callsm_ws->open()directly without going through the reconnect timer. If the server rejects the new auth or the open fails synchronously,onWsDisconnectedwould fire again withm_authNeedsRefresh == false, falling into the exponential backoff path. This is fine for resilience, but it means the user's toggle won't have immediate effect — worth a debug log at minimum. Also, ifenableReporting()is called twice rapidly before the first reconnect completes,m_authNeedsRefreshcould be set again while the socket is mid-close. Consider guarding against that (e.g. checkingm_ws->state()before callingclose()inenableReporting()). -
disableReporting()sends events then immediately closes —sendEvent("message_update", ...)andsendEvent("freq_change", {freq: 0})are followed bym_ws->close(). SinceQWebSocket::sendTextMessage()buffers to the OS, this should flush before the close handshake, but it's worth noting this relies on the WebSocket close handshake not racing the sends. In practice this is fine with Qt's implementation, but a comment documenting the assumption would help future readers. -
rx_reportwith empty callsign — TheonSnrTimer()sendsrx_reportwithcallsign: "". The PR description notes the server may silently drop these. If the server does process them, this could create phantom entries in the reporter's "heard" table. Consider either not sendingrx_reportat all until callsign identification is available, or gating it behind a config flag. The infrastructure being "in place" is good forward thinking, but sending empty data to a production server is a risk. -
hasGpsHardware()helper — The diff adds this toRadioModel.hbut I don't see it in the current base. It follows the existing model-detection pattern (m_model.contains(...)) which is good. However, the PR description mentions "FLEX-8000 class and Aurora" — make sure the string matching covers all 8000-series variants (8400, 8600) and the Aurora model string correctly. The existing code uses patterns likem_model.contains("8600")— does the 8400 have GPS? If not, the helper namehasGpsHardwareshould be documented to clarify which models qualify.
Thread safety
-
updateRxSnr()/updateRxSynced()called cross-thread — These are called fromRADEEnginesignals (worker thread) and store tom_lastSnr/m_radeSyncedwhich are read byonSnrTimer()(FreeDV worker thread). The PR description mentionsQMetaObject::invokeMethodfor thread safety inMainWindow, but theFreeDvClientmembersm_lastSnr(float) andm_radeSynced(bool) are written from one thread and read from another without synchronization. On x86 this is benign for aligned floats/bools, but it's technically a data race. Consider either:- Making these
std::atomic<float>/std::atomic<bool>, or - Ensuring the signals are connected with
Qt::QueuedConnectionso writes happen on the FreeDV thread
If the connections from
RADEEngine→FreeDvClientare queued (both live on different threads with event loops), this is already safe. Just want to confirm that's the case. - Making these
Scope
.gitignorechange — Adding.vscode/c_cpp_properties.jsonis fine but tangential to this PR. Not blocking, just noting it's unrelated to the feature.
Minor
-
SNR timer interval — The 1-second SNR reporting interval (
m_snrTimer->setInterval(1000)) is reasonable, but the FreeDV Reporter server may have its own rate limits. The PR description mentions a 5-minute keepalive forstation_update— is the 1-secondrx_reportrate documented as acceptable by the server? If the server throttles or drops high-rate reports, the timer could be bumped to 5s without losing much value. -
deactivateRADE()"bug fix" — The PR claimsm_radeSliceIdwas zeroed before teardown read it, but looking at the existing code, nothing afterm_radeSliceId = -1(line 10691) references the slice ID — the remaining teardown usesm_radeEnginedirectly. The fix is correct in the context of the newstopFreeDvReporting()code that needs the slice ID, but characterizing it as a "pre-existing bug" is slightly misleading. It's more accurately "capture slice ID before the block that resets it, since we now need it for reporting teardown."
Overall
This is solid work. The opt-in design, AppSettings persistence, and GPS/callsign sync from the radio are all well thought out. The thread-safety question (item 5) is the most important to resolve before merge. Everything else is minor or informational.
Thanks for the contribution! 73
When RADE is active and the user opts in, AetherSDR now registers as a full participant on the FreeDV Reporter network (qso.freedv.org) rather than connecting as a read-only spot viewer. Core (FreeDvClient): - enableReporting(callsign, grid, message, softwareVersion, freqMhz) / disableReporting() control the reporting lifecycle - Socket.IO handshake includes required role/rx_only/os fields - station_update sent on connect, frequency change, and 5-min keepalive - freedv_update (SNR + sync) sent in real-time from RADEEngine signals - TX state reported via reportTxState(); guards against tune/ATU false positives by checking isTuning() and ATUStatus::InProgress first - Toggling reporting triggers transparent reconnect via m_authNeedsRefresh Core (RADEEngine): - Exposes snrChanged and syncChanged signals for MainWindow routing Models (RadioModel): - hasGpsHardware() detects FLEX-8000 class and Aurora (GPS-capable models) GUI (FreeDV tab — Station Reporting group box): - Enable checkbox: opt-in, off by default; immediately switches role on toggle without visible reconnect delay - Callsign field with "Use radio" toggle (syncs live from RadioModel) - Grid Square field with "Use GPS" toggle (only shown on GPS-capable hardware) - Station Message field (blank by default) - All fields persist via AppSettings; new values apply on next enable cycle MainWindow: - startFreeDvReporting/stopFreeDvReporting helpers replace inline blocks - freedvReportingToggled signal wired for immediate checkbox response - Fix: deactivateRADE() captured m_radeSliceId after zeroing it (always got null slice on teardown) Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
99c33d9 to
644eac0
Compare
|
Thanks for the detailed review — responses inline: #1 — auth reconnect race — Fixed. Both #2 — Actually on reflection, the sends are fire-and-forget buffers and Qt's close handshake won't interrupt in-flight frames — no code change needed, but noted. #3 — #4 — #5 — thread safety for #6 — #7 — SNR timer interval — As noted in #3, #8 — deactivateRADE() characterization — Fair point. The original teardown code didn't use |
…gitignore
Two review-feedback cleanups:
1. Refuse to enable reporting when callsign or grid is empty.
Original code fell back to QStringLiteral("N0CALL") / QStringLiteral("AA00")
when the user-entered fields were blank. Sending bogus placeholder
stations to the public qso.freedv.org reporter map is bad community
citizenship — users who haven't filled in their station info would
pollute the shared resource just by toggling the checkbox.
Two-layer fix:
- DxClusterDialog::buildFreeDvTab — when the user toggles the
Enable checkbox to ON, resolve the effective callsign/grid the
same way startFreeDvReporting() does (radio/GPS first, fields
second). If either is empty, show a QMessageBox warning and
revert the checkbox via QSignalBlocker so the toggled() recursion
doesn't fire.
- MainWindow::startFreeDvReporting — defense in depth. Auto-
activation by RADE doesn't go through the toggle path, so guard
there too. Logs a qCWarning and returns early instead of
broadcasting placeholders.
2. Drop the .vscode/c_cpp_properties.json line from .gitignore — it
looked like personal IDE config sneaking in via the PR.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
The DxClusterDialog::freedvReportingToggled connect lambda references m_radeEngine, m_radeSliceId, startFreeDvReporting(), and stopFreeDvReporting() — all of which are only declared under #ifdef HAVE_RADE. The connect itself was wrapped in #ifdef HAVE_WEBSOCKETS only, so on Windows (WebSockets present, RADE absent) the build broke with C2065/C3861. Introduced in #2173 (FreeDV Reporter), but check-windows skipped that PR because it didn't touch CMakeLists.txt / third_party / workflows. This NAVTEX branch modifies CMakeLists.txt for the new test executable, so check-windows fired and surfaced the regression. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
* NAVTEX waveform support — data layer + protocol plumbing (#2132) Adds support for FlexLib v4.2.18's NAVTEX maritime broadcast waveform. Scope is data layer + protocol plumbing only, per #2132's explicit "visual design + UX is maintainer-only" instruction. Carved out of bot PR #2137; the standalone NavtexApplet UI class it included was unwired (no AppletPanel registration, no construction site in MainWindow, no activate/deactivate command path), so it would have been dead code in production. Leaves the UI work for a maintainer-driven follow-up when the activate/deactivate UX is decided. See PR #2137 review comment for the full rationale. What's included: - NavtexModel: parses `navtex` and `navtex sent` status messages, formats `navtex send tx_ident=... subject_indicator=... msg_text=...`, tracks per-message state (Pending → Queued → Sent / Error). Pending messages keyed by command sequence number; promoted to Queued via the reply handler then to Sent via the broadcast. - RadioModel wiring: subscribes to `sub navtex all` and routes status to NavtexModel. Forwards commandReady and replyCommandReady, with a reply handler that drives handleSendResponse. - SliceModel: NT mode classified as USB-family for filter/AGC purposes (matches FlexLib's slice-mode handling for digital data modes). - MainWindow / RxApplet / VfoWidget: NT mode threaded through the mode-string switch so the GUI doesn't crash or fall through to "unknown" when a slice enters mode NT. Improvements over the original PR: 1. Quote-escape `msg_text="..."` in NavtexModel::sendMessage. The original `arg(msgText)` substitution would break the radio's command parser if the user's message contained a `"` or `\`. Backslash is escaped first to avoid double-escaping existing backslashes. 2. Failure-path bug fix in handleSendResponse: failure responses come as `R<seq>|<errcode>|` with empty body, so the original "if indexStr empty bail" check fired BEFORE the "if respVal != 0 mark Error" branch — making error reporting unreachable. Fixed by reordering: check pending entry first, then respVal != 0, then index parsing. 3. New unit test `tests/navtex_model_test.cpp` (21 assertions) covering: Pending → Queued promotion, Queued → Sent promotion, send-failure path, orphan navtex-sent (Multi-Flex case), case- insensitive status string parsing, msg_text quote/backslash escaping, idempotent navtex-sent handling. Fixes #2132. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * Fix Windows build: guard freedvReportingToggled connect on HAVE_RADE The DxClusterDialog::freedvReportingToggled connect lambda references m_radeEngine, m_radeSliceId, startFreeDvReporting(), and stopFreeDvReporting() — all of which are only declared under #ifdef HAVE_RADE. The connect itself was wrapped in #ifdef HAVE_WEBSOCKETS only, so on Windows (WebSockets present, RADE absent) the build broke with C2065/C3861. Introduced in #2173 (FreeDV Reporter), but check-windows skipped that PR because it didn't touch CMakeLists.txt / third_party / workflows. This NAVTEX branch modifies CMakeLists.txt for the new test executable, so check-windows fired and surfaced the regression. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
Summary
Adds opt-in FreeDV Reporter station reporting that activates automatically while RADE is running, and provides a Station Reporting settings group in the FreeDV tab of the Spot Sources dialog to configure the station details sent to the reporter.
Previously AetherSDR connected to FreeDV Reporter in
role=view(read-only spots) only. This branch enablesrole=report, which registers the station and periodically broadcasts RX SNR, sync state, frequency, and TX status while RADE is active.Changes
Core —
FreeDvClientenableReporting(callsign, grid, message, softwareVersion, freqMhz)/disableReporting()methods control the reporting lifecyclerole,rx_only, andosfields required by the FreeDV Reporter serverstation_updateevent sent on connect, on frequency change, and on a 5-minute keepalive timerfreedv_update(SNR + sync state) sent in real-time fromRADEEnginesignalsreportTxState()— triggers afreedv_updateimmediately on MOX changeviewvsreport) is set at Socket.IO handshake time; toggling reporting triggers a transparent reconnect viam_authNeedsRefreshwithout visible backoff delayCore —
RADEEnginesnrChangedandsyncChangedsignals soMainWindowcan route them to the FreeDV clientModels —
RadioModelhasGpsHardware()inline helper that detects FLEX-8000 class and Aurora radios (the only models with GPS hardware); used to conditionally show the GPS grid optionGUI — Station Reporting group box (FreeDV tab)
New Station Reporting group between the connection controls and the Spots group, containing:
roleon toggle (reconnects in background).N0CALL). "Use radio" checkbox (on by default) locks the field and syncs live from the radio's configured callsign viaRadioModel::infoChanged.AA00). "Use GPS" checkbox only appears on FLEX-8000 / Aurora; populates fromRadioModel::gpsStatusChangedwhen checked.All fields save to
AppSettingsimmediately on change. New values are applied to an active reporting session when the Enable checkbox is toggled off/on (consistent with how other connection-parameter fields work in the rest of the dialog).AppSettings keys added:
FreeDvAutoReport,FreeDvMyCallsign,FreeDvUseRadioCallsign,FreeDvMyGrid,FreeDvUseGpsGrid,FreeDvMyMessage.GUI —
MainWindowactivateRADE()calls newstartFreeDvReporting(sliceId)helper whenFreeDvAutoReportis set; auto-starts the FreeDV client connection if not already connecteddeactivateRADE()callsstopFreeDvReporting()and correctly capturesm_radeSliceIdbefore zeroing it (fixes pre-existing bug where teardown always got a null slice)startFreeDvReporting()wires SNR, sync, frequency, and MOX signals to the FreeDV client withQMetaObject::invokeMethodfor thread safetystopFreeDvReporting()disconnects all those signals and callsdisableReporting()isTuning()andatuStatus() == ATUStatus::InProgresson the main thread before dispatching to the FreeDV worker thread, preventing false TX reports during tune and ATU operationsfreedvReportingToggledsignal from the dialog is connected: checksm_radeEngineto decide whether to start or stop reporting immediatelyBug Fixes
deactivateRADE()always got null slice —m_radeSliceIdwas zeroed before the teardown block read it; fixed by capturingconst int radeSliceIdat function entrymoxChangednow guards againstisTuning()andATUStatus::InProgressbefore reporting TX state to the FreeDV ReporterTest Plan
role=view(spots flow in, no station registration)role=view)🤖 Generated with Claude Code