npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sailingnaturali/signalk-dsc

v0.10.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).

Downloads

1,600

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.

  • A chart-marker layerGET /signalk/v2/api/resources/dsc-call-markers serves logged calls as Freeboard-SK ResourceSets, one per category (distress/urgency/safety/routine), each a GeoJSON FeatureCollection of Point markers whose popup carries the nature, caller name/MMSI, and times. To use it in Freeboard: Settings → Resources (Custom) → add resource type dsc-call-markers, reload, then toggle the per-category layers. Non-distress calls drop off after markerWindowHours (default 24); an active (un-cleared) distress call stays until you clear the alarm. This is the detail layer — distinct from the prominent live SaR marker a distress call also draws via the sar. context (see Remote-vessel deltas).

    DSC distress markers in Freeboard-SK

  • Alarms under your own vessel — each call raises a per-call notifications.received.<category>.dsc-<id> (distress → state emergency, urgency → alarm, safety → warn), so two concurrent calls never overwrite one alarm. 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) under the caller's context, so chartplotters can show where the call came from. A distress caller is emitted under the Search-and-Rescue context sar.urn:mrn:imo:mmsi:<caller> — which plotters like Freeboard-SK render as a distress (SaR) target rather than an ordinary AIS vessel; every other category stays under vessels.urn:mrn:imo:mmsi:<caller>.

  • Every stored call carries an ownShip snapshot 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 structured observations; non-distress calls that propose a working channel get it in the entry text and on the stored event as workingChannel.

  • Optional ship's-log entries via signalk-logbook — a GMDSS-style radio log of received distress/urgency/safety traffic.

Note: Visibility on environment.outside.visibility is 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 DSC and DSE sentence 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 with DSE position 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, since n2k-signalk produces no delta for it.

Configuration

| Option | Default | Notes | | --- | --- | --- | | maxEvents | 1000 | Oldest calls dropped beyond this. | | markerWindowHours | 24 | Non-distress calls leave the dsc-call-markers chart layer after this many hours; active distress stays until cleared. | | 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). |

DSCWatch reporting

DSCWatch (dscwatch.com) is a crowdsourced network that aggregates received DSC traffic from stations around the world. Participation is on by default — each received call is sent to the network, including your receiver's position, to help build coverage maps and distress logs. Set dscwatchEnabled to false to keep all data on the boat.

Configuration

| Option | Default | Notes | | --- | --- | --- | | dscwatchEnabled | true | On by default; every received call is reported. Set to false to keep all data on the boat. | | dscwatchReceiverKey | empty | Leave blank to use an auto-generated UUID that persists in the plugin data directory (dscwatch-receiver-key). Enter your station's 9-digit MMSI if you want your reports attributed to a licensed station. | | dscwatchUrl | https://dscwatch.com/api/v1/report | Override for local testing only; the receiver key is appended automatically. |

What leaves the boat

Each report contains only the call's parsed fields and raw sentence or PGN payload: receivedAt, source, category, format, raw, mmsi, position, positionResolution, utcTime, natureOfDistress, distressedMmsi, workingChannel, acknowledgement, deviceBeacon, and the boolean flags relay, expansion, self, positionRefined (only when true). The receiver's position at the moment the call arrived is added as ownPosition when the server has a fix.

What never leaves the boat

The local-only fields — ownShip weather and sea-state snapshots, spoken message text, repeats, lastReceivedAt, clearedAt, and internal event IDs — are never sent.

Submission model

Every radio reception — including DSC auto-repeats and every $DSE position refinement — is submitted as its own POST. The DSCWatch backend deduplicates repeated transmissions of the same call; sending each one lets the network track propagation and timing.

Undelivered reports (connectivity loss, server restart during delivery) queue on disk at dscwatch-queue.jsonl in the plugin data directory and are retried when connectivity returns. An HTTP 404 response (receiver key unknown to the service, or a misconfigured dscwatchUrl) sets the plugin status once; subsequent reports are still attempted and dropped until the receiver key or endpoint URL is corrected — a fixed configuration heals automatically without a restart.

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: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 7777

All --nature values: fire, flooding, collision, grounding, listing, sinking, adrift, abandon, piracy, mob, epirb.

Verify the call was captured:

GET /signalk/v2/api/resources/dsc-calls

Manual 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*1B

Clearing an alarm

Each call raises a per-call notifications.received.<category>.dsc-<id>, re-raised for up to an hour across server restarts. Acknowledge one alarm by PUTting its own path — what a chartplotter does when you clear the alarm it is showing. To bulk-clear every active call of a category from the CLI — dropping the live notifications 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. This clears the self-context alarms; the transient per-caller notification raised under the sender's vessel context is not persisted or re-raised, so it is left untouched.

Limitations

  • Distress relays, acknowledgements, and cancellations are stored (with acknowledgement/distressedMmsi fields) but don't yet clear or transform the original alarm.
  • Multi-sentence DSE groups 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.

Future work

This plugin is receive-only: it reads, logs, and alarms on DSC calls a radio puts on the bus, and never transmits. The obvious next capability is the write path — initiating a DSC call from SignalK, e.g. relaying a MAYDAY or sending a distress/MOB alert to the radio to broadcast.

The blocker is hardware, not software. Almost no marine VHF exposes an interface to be commanded to transmit a DSC call:

  • NMEA 0183 radios take a GPS position in and emit received calls out, but there's no standard sentence to initiate a transmission.
  • On NMEA 2000, PGN 129808 carries received call info; there's no widely-implemented PGN to command a transmit. Where "send distress from the chartplotter" exists at all, it's proprietary same-vendor MFD↔radio integration, not an open standard a third-party plugin can drive.

The closest exception we've found is Icom's networked VHFs — the M510 EVO and M605 — which expose external/remote DSC initiation, where most radios only let you initiate a DSC call on the radio itself. That makes them the realistic target for a transmit path.

So a SignalK-driven relay is gated on a radio that actually supports external DSC transmission — still rare — plus the care that initiating a distress demands (it broadcasts on behalf of a real, licensed MMSI). Until such hardware is common this stays out of scope and the plugin remains a passive receiver. If you have a radio that exposes a transmit interface, open an issue.

See also

DSC is the alerting signal — it tells you a vessel is in distress and roughly where. For finding the casualty, pair this with the companion @sailingnaturali/signalk-ais-distress, which alarms on AIS survival beacons (SART / MOB / EPIRB) and gives you a position stream to home on. Both share the same 97x identity classes and build on @sailingnaturali/signalk-distress-core.

License

MIT