feat(psk-map): band conditions, lookback, hover card + cross-platform spot-delivery fixes#3635
Conversation
Adds a small row at the bottom of the PSK Reporter window showing the four N0NBH/hamqsl HF band-group ratings (80-40 / 30-20 / 17-15 / 12-10m) as Good/Fair/Poor colored pills, reusing the existing PropForecastClient feed (no new network source). Day vs night rating set is chosen by the operator's local time. The dialog now takes the shared PropForecastClient (nullable — row is hidden when absent), fetches the detailed forecast on open (guarded against overlapping requests), and refreshes on detailUpdated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Reception stats (spot count, farthest) now pin to the top-right corner (right-aligned), instead of sharing the row with the transient status text which pushed them off-center. - Status/update text moves to the bottom-right; the HF band-conditions pills stay bottom-left — a clean bottom row. - Stats enriched: distinct bands heard, and best SNR (who hears you loudest) alongside spot count and farthest receiver. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds a small inline sparkline (new reusable Sparkline widget) pinned to the bottom-right of the PSK Reporter window. Each sample is the number of receivers that heard us within the trailing 15 minutes, taken once per minute over the past hour — a rising line flags a band opening. On open it backfills the full hour from the cached spot timestamps so it's useful immediately; a per-minute QTimer extends it live and stops when the window closes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… not showing) Root cause of cross-platform 'no spots': Live mode (default) connected to the PSK Reporter MQTT broker over TLS (1884) and relied on OpenSSL's default cert store. Our app links Homebrew OpenSSL, so the baked-in CA path is the build machine's — absent on users' macOS boxes, and Windows has no system-store handling at all. TLS verification failed, MQTT never connected, and there was no fallback, so shipped users saw no live spots even though the website (and our Qt HTTPS path) worked fine. Fixes: - Connect to the broker over plain MQTT (1883) instead of TLS (1884). The feed is public, read-only, credential-less spot data, so TLS adds nothing — and plain MQTT behaves identically on every platform with no CA handling. Shared MqttClient is untouched (no risk to other users). - HTTP fallback: if MQTT can't connect (port blocked, etc.) keep polling retrieve.pskreporter.info every 5 min; stop once MQTT confirms connect. - RadioModel now emits callsignChanged; the PSK dialog restarts the client on a late-arriving or edited callsign instead of only on reopen. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…I cleanup - Lookback dropdown (15m/30m/1h/2h/4h/8h) right of Mode, driving the HTTP backfill depth and the retained/displayed window (replaces the fixed 24h tombstone); persisted. - Removed the activity sparkline; replaced with a bottom-right connection indicator: 'MQTT'/'HTTP' + a status bullet (green = connected with data, yellow = connected/connecting but no data, red = no good connection). Driven by new PskReporterClient connection-state API. - Band-condition pills now snap to the bottom-left corner — dropped the near-invisible palette(mid) 'HF bands:' title that was pushing them off the corner; day/night context moved into each pill's tooltip. - Removed the 'best SNR' stat from the top-right line (kept count, bands, farthest). - Instant hover card: QGeoView's mapMouseMove doesn't fire for plain hovering (inner QGraphicsView viewport consumes move events), so the hover card never showed once the delayed tooltip was disabled. MapView now event-filters the viewport's MouseMove and shows the card immediately. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…istent hover card, orange SNR - Opening the window now always does a deep HTTP backfill, even in Live (MQTT) mode: start() resets lastSeqNo so the seed query fetches the full lookback window instead of an incremental (empty) query left over from a prior session. Previous spots now populate immediately on open. - Connection bar shows 'MQTT'/'HTTP' in the normal (white) label color; only the status bullet is colored. - Hover card no longer uses QToolTip (which fades on a timer). MapView now shows a persistent frameless child label that stays up until the mouse leaves the marker (or the viewport), and follows the cursor. - SNR in the hover card is always dark orange (#ff8c00) for visibility; card retuned for its dark background (muted text lightened). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The HTTP query set 'Accept-Encoding: gzip' by hand, which disables Qt's transparent decompression — so the gzipped reply was fed raw to QXmlStreamReader and failed with 'incorrectly encoded content', parsing 0 reception reports every time (confirmed in logs). Removing the header lets QNetworkAccessManager negotiate and decompress automatically, so the backfill now yields real spots. MQTT live was unaffected and already healthy. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Changing Lookback fires an immediate deep HTTP backfill; spinning through options issued several queries within seconds and tripped PSK Reporter's rate limiter (503). The selector now persists the choice immediately but defers the client update behind a 750ms single-shot timer, so rapid toggling coalesces into one query. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Changing Lookback re-fired a deep HTTP query every time — even when narrowing or revisiting a window already covered — which tripped PSK Reporter's 503 rate limiter during quick stepping. The client now tracks the deepest window backfilled this session and only issues a network query when the new lookback exceeds it; narrowing/revisiting is a local display filter (the dialog filters spots() by the current lookback). Retention keeps spots back to the deepest fetched window so widening back is instant and request-free. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Thanks for this, @jensenpat — really thorough work, and the writeup made review easy. I traced the main flows and the code holds up well: the deeper-only fetch gate in setLookbackSeconds, the pruneOldSpots() retention to qMax(m_lookbackSec, m_fetchedLookbackSec), and spotsUpdated → rebuildMarkers filtering display by the current lookback compose correctly, so narrowing/widening within a covered window stays local and request-free as described. Null-guarding on m_propForecast is consistent (constructor, updateBandConditions, bottom-row build), the PropForecastDetail API matches usage, settings go through the nested-JSON pskSettings()/writePskSetting() blob (Principle V), and Qt parent-ownership keeps the new m_hoverCard/timers leak-free. CI is green on all three platforms.
The three transport fixes are the high-value part and each is well-reasoned:
- gzip header removal — correct; manually setting
Accept-Encoding: gzipdoes suppress Qt's transparent decompression, so this is a real fix, not a guess. - deep backfill on (re)open — resetting
m_lastSeqNo/m_fetchedLookbackSecinstart()is the right place. flowStartSeconds=-fetchDepthinitial query — bounded well under the 24h API cap.
A couple of non-blocking notes:
-
TLS → plain MQTT (1884 → 1883) is a transport-security downgrade, not just a CA-path fix. The diagnosis (Homebrew OpenSSL's baked-in CA path is absent on users' machines) is sound and plaintext is a defensible trade-off for public, credential-less, read-only spot data. Worth stating explicitly that the accepted consequence is the feed is now unauthenticated and unencrypted — an on-path attacker could inject bogus spots/markers. For a hobbyist map layer that's low-impact, so I'd merge as-is; just flagging it so the decision is on the record rather than implicit. (A more surgical alternative — keep TLS but point at a bundled/system CA — is more work and probably not worth it here.)
-
Minor / optional:
MapView::eventFilterusesstatic_cast<QMouseEvent*>(event)->pos(), which is deprecated in Qt 6 in favor ofposition().toPoint(). Builds clean today (no-Werroron first-party code), so purely a forward-compat nicety — take it or leave it.
Nothing blocking from me. Nice incremental hardening of the PSK Reporter path.
🤖 aethersdr-agent · cost: $7.4882 · model: claude-opus-4-8
NF0T
left a comment
There was a problem hiding this comment.
Reviewed independently — transport fixes, feature additions, and code conventions all checked.
Transport fixes are real and correctly diagnosed. The gzip Accept-Encoding header removal is the right fix: manually setting the header tells QNetworkAccessManager to hand decompression back to the caller, so Qt was delivering raw gzip bytes to QXmlStreamReader and silently parsing zero spots. Removing the header restores transparent decompression. The TLS→plain MQTT change is a defensible tradeoff — libssl on macOS/Windows can't resolve the Homebrew-baked CA path at runtime; for public read-only spot data the security exposure is minimal and the comment captures the reasoning. The m_lastSeqNo = -1 / m_fetchedLookbackSec = 0 reset in start() correctly ensures the HTTP seed query covers the full lookback window on first open.
Feature implementation is solid. The three-layer data architecture (fetch deep / retain qMax(m_lookbackSec, m_fetchedLookbackSec) / display current window) is well-constructed. The "deeper-only" fetch gate prevents unnecessary network hits when narrowing the window. Null guards on m_propForecast are consistent across all call sites. callsignChanged signal guards with != m_callsign before emitting — correct Qt pattern.
One minor finding (non-blocking): me->pos() in MapView::eventFilter is deprecated in Qt 6.0 in favor of position().toPoint(). Adds one entry to the warning cleanup tracking list but doesn't block merge — no -Werror on CI.
Principle V confirmed. All settings through pskSettings()/writePskSetting().
CI is 6/6 green across all platforms. All paths are Tier 3.
Summary
Builds on the PSK Reporter map (#3565) with a forecast/UX layer and, importantly, fixes the cross-platform "no spots" reports from macOS/Windows users. Adds an HF band-conditions row, a lookback selector, richer reception stats, an instant hover card, and a connection-health indicator — and hardens the PSK Reporter transport so spots actually arrive on shipped builds.
All read-only public data; no new API keys.
The bug users hit: no spots on macOS/Windows
Live (MQTT) is the default, and it connected over TLS (1884) trusting OpenSSL's default cert store. The app links Homebrew OpenSSL, so the baked-in CA path is the build machine's — absent on users' Macs, and Windows has no system-store handling at all. TLS verification failed → MQTT never connected → no live spots, while the website (and our Qt HTTPS path) worked fine.
Fixes:
PskReporterClient; the sharedMqttClientis untouched.retrieve.pskreporter.infoevery 5 min and stops once MQTT confirms connected.poll()manually setAccept-Encoding: gzip, which disables Qt's transparent decompression — so the gzipped reply was fed raw toQXmlStreamReaderand parsed 0 reports every time. Removing the header fixes the backfill (verified in logs: now parses 100+ reports).start()resetslastSeqNo, so opening always backfills the lookback window (even in Live mode) instead of doing a stale incremental query that returns nothing on reopen.RadioModelnow emitscallsignChanged; the dialog restarts the client on a late-arriving or edited callsign instead of only on reopen.Features
PropForecastClientfeed (no new network source).mapMouseMovedoesn't fire for plain hovering (the innerQGraphicsViewviewport consumes move events), so the card never appeared once the delayed tooltip was disabled.MapViewnow event-filters the viewport'sMouseMoveand shows a persistent frameless card that stays up until the mouse leaves the marker (no QToolTip fade). SNR is rendered in dark orange for readability on the dark card.MQTT/HTTP(white) plus a status bullet — green = connected with data, yellow = connected/connecting but no data, red = no good connection.Server politeness (PSK Reporter rate limits)
appcontactset; MQTT health summarized to the log every 5 min.Testing
main.Files
PskReporterClient.{h,cpp}(transport + lookback + connection state),PskReporterMapDialog.{h,cpp}(UI: band row, lookback, stats, connection bullet),map/MapView.{h,cpp}(instant persistent hover card),RadioModel.{h,cpp}(callsignChanged),MainWindow_DigitalModes.cpp(passPropForecastClient).💻 Generated with Claude Code (Fable 5.0) with architecture by @jensenpat