rig-bridge: Direct SSE mode, plugin data pipeline & APRS improvements#852
rig-bridge: Direct SSE mode, plugin data pipeline & APRS improvements#852accius merged 10 commits intoaccius:Stagingfrom
Conversation
When a cloudRelaySession is active the Cloud Relay card now shows the session status and a Disconnect button. Clicking it clears the session and immediately saves config (no manual Save required), switching RigContext back to the direct SSE connection path. Previously the only way to leave cloud-relay mode was to manually clear the hidden session field; without that the direct connection path in RigContext was never reached even after disabling the relay in rig-bridge. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Instead of rig-bridge firing fire-and-forget HTTP POSTs to a hardcoded localhost:8080 (wrong port, always failed silently), all plugin data now flows over the same SSE /stream connection already used for freq/mode/ptt. rig-bridge changes: - state.js: add 100-entry decode ring-buffer (addToDecodeRingBuffer / getDecodeRingBuffer) so connecting browsers see recent decodes immediately - rig-bridge.js: subscribe to pluginBus after connectIntegrations() and broadcast typed plugin messages for decode/status/qso/aprs events - server.js: send plugin-init after the rig init message on /stream connect, carrying the ring-buffer replay and list of running integration plugins - aprs-tnc.js: remove forwardToLocal() call from handleKissData — SSE broadcast replaces it; fix default ohcUrl port from 8080 → 3000 - config.js: fix default aprs.ohcUrl port 8080 → 3000 Frontend changes: - RigContext.jsx: dispatch window CustomEvent 'rig-plugin-data' for type:'plugin' and type:'plugin-init' messages from local SSE - useAPRS.js: listen for rig-plugin-data aprs events, POST raw packet to /api/aprs/local (same-origin, always reachable), then refresh stations; seed tncConnected from plugin-init - useWSJTX.js: listen for decode/status events; seed decode list from plugin-init ring-buffer replay; populate clients map from status events - useDigitalModes.js: listen for status events to update plugin statuses (OHC server has no /api/mshv|jtdx|js8call/status routes, so HTTP polling was always failing silently in local mode) Cloud relay path is entirely unchanged — window events only fire when the browser is connected directly to rig-bridge's /stream. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- useWSJTX: add isLocalMode ref that stops the adaptive polling loop the moment the first rig-bridge SSE event arrives; add qso event handling; avoid setting hasDataFlowing from SSE path (prevents spurious 2 s burst) - rig-bridge/lib/aprs-parser.js: new standalone APRS position parser (!, =, /, @, ; data types) extracted so rig-bridge can produce fully- parsed station objects without a server round-trip - aprs-tnc: parse packet before bus emit; spread lat/lon/symbol/comment into the SSE payload alongside raw AX.25 fields; tag with stationSource - useAPRS: maintain separate rfStations Map fed purely by SSE events; merge RF + internet stations (RF wins on duplicate callsign); add 60-minute aging cleanup matching server APRS_MAX_AGE_MINUTES; remove server POST call entirely for local-TNC path Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- APRSPanel: add distance-to-DE column using calculateDistance/formatDistance, respecting user metric/imperial setting; pass deLocation + units from DockableApp - APRSPanel: hover tooltip showing full comment, coordinates, distance, age, speed/course, altitude and symbol — fixed-position, pointer-events:none - APRSPanel: fix age display for RF stations (NaNh) by computing age from timestamp when server-provided age field is absent - APRSPanel: prevent server poll from resetting aprsEnabled to false when aprs-tnc was detected via SSE (tncDetectedViaSse ref) - CallsignLink: strip APRS SSID suffix (-0…-15) before QRZ lookup so W1ABC-9 opens QRZ as W1ABC Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add standard APRS symbol sprite sheets (hessu/aprs-symbols, 24 px) to public/ — primary table (0), alternate table (1), overlay table (2) - New src/utils/aprs-symbols.js: getAprsSymbolIcon() maps the two-char APRS symbol field to a Leaflet divIcon using CSS background-position into the sprite sheet; supports primary (/), alternate (\), and alphanumeric overlay table chars; falls back to null for unknown symbols - WorldMap.jsx: use symbol sprite icon when available, keeping the CSS triangle as fallback; colour ring (amber/green/cyan) preserved via box-shadow on the icon wrapper; watched stations rendered at 20 px, others at 16 px - WorldMap.jsx: fix age display for RF stations in popup (NaN) by falling back to timestamp when station.age is absent Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Clicking an APRS station on the map or in the panel should not set the DX target — APRS is a monitoring tool, not a contact/spotting source. - WorldMap: remove onSpotClick call from APRS marker click; popup still opens via Leaflet bindPopup as before - DockableApp: stop passing onSpotClick to APRSPanel Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
In local/LAN mode the browser already receives all decodes via the SSE
/stream — the HTTP batch POST to /api/wsjtx/relay on the OHC server is
redundant and generates unnecessary traffic.
- config.js: add wsjtxRelay.relayToServer (default false) — false means
SSE-only, true means also POST batches to OHC server
- wsjtx-relay.js: compute willRelay flag at connect() time; gate message
queue push, scheduleBatch, heartbeat and health-check intervals behind
willRelay; SSE bus.emit paths are always active regardless of mode;
getStatus() now surfaces relayToServer and serverUrl only when active
- state.js: export getSseClientCount() so other modules can read the
number of live SSE connections
- server.js: import getSseClientCount; add GET /api/status returning
{ sseClients, uptime } — lightweight endpoint for UI health display
- SettingsPanel: add wsjtxRelayToServer toggle that immediately PATCHes
rig-bridge config; handleConfigureWsjtxRelay now also sets
relayToServer:true when pushing cloud-relay credentials; add SSE
client count badge with Refresh button so users can verify local
connections before disabling server relay
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The delivery mode toggle belongs on the source side (rig-bridge) not in the OHC settings panel. rig-bridge UI (server.js): - Replace the flat wsjtx opts section with a two-option delivery mode selector: "SSE only" (local/LAN) and "Relay to OHC server" (cloud) - SSE mode shows only UDP port and multicast options — server fields (URL, relay key, session ID, batch interval) are hidden in a separate wsjtxServerOpts div that only appears in relay mode - populateIntegrations() reads relayToServer flag to set the radio - toggleWsjtxMode() shows/hides the server-specific block - saveIntegrations() writes relayToServer from the selected radio OHC SettingsPanel: - Remove wsjtxRelayToServer state, handleToggleRelayToServer handler, fetchSseClientCount handler, toggle UI block and SSE count badge — all moved to rig-bridge UI - relayToServer:true is still sent when the user clicks Configure Cloud Relay, since that action explicitly enables server delivery Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nt and lib files
- Rewrite WSJT-X Relay section: document SSE-only (default) vs relay-to-server
delivery modes, three setup options, updated config reference with relayToServer
- Add GET /api/status endpoint to API reference table ({sseClients, uptime})
- Add lib/aprs-parser.js and lib/wsjtx-protocol.js to project structure tree
- Update pluginBus event docs to include status, qso and aprs events
- Add SSE ring-buffer replay note to Digital Mode Plugins section
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
accius
left a comment
There was a problem hiding this comment.
Good work Jörg — the SSE plugin pipeline is well-architected and the bandwidth reduction for local/LAN users is significant. A few things to address:
Needs fix
1. forwardToLocal removed from aprs-tnc.js — verify cloud relay APRS still works
The forwardToLocal([aprsPacket]) call was removed. In SSE-only mode this is fine (browser gets APRS via SSE directly). But for cloud relay users, that POST to /api/aprs/local was how RF packets got into the remote server's station cache. Now only bus.emit('aprs', ...) remains. Does the cloud-relay plugin pick up aprs bus events and forward them to the remote server? If not, cloud relay users will lose RF APRS data on the map.
2. isLocalMode is permanently latched — no recovery on SSE disconnect
In useWSJTX.js, isLocalMode.current = true is set on the first SSE plugin message and never reset. If rig-bridge drops mid-session, HTTP polling never resumes — the user sees stale data until they refresh. Consider resetting on SSE disconnect or adding a staleness timeout that falls back to polling.
3. ohcUrl default changed from 8080 to 3000
- ohcUrl: 'http://localhost:8080',
+ ohcUrl: 'http://localhost:3000',
This changes the default for all new installs and will be patched into existing configs via the CONFIG_VERSION migration. Is 3000 the intended production default? If the dev server runs on 3000 but deployed/Docker installs use 8080, this could break self-hosted users on upgrade.
Worth addressing
4. Tooltip can overflow viewport
The hover tooltip positions at clientX + 14, clientY + 14 but doesn't clamp to viewport bounds. Stations near the right or bottom edge will have clipped tooltips. The comment in the code says "Keep tooltip within viewport" but the clamping isn't implemented.
5. Dead onSpotClick prop on APRSPanel
APRSPanel still accepts onSpotClick and the row onClick still calls onSpotClick?.(...), but DockableApp.jsx no longer passes it. Harmless (optional chaining), but dead code — clean it up for clarity.
6. CONFIG_VERSION not bumped for relayToServer
The new relayToServer: false key is added to the default config but CONFIG_VERSION stays at 7. Existing configs won't get it auto-patched. Works in practice since !!undefined is false, but breaks the convention of bumping for new keys.
Looks good
pluginBus→ SSEbroadcast()→CustomEvent→ per-hook subscription: clean decouplingplugin-initwith 100-entry decode ring buffer on SSE connect: great UX, no waiting for next FT8 cycle- APRS RF station store with 60-min aging, RF-wins merge,
tncDetectedViaSseflicker fix — all solid - APRS symbol sprites with fallback triangle, overlay char support, colour ring via
box-shadow - SSID strip in
extractBaseCall()for QRZ links - Cloud Relay disconnect button with immediate save
- APRS clicks no longer moving DX target
|
Please note, this is a AI Review posted above, there was too much in the diff for me to parse through this morning |
…ip clamp, config defaults - useWSJTX: reset isLocalMode after 30 s SSE silence so HTTP polling resumes if rig-bridge disconnects mid-session (was permanently latched) - APRSPanel: clamp hover tooltip to viewport bounds; remove dead onSpotClick prop - rig-bridge config: revert ohcUrl default to localhost:8080 (was 3000, dev-only port) - rig-bridge config: bump CONFIG_VERSION 7 → 8 for relayToServer key addition Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
I just committed a fix. Claude was complaining about the first finding: Finding 1 — forwardToLocal / cloud relay APRS: No change needed If this will be committed I have another PR based on this in the pipeline to add HTTPS (optional) to the bridge. Target is to integrate better with browsers not allowing http combined with https. Like Safari. When both are in I can work again on the MeshCom plugin. |
|
merged thank you |
What does this PR do?
Summary
This PR has two themes: eliminating unnecessary server traffic when rig-bridge is connected directly (local/LAN mode), and a set of APRS panel and map UX improvements.
1 — Disconnect Cloud Relay button
The Settings panel previously had no way to disconnect an active cloud relay session — the only option was to manually clear the session ID and save. A Disconnect Cloud Relay button is now shown when a session is active. It clears the session immediately without requiring a separate Save click.
2 — Route all rig-bridge plugin data over SSE (local mode)
Previously, when cloud relay was off, plugin data (WSJT-X decodes, digital mode status, QSOs, APRS RF packets) still made round-trips through the OHC server — either via HTTP polling or HTTP POST. The rig-bridge connection was only used for rig frequency/PTT.
What changed:
rig-bridge.js— centralpluginBuslistener fans out all plugin events (decode,status,qso,aprs) to SSE clients viabroadcast()astype: 'plugin'messagesserver.js— on SSE connect, sends atype: 'plugin-init'message with the running plugin list and a 100-entry decode ring-buffer (so the browser gets recent history immediately)RigContext.jsx— dispatches awindowCustomEventrig-plugin-datafor every plugin SSE message; hooks subscribe independentlyuseWSJTX.js— adds anisLocalModeref that permanently stops the adaptive HTTP polling loop the moment the first SSE decode arrivesuseDigitalModes.js— populates MSHV/JTDX/JS8Call status directly from SSEstatusevents (the OHC server has no status routes for these in local mode)rig-bridge/lib/aprs-parser.js— new standalone APRS position parser so rig-bridge can produce fully-parsed station objects (lat/lon/symbol/speed/course/altitude) before broadcasting, without a server round-tripaprs-tnc.js— calls the parser before emitting on the bus; tags packets withstationSource: 'local-tnc'useAPRS.js— maintains a separaterfStationsMap fed purely by SSE; merges RF + internet stations (RF wins on duplicate callsign); 60-minute aging matches serverAPRS_MAX_AGE_MINUTES; server POST to/api/aprs/localremoved entirelyBandwidth comparison — cloud relay ON vs OFF
In local mode the OHC server receives zero real-time rig-bridge traffic. The only remaining server contact is the 15 s APRS-IS internet station poll — which is independent of rig-bridge. For a self-hosted install running entirely on a home network this cuts ongoing rig-bridge-related HTTP requests from ~3 req/s to 0 req/s.
3 — APRS panel improvements
timestamp(epoch ms) but no pre-computedage(minutes). Age is now derived fromtimestampwhenageis absentaprsEnabledtofalse(server hasAPRS_ENABLED=false) even after SSE confirmed the TNC was running. AtncDetectedViaSseref now prevents the server poll from overriding that stateW1ABC-9now opens QRZ forW1ABC; the-NSSID suffix is stripped inextractBaseCall()before the URL is constructed4 — APRS symbol sprites on map
All APRS map markers previously rendered as a generic coloured triangle regardless of the symbol field in the packet.
hessu/aprs-symbols, 24 px) topublic/— primary table, alternate table, and overlay tablesrc/utils/aprs-symbols.js—getAprsSymbolIcon()maps the two-char APRS symbol string to the correct sprite cell using CSSbackground-position; supports primary/, alternate\, and alphanumeric overlay table chars; returnsnullfor unknown symbols so the triangle fallback is usedWorldMap.jsx— uses the symbol sprite icon when available; colour ring (amber = watched, green = RF, cyan = internet) preserved viabox-shadowon the icon wrapper; watched stations 20 px, others 16 px5 — APRS clicks no longer move DX location
Clicking an APRS station on the map or in the panel was calling
handleSpotClick, which sets the DX target location. APRS is a monitoring tool — it should not affect the DX target. Map clicks now only open the Leaflet popup; panel row clicks no longer callonSpotClickat all.Files changed
rig-bridge.js,core/server.js,core/state.js,core/config.jsplugins/aprs-tnc.jslib/aprs-parser.js(new)useWSJTX.js,useAPRS.js,useDigitalModes.jsRigContext.jsxSettingsPanel.jsx,APRSPanel.jsx,WorldMap.jsx,CallsignLink.jsxDockableApp.jsxpublic/aprs-symbols-24-{0,1,2}.png(new)src/utils/aprs-symbols.js(new)Test plan
/api/wsjtpolling in browser devtools Network tabNaNh) for RF stationsOE5XYZ-9) — QRZ opens for base callsign