@sailingnaturali/signalk-dsc 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +36 -2
  2. package/index.js +18 -7
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -48,8 +48,12 @@ For every DSC call heard by a connected radio:
48
48
  Full detail (MMSI, coordinates, reported time, transport) goes to the call
49
49
  log and the logbook entry instead, so TTS pipelines stay terse.
50
50
  - **Remote-vessel deltas** — the caller's `navigation.position` (and a distress
51
- notification) under `vessels.urn:mrn:imo:mmsi:<caller>`, so chartplotters can
52
- show where the call came from.
51
+ notification) under the caller's context, so chartplotters can show where the
52
+ call came from. A **distress** caller is emitted under the Search-and-Rescue
53
+ context `sar.urn:mrn:imo:mmsi:<caller>` — which plotters like
54
+ [Freeboard-SK](https://github.com/SignalK/freeboard-sk) render as a distress
55
+ (SaR) target rather than an ordinary AIS vessel; every other category stays
56
+ under `vessels.urn:mrn:imo:mmsi:<caller>`.
53
57
  - Every stored call carries an `ownShip` snapshot of the moment it arrived —
54
58
  position, course, heading, speed, wind, pressure, and (when a source publishes them)
55
59
  sea state, visibility, and cloud coverage. Absent sensor, absent field.
@@ -154,6 +158,8 @@ SIGNALK_TOKEN=<readwrite-token> npm run clear-dsc -- --category distress
154
158
 
155
159
  `--category all` clears all three. Clearing is a write, so it needs a readwrite token
156
160
  (the same one used to fire a test MOB). A new incoming call still alarms normally.
161
+ This clears the `self`-context alarm; the transient per-caller notification raised under
162
+ the sender's vessel context is not persisted or re-raised, so it is left untouched.
157
163
 
158
164
  ## Limitations
159
165
 
@@ -164,6 +170,34 @@ SIGNALK_TOKEN=<readwrite-token> npm run clear-dsc -- --category distress
164
170
  - A raised distress notification stays active until cleared from the server —
165
171
  deliberate: a received MAYDAY should not silently expire.
166
172
 
173
+ ## Future work
174
+
175
+ This plugin is **receive-only**: it reads, logs, and alarms on DSC calls a radio
176
+ puts on the bus, and never transmits. The obvious next capability is the *write*
177
+ path — initiating a DSC call from SignalK, e.g. relaying a MAYDAY or sending a
178
+ distress/MOB alert *to* the radio to broadcast.
179
+
180
+ The blocker is hardware, not software. Almost no marine VHF exposes an interface
181
+ to be **commanded to transmit** a DSC call:
182
+
183
+ - NMEA 0183 radios take a GPS position *in* and emit received calls *out*, but
184
+ there's no standard sentence to initiate a transmission.
185
+ - On NMEA 2000, PGN 129808 carries received call info; there's no
186
+ widely-implemented PGN to command a transmit. Where "send distress from the
187
+ chartplotter" exists at all, it's proprietary same-vendor MFD↔radio
188
+ integration, not an open standard a third-party plugin can drive.
189
+
190
+ The closest exception we've found is Icom's networked VHFs — the **M510 EVO** and
191
+ **M605** — which expose external/remote DSC initiation, where most radios only
192
+ let you initiate a DSC call on the radio itself. That makes them the realistic
193
+ target for a transmit path.
194
+
195
+ So a SignalK-driven relay is gated on a radio that actually supports external DSC
196
+ transmission — still rare — plus the care that initiating a distress demands (it
197
+ broadcasts on behalf of a real, licensed MMSI). Until such hardware is common
198
+ this stays out of scope and the plugin remains a passive receiver. If you have a
199
+ radio that exposes a transmit interface, open an issue.
200
+
167
201
  ## License
168
202
 
169
203
  MIT
package/index.js CHANGED
@@ -205,7 +205,9 @@ module.exports = function makePlugin(app) {
205
205
  if (event.mmsi && event.mmsi === selfMmsi()) event.self = true;
206
206
 
207
207
  // Re-transmission of the same call (distress alerts auto-repeat until
208
- // acknowledged): bump the stored call, do not re-alarm.
208
+ // acknowledged): bump the stored call, do not re-alarm. This matches on
209
+ // mmsi+category+nature and ignores `clearedAt`, so an operator-cleared call
210
+ // that keeps repeating stays silent — a cleared MAYDAY should not re-nag.
209
211
  const duplicate = store.findRecent(
210
212
  (e) =>
211
213
  e.mmsi === event.mmsi &&
@@ -242,8 +244,14 @@ module.exports = function makePlugin(app) {
242
244
  return event;
243
245
  }
244
246
 
245
- function remoteContext(mmsi) {
246
- return `vessels.urn:mrn:imo:mmsi:${mmsi}`;
247
+ // The SignalK context the caller's position is emitted under. A distress
248
+ // caller is a vessel in extremis, so it goes under the Search-and-Rescue
249
+ // context (`sar.`) — which chartplotters (e.g. Freeboard-SK) render as a
250
+ // distress/SaR target rather than an ordinary AIS vessel. Every other
251
+ // category stays under `vessels.` like any AIS contact.
252
+ function callerContext(category, mmsi) {
253
+ const prefix = category === 'distress' ? 'sar' : 'vessels';
254
+ return `${prefix}.urn:mrn:imo:mmsi:${mmsi}`;
247
255
  }
248
256
 
249
257
  // Custom sentence parsers override the stock hooks (a superset of the
@@ -261,8 +269,8 @@ module.exports = function makePlugin(app) {
261
269
  receivedAt: input.tags && input.tags.timestamp,
262
270
  });
263
271
 
264
- // Upstream-compatible delta under the sender's context so chartplotters
265
- // and AIS-style consumers see the caller's position.
272
+ // Delta under the caller's context so chartplotters and AIS-style
273
+ // consumers see the caller's position (distress → SaR target).
266
274
  if (parsed.mmsi) {
267
275
  const values = [];
268
276
  if (parsed.position) {
@@ -279,7 +287,10 @@ module.exports = function makePlugin(app) {
279
287
  });
280
288
  }
281
289
  if (values.length) {
282
- return { context: remoteContext(parsed.mmsi), updates: [{ values }] };
290
+ return {
291
+ context: callerContext(parsed.category, parsed.mmsi),
292
+ updates: [{ values }],
293
+ };
283
294
  }
284
295
  }
285
296
  } catch (err) {
@@ -304,7 +315,7 @@ module.exports = function makePlugin(app) {
304
315
  const refined = refinePosition(target.position, ext);
305
316
  store.update(target.id, { position: refined, positionResolution: 'enhanced' });
306
317
  return {
307
- context: remoteContext(ext.mmsi),
318
+ context: callerContext(target.category, ext.mmsi),
308
319
  updates: [{ values: [{ path: 'navigation.position', value: refined }] }],
309
320
  };
310
321
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sailingnaturali/signalk-dsc",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "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).",
5
5
  "main": "index.js",
6
6
  "scripts": {