@sailingnaturali/signalk-dsc
v0.3.0
Published
Receive, log, and alert on DSC (VHF digital selective calling) calls — distress, urgency, safety, routine — from NMEA 0183 ($CDDSC/$CDDSE) and NMEA 2000 (PGN 129808).
Maintainers
Readme
@sailingnaturali/signalk-dsc
SignalK plugin that receives, logs, and alerts on DSC (VHF digital selective
calling) traffic — distress, urgency, safety, and routine calls — from both
NMEA 0183 ($--DSC/$--DSE) and NMEA 2000 (PGN 129808).
Why
When a vessel hits the red button, its radio broadcasts a DSC burst on channel 70 with MMSI, position, and nature of distress — perfectly readable even when the follow-up voice call on 16 is not. Stock SignalK mostly drops this data: the 0183 hook misses common sentence variants and persists nothing, and the N2K converter has no PGN 129808 mapping at all. If you might be the nearest boat, you want every received alert stored with its position and surfaced as an alarm — that is this plugin.
Logging that traffic is also the regulatory standard. Maritime radio rules require compulsorily-equipped vessels to record every distress, urgency, and safety call made or intercepted, with the time and position of the station in distress (47 CFR §80.409; SOLAS Ch. IV; ITU Radio Regulations; Canada's TP 1539). Pleasure craft are generally exempt from the log mandate — this just gives you that same SOLAS-grade record automatically. The parser gaps and the regulation are written up in more detail on the engineering blog.
What you get
For every DSC call heard by a connected radio:
A persistent call log — JSONL on disk, served at
GET /signalk/v2/api/resources/dsc-calls(anonymously readable when the server allows read-only access). Raw sentence/PGN is always kept alongside the parsed fields: time, MMSI, category, nature of distress, position, UTC time.Alarms under your own vessel —
notifications.dsc.distress(stateemergency),notifications.dsc.urgency(alarm),notifications.dsc.safety(alert). Routine calls never alarm. Repeated re-transmissions of the same alert (DSC auto-repeats until acknowledged) update the stored call instead of re-alarming. Alerts received within the last hour are re-raised after a server restart — notifications are in-memory, and a received MAYDAY must not vanish because the server bounced.A voice-sized message — the notification message is deliberately minimal (type, vessel, situation, range and direction from own position, action):
DSC distress alert: vessel Wind Chaser, sinking, 2.3 nautical miles northwest. Monitor channel 16.
Full detail (MMSI, coordinates, reported time, transport) goes to the call log and the logbook entry instead, so TTS pipelines stay terse.
Remote-vessel deltas — the caller's
navigation.position(and a distress notification) undervessels.urn:mrn:imo:mmsi:<caller>, so chartplotters can show where the call came from.Every stored call carries an
ownShipsnapshot of the moment it arrived — position, course, heading, speed, wind, pressure, and (when a source publishes them) sea state, visibility, and cloud coverage. Absent sensor, absent field.Logbook entries are written with
vhf: "70"(DSC is received on channel 70 by definition) plus structuredobservations; non-distress calls that propose a working channel get it in the entry text and on the stored event asworkingChannel.Optional ship's-log entries via signalk-logbook — a GMDSS-style radio log of received distress/urgency/safety traffic.
Note: Visibility on
environment.outside.visibilityis read as meters and converted to the logbook 0–9 fog scale; an integer value ≤ 9 is assumed to already be a fog-scale code, so sub-10-meter metric readings would be misread.
Transports
- NMEA 0183: registers custom
DSCandDSEsentence parsers (these replace the server's stock DSC hook with a superset: tolerant of sparse distress alerts some radios emit — see nmea0183-signalk#217 — and withDSEposition refinement from ±1 NM to ten-thousandths of a minute, which the stock parser ignores entirely). - NMEA 2000: listens to the server's analyzer stream (
N2KAnalyzerOut) for PGN 129808, sincen2k-signalkproduces no delta for it.
Configuration
| Option | Default | Notes |
| --- | --- | --- |
| maxEvents | 1000 | Oldest calls dropped beyond this. |
| logbookEnabled | true | Requires signalk-logbook and a token. |
| logbookRoutine | false | Also log routine calls. |
| logbookUrl | http://localhost:3000/plugins/signalk-logbook/logs | |
| logbookToken | empty | SignalK access token; logbook writes are skipped without one (plugin routes are auth-gated). |
| snapshotPaths | [] | Extra { field, path } pairs added to the ownShip snapshot on each stored call (position, course, heading, speed, wind, pressure, sea state, visibility and cloud coverage are always attempted). |
Trying it without a radio
Quick test script
The repo includes a script that builds a valid DSC sentence and fires it at the server over UDP. First add a UDP input in your SignalK pipedProviders (Settings → Connections → Add):
{
"id": "dsc-test-udp",
"pipeElements": [{ "type": "providers/simple",
"options": { "type": "NMEA0183", "subOptions": { "type": "udp", "port": "7777" } } }]
}Then send a fake distress call:
# Default: sinking, MMSI 366191919, near Boundary Pass → naturalaspi.local:7777
node scripts/send-test-dsc.js
# npm alias
npm run send-test-dsc
# Different nature of distress
node scripts/send-test-dsc.js --nature fire
node scripts/send-test-dsc.js --nature mob --category urgency
# Different vessel / position
node scripts/send-test-dsc.js --mmsi 316555777 --lat 48.9 --lon -123.5
# Different host / port
node scripts/send-test-dsc.js --host localhost --port 7777All --nature values: fire, flooding, collision, grounding, listing,
sinking, adrift, abandon, piracy, mob, epirb.
Verify the call was captured:
GET /signalk/v2/api/resources/dsc-callsManual sentence injection
You can also feed raw sentences through any NMEA 0183 connection (TCP, UDP, file playback):
$CDDSC,12,3380400790,12,05,00,1423108312,2019,,,S,E*69
$CDDSE,1,1,A,3380400790,00,45894494*1BClearing an alarm
A received distress/urgency/safety call raises notifications.dsc.<category> and is
re-raised for up to an hour across server restarts. To clear an active alarm — dropping
the live notification and stopping the restart re-raise:
SIGNALK_TOKEN=<readwrite-token> npm run clear-dsc -- --category distress--category all clears all three. Clearing is a write, so it needs a readwrite token
(the same one used to fire a test MOB). A new incoming call still alarms normally.
Limitations
- Distress relays, acknowledgements, and cancellations are stored (with
acknowledgement/distressedMmsifields) but don't yet clear or transform the original alarm. - Multi-sentence
DSEgroups are ignored (single-sentence covers Class-D gear). - A raised distress notification stays active until cleared from the server — deliberate: a received MAYDAY should not silently expire.
License
MIT
