Conversation
Port the intelligence layer from the OHC server's WSJTX route into the
rig-bridge plugin stack so SSE consumers receive rich events even when
no server relay is configured.
New shared library — rig-bridge/lib/wsjtx-enrich.js
• gridToLatLon() — Maidenhead grid → lat/lon (pure math)
• getBandFromHz() — Hz → band name (160 m – 70 cm)
• createGridCache() — callsign → grid cache (2 h TTL, per-instance)
• parseDecodeMessage()— FT8/FT4 text parser: CQ/QSO type, caller,
modifier, dxCall/deCall, embedded grid;
populates grid cache as a side-effect
• enrichDecode() — full decode enrichment + grid-cache fallback
• enrichStatus() — band name, band-change flag, DX/DE lat/lon
• enrichQso() — band name, dxGrid → lat/lon
• enrichWspr() — band name, grid → lat/lon
wsjtx-relay.js changes (Phases 1–4)
• CLEAR message → new `clear` bus event (was silently dropped before)
• WSPR_DECODE → new `wspr` bus event with enriched fields
• STATUS → enriched with band, bandChanged, dxLat/dxLon, deLat/deLon
• DECODE → enriched with parsed message fields, grid → lat/lon,
content-based dedup ID; duplicates are suppressed
• QSO_LOGGED → enriched with band + lat/lon; 60 s call+freq+mode dedup
• Grid cache pruned every 5 min; cleared per-client on CLEAR
• getStatus() now reports gridCacheSize
• Relay queue still receives raw (un-enriched) messages — server does
its own enrichment on the relay path (no double-processing)
digital-mode-base.js changes (MSHV / JTDX / JS8Call)
• Same enrichment applied via wsjtx-enrich helpers
• CLEAR forwarded as `clear` bus event (was silently dropped)
• STATUS/DECODE/QSO enriched identically to wsjtx-relay
• lastBand added to getStatus() alongside existing lastMode
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…chment
Adds opt-in background callsign → lat/lon resolution via the HamQTH DXCC
API, matching the server's Phase 5 behaviour but running entirely inside
the local rig-bridge process.
wsjtx-enrich.js
• createCallsignCache() — 24 h TTL cache for HamQTH results, separate
from the 2 h gridCache (different source, different lifetime)
• triggerHamqthLookup(callsign, cache, inflight, onResult) — fire-and-
forget HTTPS GET to hamqth.com/dxcc.php; max 5 concurrent requests
(inflightSet guard); writes result into callsignCache and calls
onResult so callers can emit a decode-update event
• enrichDecode() — accepts optional 5th param callsignCache; consults
it as a third fallback tier between gridCache and giving up
wsjtx-relay.js
• Instantiates callsignCache + hamqthInflight per plugin instance
• Passes callsignCache to enrichDecode() when cfg.hamqthLookup is true
• After emitting a decode with no lat/lon, calls triggerHamqthLookup
and on resolution emits a decode-update bus event with the resolved
callsign + coordinates for frontend live-patching
• gridPruneInterval now also prunes callsignCache every 5 min
• getStatus() reports callsignCacheSize, hamqthLookup flag, hamqthInflight
rig-bridge/core/server.js (Integrations tab UI)
• New "HamQTH callsign lookup" checkbox (id=wsjtxHamqth) with help text
explaining the internet requirement and 24 h cache
• populateIntegrations() reads w.hamqthLookup → checkbox
• saveIntegrations() writes checkbox → wsjtxRelay.hamqthLookup
• Status line now shows callsign cache size when hamqthLookup is active
and omits relay count when running in SSE-only mode
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
rig-bridge/rig-bridge.js
• decode handler was building a trimmed object that silently dropped all
fields added by wsjtx-enrich (lat, lon, band, grid, type, caller,
modifier, dxCall, deCall, gridSource). Now passes them all through.
• id forwarded directly from enrichDecode (content-based) instead of
being rebuilt; raw-decode fallback retained for non-enriched plugins.
• status handler forwards new enriched fields: band, bandChanged,
dxLat, dxLon, deLat, deLon.
• qso handler forwards: band, lat, lon, frequency, myCall, myGrid.
• New clear handler → broadcasts clientId + window to SSE.
• New wspr handler → broadcasts full enriched WSPR decode to SSE.
• New decode-update handler → broadcasts HamQTH-resolved callsign
coordinates to SSE for live map-pin patching.
src/hooks/useWSJTX.js
• status handler now stores band, bandChanged, dxLat, dxLon so DX
target resolution and band-change detection work without server.
• Band-change detection uses the bandChanged flag from the enriched
status event; falls back to manual prev-band comparison for the
server/relay path.
• New clear event handler: filters decodes by clientId.
• New wspr event handler: prepends to wspr array (capped at 100).
• New decode-update event handler: patches existing decodes that share
the resolved callsign and have no lat/lon yet (HamQTH async result).
src/layouts/ModernLayout.jsx
• Pass wsjtxWspr={wsjtx.wspr} to PSKReporterPanel so the WSPR tab
actually receives data (DockableApp already had this; ModernLayout
was the only layout missing it).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nvention PSKReporterPanel filters on d.type === 'CQ' (uppercase), matching the OHC server's parseDecodeMessage output. wsjtx-enrich was using lowercase 'cq'/'qso', causing the CQ filter and the blue CQ row colour to never match in local SSE mode. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
I'm happy that it does what it says it does. I'm not seeing any errors from the rig-bridge node.js nor extras from the clients. I checked out the debug stuff that was in there, and the api that had some stats on port 5555. Also played with adding in the gridSource to the spots on WorldMap to verify that we were getting grid locs from different places. I feel I was a bit close to this one to approve it, and I'd also be more comfortable with you doing the best-practice javascript once over, given there is some claude code in there. |
|
@accius if you don't like the .gitignore leave it. I just want to avoid that this thing (Claude.md) get's into the repo accidentally. |
accius
left a comment
There was a problem hiding this comment.
Review — WSJT-X SSE enrichment
Really solid work. Local enrichment makes the SSE-only mode genuinely useful, and the separation (raw → server relay, enriched → bus) is clean. Good test plan too. A handful of issues to fix before merge:
Bugs
-
Duplicate
typekey inrig-bridge.jsdecode forwarder:type: msg.message.startsWith('CQ') ? 'CQ' : 'QSO', ... type: msg.type,
Both keys are on the same object literal — the second overwrites the first and the first line is dead (and throws
TypeErrorifmsg.messageis ever undefined, which is guarded elsewhere with?? ''). Remove the first. -
clientStates[msg.id]?.deCall/deGridnever populated. In bothdigital-mode-base.jsandwsjtx-relay.js,clientStates[msg.id]is set to{ band, dialFrequency, mode }only. The QSO_LOGGED handler falls back toclientStates[msg.id]?.deCall || null, which is alwaysundefined → null. Either storedeCall/deGridfrom STATUS intoclientStates, or drop the fallback and acceptmyCall/myGridbeing null when WSJT-X doesn't supply them. -
isGridcase-sensitivity bug: the regex/^[A-R]{2}\d{2}(?:[A-Xa-x]{2})?$/has noiflag and is tested against the original input (not the uppercasedg). A 4-char lowercase grid (em28) returns false. FT8 messages are uppercase in practice, so low-impact, but trivial to fix — test againstg, or add theiflag. -
pluginBus.on('wspr', …)forwardsmsgdirectly asdata, including the internalsourcefield and no field whitelisting — every other handler carefully selects fields. Be consistent, and be careful not to leak fields the UI shouldn't see.
Safety / robustness
-
HamQTH XML parsed by regex (
<lat>([^<]+)<\/lat>). Works for the current endpoint but brittle against whitespace/attributes. Consider a minimal XML parser or at least matching against<lat[^>]*>(-?[0-9.]+)</lat>to constrain to numerics. -
HamQTH public
dxcc.php— confirm this endpoint permits unauthenticated automated use at our expected volume (5 concurrent × many users). If ToS requires a session token, this breaks silently. Worth documenting in the README. -
No persistent HamQTH cache. Every rig-bridge restart re-queries — not a correctness issue, but if we're asking users to enable this across the fleet we may want to persist a JSON cache file.
-
Rate limiting only via
HAMQTH_MAX_CONCURRENT = 5. There is no overall QPS limit; on a busy band a burst of unknown calls could trigger dozens of requests per minute. Consider a short (~2s) per-callsign cool-down plus a global QPS cap. -
SEEN_DECODE_MAX = 2000— reasonable, but check memory behavior on a very active 20m FT8 session over 24h. Should be fine with FIFO eviction.
Minor
.gitignoreaddsCLAUDE.md— harmless, but unrelated to this PR's topic. Happy to leave it.dtis stored as a pre-formatted string (toFixed(1)) inenrichDecode, butuseWSJTXand the UI may want the numeric form for sorting. Worth passing both or keeping numeric and formatting at render time.- Setup HTML adds hardcoded colors
#1a1f2e,#2d3548,#6b7280— matches existing style in this file, so not a regression, but a note for a future cleanup. .gitignore-ingCLAUDE.mdwith no comment — a one-line comment would help future readers.
Checklist: all UI-related items are unchecked — please verify at least the Modern layout WSPR tab and the CQ filter in PSKReporterPanel before merge.
Overall: fix 1–3 (real bugs) and this is ready.
- Remove dead duplicate `type` key in rig-bridge.js decode handler (could throw TypeError when msg.message is undefined) - Whitelist fields in wspr bus handler instead of forwarding raw msg - Fix isGrid() to test regex against uppercased input so lowercase grids like em28 are recognised correctly - Tighten HamQTH XML regex to accept only numeric lat/lng values, rejecting arbitrary text content - Store deCall/deGrid in clientStates from STATUS in both wsjtx-relay and digital-mode-base so QSO_LOGGED myCall/myGrid fallback works Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rate limiting (item 8): - Add per-callsign 60s cooldown via lastAttemptedMap in triggerHamqthLookup - Add global 2 req/s QPS cap via module-level sliding-window timestamp array - Both guards are in wsjtx-enrich.js; wsjtx-relay passes lastAttemptedMap Persistent HamQTH cache (item 7): - Add loadCallsignCache/saveCallsignCache helpers to wsjtx-enrich.js - createCallsignCache().set() now accepts an optional timestamp to preserve original expiry time when loading persisted entries - wsjtx-relay loads cache from hamqth-cache.json at connect, flushes on disconnect, and does a debounced 30s save after each successful lookup dt as numeric (item 11): - enrichDecode/enrichWspr now store dt as float (msg.deltaTime ?? 0) instead of a pre-formatted string - PSKReporterPanel formats dt at render time with sign and one decimal place; handles both legacy string values (server path) and new numeric values Documentation / housekeeping: - .gitignore: add one-line comment explaining CLAUDE.md entry (item 13) - rig-bridge/README.md: add HamQTH callsign lookup section documenting ToS context, rate limits, cache behaviour, and network requirements (item 6) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
What does this PR do?
Summary
This PR moves the WSJTX decode enrichment intelligence from the OHC server into the local
rig-bridgeprocess, so SSE consumers receive fully enriched events even when no server relay is configured.rig-bridge/lib/wsjtx-enrich.js): Maidenhead grid → lat/lon, Hz → band name, FT8/FT4 message parser (CQ/QSO type, caller, modifier, embedded grid), per-client grid cache (2 h TTL), and full enrichment helpers for DECODE, STATUS, QSO_LOGGED, and WSPR_DECODE messages.hamqth.com/dxcc.php; 24 h cache, max 5 concurrent requests. On resolution adecode-updateSSE event is emitted so the frontend can live-patch map pins already on screen. Toggled via a new checkbox in the rig-bridge Integrations tab.rig-bridge.js): decode, status, qso, clear, wspr, and decode-update bus events are now all forwarded to SSE with their full enriched field sets (previously only a trimmed subset was forwarded, silently dropping all enrichment).useWSJTX.js): handles new SSE event types —clear(per-client filtering),wspr(capped ring buffer),decode-update(async HamQTH patch). Band-change detection uses thebandChangedflag from enriched status; falls back to manual comparison on the server/relay path.wsjtx-enrichwas emitting lowercase'cq'/'qso'type strings; changed to uppercase'CQ'/'QSO'to match the server convention and fix CQ filter + row colouring in PSKReporterPanel.ModernLayoutwas not passingwsjtxWsprtoPSKReporterPanel— the WSPR tab now receives data in that layout.Test plan
gridCacheSize,callsignCacheSize, andhamqthLookupflag correctlydigital-mode-base.js) receive the same enrichment as WSJT-XChecklist
server.js: caches have TTLs and size caps (we serve 2,000+ concurrent users)var(--accent-cyan), etc.).bak,.old,console.logdebug lines, or test scripts included