Skip to content

Add D-STAR presence alerts (QuadNet/ircDDB source) and local dev mode - #29

Open
jbburns wants to merge 8 commits into
hamalert:mainfrom
jbburns:claude/dstar-alerting-functionality-xadhmf
Open

Add D-STAR presence alerts (QuadNet/ircDDB source) and local dev mode#29
jbburns wants to merge 8 commits into
hamalert:mainfrom
jbburns:claude/dstar-alerting-functionality-xadhmf

Conversation

@jbburns

@jbburns jbburns commented Sep 10, 2026

Copy link
Copy Markdown

Implements the D-STAR side of https://github.com/orgs/hamalert/discussions/17 (presence alerts for D-STAR: "callsign X is active on, or linked to, repeater/reflector module Y"), plus a one-command local development mode that made it testable.

What's new

D-STAR presence source (dstar.js)

  • Tails the public "last heard" logs of QuadNet (openquad.net/ics/ics, HTTP Range tail) and ircDDB (live.ircddb.net:8080/jj3.yaws, line-number poll). Both feeds are normalized into one record shape and go through one shared dedupe cache, so a dual-homed gateway never produces two alerts.
  • Each transmission is classified: voice → active event; a link command in UR (e.g. REF030CL) → linked event (and also active if held long enough to be voice, which is common); info/echo/unlink and other control commands never alert. Directed (callsign-routed) calls are ignored by default (ignoreDirectedCalls).
  • One spot per callsign + node + reflector + event within dedupeInterval (15 min). Node/reflector identifiers are W4HFH-C / REF030-C; the module letter is optional in trigger conditions (REF030 matches all modules).
  • Spots: source: 'dstar', mode: 'dstar', no frequency/band, new fields dvEvent, dvNode, dvReflector, plus dvSuffix, dvDuration, comment (TX message), spotter (reporting gateway).
  • New matcher conditions dvEvent, dvNode, dvReflector; server.js skips the band-range derivation for spots without a frequency; notification params include the new fields; the simulator accepts D-STAR spots without a frequency.
  • Fixes found along the way: the telnet notifier threw on spots without a frequency/spotter and the exception was swallowed silently (D-STAR telnet alerts were dropped); the app notifier is now skipped when APNS/FCM config is absent so the server can start without push credentials; Club Log lookups are skipped when no API key is configured.

Local development mode

  • config_loader.js: every module loads config through it; ./config.js stays the default, HAMALERT_CONFIG=<file> overrides.
  • config-local.js: committed, credential-free config (local Mongo/Redis on non-default ports, no push, no RBN/cluster logins).
  • npm run local-dev (tools/localDev.js): starts clean mongo:7 and redis:7 containers (Docker or Podman), seeds a test user + two D-STAR triggers, optionally builds and starts the hamalert-web dev image (hamalert/hamalert-web PR), prints how to connect, and runs the server. See LOCAL_DEV.md.

Design notes / open points for review

  • Privacy: this is presence, not DX spotting. ircddb.net only publishes users who sent VIS ON; QuadNet publishes everything, so HamAlert exposes nothing that isn't already public. The CQCQCQ-only idea from the discussion doesn't distinguish a QSO on modern D-STAR (both sides keep UR=CQCQCQ through a reflector), so the filter is "voice vs. control traffic" instead; alert wording is "heard on", never "is talking to".
  • Both feeds are volunteer-run; a courtesy note to the QuadNet and ircDDB teams before this runs in production is on the to-do list. Polling is light (one small HTTP request every 5 s per feed) and identifies itself with a HamAlert User-Agent.
  • Live coverage: hotspots and gateways on QuadNet/ircddb.net. US Icom G2/G3 repeaters that report only to dstarusers.org are not covered.
  • Thresholds (minVoiceDuration, minVoiceDurationLinkCommand, dedupeInterval) are config values and will want tuning against real traffic.

Testing

  • Offline replay of 8.5 h of QuadNet log: 3603 records → 318 spots (159 active, 159 linked).
  • End to end with npm run local-dev (Docker and Podman): simulated D-STAR spots reach the telnet notifier, Mongo and the Redis stream; the module-less REF030 condition matches REF030-A; live QuadNet/ircDDB spots flow through the pipeline.
  • Web trigger editor and simulate page exercised in a headless browser against this server (see the web PR).

Screenshots

Terminal captures (server startup with both feeds priming, live D-STAR spots, telnet session receiving simulated alerts, dstar-test on the live feed) are attached below.

Companion PRs: hamalert/hamalert-web (trigger editor, docs, Alerts page, dev image) and hamalert/hamalert-app (rendering of D-STAR spots).

🤖 Generated with Claude Code

jbburns and others added 7 commits September 10, 2026 04:11
Adds dstar.js, a receiver that tails the public QuadNet heard log and the
ircDDB live log, classifies transmissions into "active" (voice) and
"linked" (link command) events, drops info/echo/unlink control traffic,
and deduplicates per callsign, node, reflector and event type.

Spots carry source/mode "dstar", no frequency, and new fields dvEvent,
dvNode and dvReflector. The matcher gets matching conditions (node and
reflector match with or without the module letter), the band range is
only derived when a frequency exists, and notification params include
the new fields. tools/dstarTest.js runs the receiver against the live
feeds or a saved log without a database.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014rJsJxn47T1Bff6xi6g7ab
…imulator

Node and reflector identifiers are now "W4HFH-C" / "REF030-C" instead of
space-separated, so they survive whitespace splitting in trigger lists,
URL parameters and telnet output. The simulator accepts D-STAR spots
(dvEvent, dvNode, dvReflector) without a frequency, and notification
params include dvSuffix and dvDuration.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014rJsJxn47T1Bff6xi6g7ab
The telnet "DX de" formatter threw on spots without a frequency or
spotter, and the exception was swallowed silently, so D-STAR telnet
alerts were dropped. Use "DV" in place of the frequency, tolerate a
missing spotter, include event/node/reflector in the comment, and log
notify failures. The app notifier is now skipped when APNS/FCM config
is absent so the server can start without push credentials.

Adds LOCAL_DEV.md and tools/seedLocalUser.js for running the stack
against a local MongoDB and Redis.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014rJsJxn47T1Bff6xi6g7ab
All modules now load their config through config_loader.js, which
requires ./config.js by default or the file named by HAMALERT_CONFIG.
config-local.js is a committed, credential-free config (local Mongo
and Redis on non-default ports, no push, no RBN/cluster logins, no
Club Log key). tools/localDev.js starts clean mongo:7 and redis:7
containers, waits for them, seeds the test user and D-STAR triggers,
optionally builds and runs the hamalert-web dev image, prints how to
connect, and runs the server; containers are removed on exit unless
--keep is given. Club Log lookups are skipped when no API key is set.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014rJsJxn47T1Bff6xi6g7ab
Podman (docker CLI emulation) refuses short names like "mongo:7" unless
unqualified-search registries are configured; docker.io/library/... works
on both Docker and Podman.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
composer install inside the web container was rewriting the tracked
vendor/ files in the bind-mounted hamalert-web checkout. Mount the
hamalert-dev-web-vendor volume over vendor/ instead, so packages are
installed once and the checkout stays clean; --down removes the volume.
Note that Podman with the podman-docker shim works.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@jbburns

jbburns commented Sep 10, 2026

Copy link
Copy Markdown
Author
server-01-local-dev-startup server-02-live-dstar-spots server-03-telnet-alerts server-04-simulated-spots-curl server-05-dstar-test

So the mobile app's feed (which only returns spots matched by an "app"
trigger) has something to show when the app runs in browser dev mode
against the local stack.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.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.

1 participant