Skip to content

feat(gui): common Qt mapping engine + PSK Reporter map#3565

Merged
ten9876 merged 9 commits into
aethersdr:mainfrom
jensenpat:feat/mapping-engine-pskreporter
Jun 14, 2026
Merged

feat(gui): common Qt mapping engine + PSK Reporter map#3565
ten9876 merged 9 commits into
aethersdr:mainfrom
jensenpat:feat/mapping-engine-pskreporter

Conversation

@jensenpat

@jensenpat jensenpat commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator
Screenshot 2026-06-12 at 8 23 58 PM

Summary

Adds a reusable cross-platform Qt mapping engine and a PSK Reporter reception map (View ▸ PSK Reporter…) built on top of it. The map is a plain QWidget designed to be reused — the immediate consumer is the PSK Reporter map; the next planned consumer is the AetherModem APRS tab.

The map shows, on an OpenStreetMap basemap, which stations are currently hearing your callsign — centered on the radio's GPS fix (grid-locator fallback for radios without GPS hardware).

~1,900 lines of AetherSDR code plus a vendored, patched copy of QGeoView (LGPL-3.0) for the tile/scene engine.

Why QGeoView

  • MapLibre Native Qt requires Qt 6.5+ for full Qt 6 support; our Linux CI Qt is pre-6.5 — ruled out for now (revisit when CI moves to ≥ 6.5).
  • QtLocation/QML lost built-in map input handling in Qt 6 and would force QQuickWidget embedding into our widget-based UI.
  • QGeoView is an actively-maintained, QGraphicsView-based C++ widget (no QML), LGPL-3.0, with OSM/TMS slippy-tile support — the cleanest fit for a reusable widget. It's vendored under third_party/QGeoView and compiled as a static lib; all local modifications are documented in third_party/QGeoView/AETHERSDR-PATCHES.md.

OpenStreetMap tile-policy compliance

The OSMF tile policy is strict (apps get blocked for violations), so this implements the hard requirements:

  • App-identifying User-AgentAetherSDR/<version> (repo URL). The vendored QGeoView shipped with a fake MSIE User-Agent (a documented blocking offense) and disabled TLS verification — both patched out.
  • Disk cache honoring HTTP cache headers — a shared QNetworkDiskCache (256 MB) so tiles aren't refetched.
  • Viewport-only fetching — no bulk/offline pre-seeding.
  • HTTPS tile URLs and mandatory "© OpenStreetMap contributors" attribution overlay.

Reusable MapView widget (src/gui/map/)

  • OSM slippy map with smooth pan/zoom; min-zoom clamped so the world always fills the viewport (no blank tiles when zoomed out).
  • Keyboard: arrows pan, +/- zoom, Home recenters on the station.
  • Overlay controls (zoom in/out + recenter), distance scale, mode-color legend.
  • Generic marker + great-circle path layers with an instant hover card and click-info.
  • 3-second sonar-pulse animation on the station marker (animation only runs during the ~1 s sweep; idle cost is one timer tick per period).

PSK Reporter client (src/core/PskReporterClient.*)

  • Live (MQTT) default via the official mqtt.pskreporter.info feed (TLS), topic pskr/filter/v2/+/+/<CALL>/#, with connect/disconnect/error logging and a 5-minute health summary under the aether.pskreporter category.
  • HTTP polling of retrieve.pskreporter.info/query for the seed/backfill and non-live tiers — floored at the 5-minute policy minimum, incremental via lastseqno, with appcontact identification and no manual-refresh path (so we never hammer the server). The initial load uses PSK Reporter's default window (last 100 reports / up to 6 h).
  • Persistent spot cache — the rolling spot set is written to <CacheLocation>/psk-reporter-spots.json (per-callsign), so the map repopulates immediately on reopen within a 24-hour tombstone window; throttled to one write/min, flushed on close/quit.

PSK Reporter map dialog (src/gui/PskReporterMapDialog.*)

  • View ▸ PSK Reporter… (above FreeDV Reporter).
  • Band + Mode filters (default All/All); markers colored per mode group (FT8/FT4/WSPR/JS8/CW/PSK/RTTY/SSB/Other) with high-contrast hues over the basemap.
  • Update-interval selector: Live (MQTT) / 5 m / 10 m / 15 m / 30 m / 1 h (persisted).
  • Great-circle paths toggle, farthest-DX status readout, empty-state guidance.
  • Instant hover card (callsign · grid / band · freq · mode / SNR · distance @ bearing / UTC time · age) — driven from mapMouseMove rather than the multi-second OS tooltip delay, laid out as narrow stacked lines so neighbouring spots stay visible.

Notable fix included

MqttClient called mosquitto_tls_set() with an all-NULL CA path when no CA file was configured, which mosquitto rejects with MOSQ_ERR_INVAL — so every TLS MQTT connection (PSK Reporter live feed, and any other CA-less MQTT user) failed before reaching the broker. Now enables MOSQ_OPT_TLS_USE_OS_CERTS to trust the OS certificate store; an explicit CA file still uses mosquitto_tls_set().

Testing

  • Builds clean on macOS (Qt 6, all cores) after rebasing onto the latest main (post-MainWindow-decomposition).
  • Verified on-radio against the test Mac: Live MQTT connects (confirmed via logs after the TLS fix), spots populate and render with paths/markers, hover/click cards, filters, zoom/pan/Home, and cache reload across restarts.

Licensing

QGeoView is LGPL-3.0, kept as a separately-compiled static library with patches documented — compatible with the project. OSM data is ODbL; attribution is shown.

Follow-ups (out of scope)

  • Spot aging (fade markers by report age) and dark-theme tile treatment were intentionally deferred.
  • Reuse MapView for the AetherModem APRS tab.
  • Re-evaluate MapLibre Native Qt once CI Qt reaches ≥ 6.5.

💻 Generated with Claude Code (Fable 5.0) with architecture by @jensenpat

@jensenpat jensenpat marked this pull request as ready for review June 14, 2026 01:35
@jensenpat jensenpat requested review from a team as code owners June 14, 2026 01:35

@aethersdr-agent aethersdr-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this, @jensenpat — this is a substantial, well-organized feature. The reusable MapView/MapMarkerItem/MapPathItem split is clean, the OSM tile-policy compliance (app User-Agent, disk cache honoring HTTP headers, viewport-only fetch, mandatory attribution, patching out the fake-MSIE UA + restored TLS verification) is genuinely careful, and the MainWindow/menu wiring mirrors the existing Ax25HfPacketDecodeDialog pattern (QPointer member, m_persistentDialogs, frameless honoring) exactly. CI is fully green across macOS/Windows/Linux + CodeQL. A few notes, none blocking:

1. New flat AppSettings keys — Constitution Principle V. The dialog adds two new flat keys, PskReporterShowPaths and PskReporterUpdateIntervalMs (kIntervalKey). Principle V asks new feature config to live as a single nested-JSON blob under one root key rather than new flat keys (existing flat keys can stay). Suggest consolidating both into one object under e.g. PskReporterMap ({ "showPaths": ..., "updateIntervalMs": ... }). Minor, but it's an explicit project convention and easy to do now while the surface is small.

2. Nice latent-bug fix in the MQTT TLS change. Worth calling out that the old const char* ca = caFile.isEmpty() ? nullptr : caFile.toUtf8().constData(); was also a use-after-free — the toUtf8() temporary died at the end of that statement, so ca dangled before mosquitto_tls_set() read it. The new code passes caFile.toUtf8().constData() directly as a call argument, so the temporary lives through the call. The MOSQ_OPT_TLS_USE_OS_CERTS path is the right fix for the all-NULL MOSQ_ERR_INVAL rejection. 👍

3. Per-message marker rebuild in live mode (perf). In MQTT mode every incoming spot runs appendSpot + pruneOldSpots (an O(n) remove_if scan) and emits spotsUpdated(), which drives rebuildMarkers()setMarkers() → full teardown/recreate of every marker and path QGraphics item (up to kMaxSpots = 2000) plus a full great-circle path recompute. Own-callsign spot rate is usually low so this is fine in practice, but on a busy/contest channel the bursts could churn the scene hard. Consider a short coalescing/debounce timer on spotsUpdated → rebuildMarkers so a burst collapses to one rebuild. Follow-up-able, not a merge blocker.

4. Hardcoded appcontact. poll() sends appcontact=ki6bcj@aethersdr.com to the PSK Reporter query API. Just confirm that's the intended maintained project contact (vs. a personal/testing leftover) — it's the address PSK Reporter ops would use to reach the app author.

Overall this is solid work and reads like the surrounding codebase. The Principle V keys (#1) are the only thing I'd nudge before merge; the rest are optional follow-ups.


🤖 aethersdr-agent · cost: $7.2874 · model: claude-opus-4-8

@aethersdr-agent aethersdr-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the ~1,900 lines of AetherSDR code (skimmed the vendored QGeoView only for the patch claims). This is a clean, well-structured feature — the MapView widget has a genuinely reusable shape, ownership/RAII is careful throughout (QPointer dialog members, manual removeItem+delete for the non-QObject QGV items, QSaveFile for atomic cache writes), and the system-boundary error handling is solid: reply-error checks, XML parse-error logging, the m_fetchInFlight guard, and the hard 5-minute poll floor. All six CI checks are green. Thanks for the thorough PR writeup and the OSM-policy diligence.

A couple of nice incidental wins worth calling out: the MqttClient TLS fix also removes a latent dangling-pointer bug — the old const char* ca = caFile.toUtf8().constData() pointed into a temporary QByteArray that was destroyed before mosquitto_tls_set() ran; passing .toUtf8().constData() directly as an argument now keeps the temporary alive for the call. Good catch on MOSQ_OPT_TLS_USE_OS_CERTS.

A few findings, none blocking:

1. New flat AppSettings keys (Principle V). PskReporterMapDialog adds two new top-level flat keys — PskReporterUpdateIntervalMs and PskReporterShowPaths. Constitution Principle V wants new settings grouped as a nested-JSON blob under a single key (cf. AudioEngine's AetherialTubePreampTx and HidEncoderManager's RC28Mapping). Suggest collapsing these into one PskReporter key holding { updateIntervalMs, showPaths }. The sibling feature PRs (#3527, #3530) explicitly carry the "no new flat-key AppSettings" checklist item, so it'll likely be flagged otherwise.

2. Map can stay permanently idle if opened before a callsign is known. showEvent() calls restartClient() once (guarded by m_started). If no radio is connected at that point, PskReporterClient::start() hits the empty-callsign early-return and never runs. There's no callsignChanged signal on RadioModel, and the only later hook — gpsStatusChangedupdateHomeFromRadio() — updates the home marker but never restarts the client. So opening the dialog before connecting (then connecting) leaves the map fetching nothing until the user reopens it or nudges the interval selector. Consider re-running restartClient() when the callsign first becomes non-empty (e.g. inside the gpsStatusChanged handler, or have RadioModel emit on callsign change).

3. Minor i18n. The new viewMenu->addAction("PSK Reporter...") string isn't wrapped in tr(), unlike the adjacent tr("FreeDV Reporter..."). Worth wrapping for consistency.

One observation, not a request: appendSpot() dedupes by receiverCallsign alone, so a station that hears you on multiple bands collapses to its latest spot — meaning the band/mode filters can hide a station that genuinely heard you on the filtered band. That matches the documented "one marker per reporting station" intent, so just noting it in case multi-band-per-station is desirable later.

Nice work overall — happy to see the reusable widget land ahead of the APRS tab reuse.


🤖 aethersdr-agent · cost: $8.6710 · model: claude-opus-4-8

jensenpat and others added 9 commits June 13, 2026 22:14
Vendors QGeoView 1.1.0 (LGPL-3.0) as the cross-platform slippy-map
engine, patched for OSM tile-policy compliance (app-identifying
User-Agent instead of the upstream fake-MSIE string, proper TLS
verification, https tile URLs, static build) — see
third_party/QGeoView/AETHERSDR-PATCHES.md.

Adds a reusable MapView widget (src/gui/map/) with a policy-compliant
shared tile network manager (QNetworkDiskCache honoring HTTP cache
headers), keyboard pan (arrows), zoom (+/-), Home-recenter, pixel-sized
dot+label markers, and the mandatory OSM attribution overlay. Designed
for reuse by the future AetherModem APRS map tab.

Adds PskReporterClient: HTTP polling of retrieve.pskreporter.info
(floored at the 5-minute policy minimum, incremental via lastseqno,
appcontact identification, no manual refresh) plus a Live mode on the
official mqtt.pskreporter.info feed (TLS, pskr/filter/v2 topic).

Adds the PSK Reporter Map dialog to the View menu: centers on the
radio's GPS fix (grid-locator fallback), per-band colored receiver
markers, update interval persisted in AppSettings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…sonar pulse

- View menu: PSK Reporter Map now sits above FreeDV Reporter.
- MapView: zoom in/out + Home overlay buttons (upper-right), distance
  scale widget, and a 3-second sonar-pulse animation on the station
  marker (1s ring sweep; idle cost is one timer tick per period).
- Dialog: Band and Mode selectors (upper-left, default All/All) filter
  the displayed spots; markers are now colored per mode group with a
  high-contrast palette over the OSM basemap.
- Live (MQTT) is the default update mode; PskReporterClient logs
  connect/disconnect/error events and a 5-minute health summary
  (spots per window, total, connected flag, last-spot age) under the
  aether.pskreporter logging category.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…empty state

- Great-circle reception paths from the station to each receiver
  (MapPathItem: sphere-slerped geodesic, antimeridian-split, cosmetic
  1.5px stroke in the marker's mode color), toggled by a persisted
  'Paths' checkbox.
- Mode-color legend chip overlaid lower-left on the map.
- Status enrichment: spot count plus farthest receiver callsign and
  distance in the dialog top bar.
- Click a marker for full report details (band/freq/mode/SNR, distance,
  bearing, local time). Double-click anywhere zooms in anchored on the
  clicked point (wheel zoom was already cursor-anchored upstream).
- Empty-state guidance after two minutes with no reports.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…logging

MqttClient::connectToBroker called mosquitto_tls_set() with an all-NULL
cafile/capath when no CA bundle was configured, which mosquitto rejects
with MOSQ_ERR_INVAL ('Invalid input') — so every TLS connection
(including the PSK Reporter Live feed at mqtt.pskreporter.info:1884)
failed before reaching the broker and never reconnected. Now, when no CA
file is given, enable MOSQ_OPT_TLS_USE_OS_CERTS instead, which passes
validation and trusts the OS certificate store via
SSL_CTX_set_default_verify_paths(); an explicit CA file still uses
mosquitto_tls_set().

Also: the PSK Reporter initial HTTP load now omits flowStartSeconds so it
uses PSK Reporter's default window (last 100 reports / up to 6h) for a
fuller population when the window opens, and the HTTP path now logs the
query, reply size, parsed count, and errors under aether.pskreporter for
diagnosability.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…r paths

- Min-zoom clamp: QGeoView renders a single non-repeating world, so
  zooming out past world-fills-viewport exposed blank tiles. MapView now
  pins the minimum scale so the world always covers the view in both
  axes, recomputed on resize. (True horizontal wrap would require
  duplicating the world in the tile layer + scene rect + overlays — out
  of scope for bring-up.)
- Spot cache: PskReporterClient persists the rolling spot set to
  <CacheLocation>/psk-reporter-spots.json (per-callsign), throttled to
  one write/min while streaming, flushed on stop and destruction. On
  start the map repopulates from cache so it isn't blank while the feed
  warms up; spots older than the 24h tombstone window are dropped on
  load and prune.
- Paths: stroke bumped 1.5px -> 2.5px for visibility.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…distance/SNR)

QGeoView already drives hover tooltips via projTooltip()+QToolTip, which
renders rich text — so the hover box is a small HTML card, no custom
painting. Layout is optimized for minimal footprint: four short stacked
lines (identity / RF / signal+geometry / time) keep the box narrow so
neighbouring spots stay visible, rather than one wide line that occludes
a horizontal swath. SNR is color-coded strong/medium/weak (green/orange/
gray) as the headline figure, distance carries bearing, and time shows
absolute UTC plus a glanceable relative age. The same card backs the
click popup, replacing the previous plain-text tooltip + separate
click-info string.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Hover box now appears instantly. QGeoView's built-in tooltip fires on
  QEvent::ToolTip (the OS hover wake-up delay, multi-second here), so
  MapView now drives it from the mapMouseMove signal via search()
  hit-testing and disables the delayed built-in tooltip to avoid a
  double-show. Change-detection avoids re-show churn while hovering one
  marker.
- View menu item 'PSK Reporter Map...' -> 'PSK Reporter...' and window
  title 'PSK Reporter Map' -> 'PSK Reporter' (geometry key unchanged, so
  saved window placement is preserved).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… settings (aethersdr#3565 review)

Maintainer fix-up addressing the two should-fix items from the aethersdr#3565 review
(folded in on top of a rebase onto current main):

- THIRD_PARTY_LICENSES: add entry aethersdr#16 for the vendored QGeoView (LGPL-3.0-or-later,
  (C) 2018-2025 Andrey Yaroshenko, https://github.com/AmonRaNet/QGeoView). The
  PR vendored ~7k lines of LGPL code but the root attribution registry — which
  lists all 10 other bundled libs and is itself maintainer-gated — didn't list
  it. The bundled LICENSE + AETHERSDR-PATCHES.md remain; this is the canonical
  registry entry, matching the Dire Wolf (aethersdr#3527) precedent.

- PskReporterMapDialog: consolidate the two new flat AppSettings keys
  (PskReporterShowPaths, PskReporterUpdateIntervalMs) into one nested-JSON blob
  under a single "PskReporter" key (Constitution Principle V), via read-modify-
  write helpers. These keys are brand-new in this PR, so no migration is needed.
  (PskReporterMapGeometry stays — that's PersistentDialog window geometry, a
  separate established convention.)

Builds clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ten9876 ten9876 force-pushed the feat/mapping-engine-pskreporter branch from 5bc7213 to 38e85ea Compare June 14, 2026 05:20
@ten9876

ten9876 commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator

@jensenpat — great feature. I rebased onto current main and pushed a fix-up on top (38e85ead); please git fetch before further local work. Force-push, your 8 commits preserved with your authorship, signed with the maintainer key.

Rebase: resolved two conflicts — MainWindow_DigitalModes.cpp (your showPskReporterMapDialog() now sits after the WFM block that #3562 relocated there) and CMakeLists.txt (kept both aether_afskdemod from #3527 and your qgeoview in the link list). Builds clean; verified the merged tree links.

Two should-fixes folded in:

  1. THIRD_PARTY_LICENSES — added entry Hamlib/rigctld CAT interface #16 for the vendored QGeoView (LGPL-3.0-or-later, © 2018-2025 Andrey Yaroshenko). The root attribution registry lists all 10 other bundled libs and is maintainer-gated; it didn't list QGeoView (the bundled LICENSE + AETHERSDR-PATCHES.md were there, but not the canonical registry entry — matching the Dire Wolf precedent from feat(tnc): Direwolf-derived AFSK demodulator for VHF 1200-baud AX.25 #3527).
  2. Principle V — consolidated the two new flat keys (PskReporterShowPaths, PskReporterUpdateIntervalMs) into one nested-JSON PskReporter blob via read-modify-write helpers. Brand-new keys, so no migration. (PskReporterMapGeometry left alone — that's PersistentDialog geometry.)

I independently confirmed the OSM-policy diligence is real: tile requests use the app-identifying UA (fake-MSIE patched out) and default TLS verification (the VerifyNone bypass is removed) — both documented in AETHERSDR-PATCHES.md, QGeoView built as a separate static lib. Nice. The MQTT MOSQ_OPT_TLS_USE_OS_CERTS fix (and the latent dangling-ca UAF it removes) is a solid incidental win.

Left as follow-ups for you (non-blocking, from the bot passes): the map staying idle if opened before a callsign is known (no restart on callsign-becomes-known); tr()-wrap the "PSK Reporter..." menu string; debounce the per-spot marker rebuild in live MQTT mode. The appcontact is fine — I see it's a deliberate project-domain address.

Ready for re-review. It touches MainWindow.h + THIRD_PARTY_LICENSES (maintainer-gated), so it'll need a maintainer to land.

@ten9876 ten9876 merged commit 9082531 into aethersdr:main Jun 14, 2026
6 checks passed
NF0T pushed a commit that referenced this pull request Jun 17, 2026
… spot-delivery fixes (#3635)

## 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:
- **Plain MQTT (1883) instead of TLS (1884).** The feed is public,
read-only, credential-less spot data, so TLS added nothing but the cert
headache. Plain MQTT behaves identically on every platform with no CA
handling. Isolated to `PskReporterClient`; the shared `MqttClient` is
untouched.
- **HTTP fallback.** If MQTT can't connect (port blocked, etc.) the
client keeps polling `retrieve.pskreporter.info` every 5 min and stops
once MQTT confirms connected.
- **gzip decompression.** `poll()` manually set `Accept-Encoding: gzip`,
which disables Qt's transparent decompression — so the gzipped reply was
fed raw to `QXmlStreamReader` and parsed **0 reports every time**.
Removing the header fixes the backfill (verified in logs: now parses
100+ reports).
- **Deep backfill on open.** `start()` resets `lastSeqNo`, so opening
always backfills the lookback window (even in Live mode) instead of
doing a stale incremental query that returns nothing on reopen.
- **Callsign lifecycle.** `RadioModel` now emits `callsignChanged`; the
dialog restarts the client on a late-arriving or edited callsign instead
of only on reopen.

## Features

- **HF band-conditions row** (bottom-left): the four N0NBH/hamqsl
band-group ratings (80-40 / 30-20 / 17-15 / 12-10m) as Good/Fair/Poor
pills, day/night by local time, reusing the existing
`PropForecastClient` feed (no new network source).
- **Lookback selector** (right of Mode): 15m / 30m / 1h / 2h / 4h / 8h,
persisted. Drives the HTTP backfill depth and the displayed window.
- **Reception stats** (top-right): spot count, distinct bands heard, and
farthest receiver (callsign + distance).
- **Instant hover card**: QGeoView's `mapMouseMove` doesn't fire for
plain hovering (the inner `QGraphicsView` viewport consumes move
events), so the card never appeared once the delayed tooltip was
disabled. `MapView` now event-filters the viewport's `MouseMove` and
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.
- **Connection indicator** (bottom-right): `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)

- **Lookback debounce** (750 ms) so spinning the selector coalesces into
one query.
- **No redundant re-queries**: the client tracks the deepest window
backfilled this session and only hits the network when you go *deeper*;
narrowing or revisiting a covered window is a pure local display filter.
Retention keeps spots back to the deepest fetched window so widening
back is request-free. (Closes the 503s seen when stepping the selector.)
- HTTP polling still floored at the 5-minute policy minimum;
`appcontact` set; MQTT health summarized to the log every 5 min.

## Testing

- Builds clean on macOS (Qt 6) after rebasing onto latest `main`.
- Verified on-radio via the test Mac across several iterations
(logpull): MQTT connects on 1883 and streams live spots; HTTP backfill
parses 100+ reports (gzip fixed, no XML errors); deep backfill populates
on open; lookback narrowing/widening no longer re-queries; connection
bullet reflects MQTT↔HTTP and data state.

## 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` (pass `PropForecastClient`).

💻 Generated with Claude Code (Fable 5.0) with architecture by @jensenpat

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
G6PWY-Chris pushed a commit to G6PWY-Chris/AetherSDR that referenced this pull request Jun 22, 2026
Adds a reusable QGraphicsView-based Qt mapping engine (vendored QGeoView, LGPL-3.0, static lib) and a PSK Reporter reception map (View > PSK Reporter): OSM basemap showing who is hearing your callsign, centered on the radio GPS/grid. Live MQTT + HTTP-poll PskReporterClient (5-min floor, persistent spot cache), mode-colored markers, great-circle paths, instant hover card. OSM tile-policy compliant (app User-Agent, TLS verification, disk cache, viewport-only, attribution). Also fixes a latent MqttClient TLS bug (MOSQ_OPT_TLS_USE_OS_CERTS + dangling-ca UAF). QGeoView in THIRD_PARTY_LICENSES; settings under one nested PskReporter key (Principle V).

Authored by @jensenpat with a maintainer rebase + license/Principle-V fix-up.

Co-authored-by: jensenpat <patjensen@gmail.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