@sailingnaturali/signalk-dsc 0.2.0 → 0.3.1
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.
- package/README.md +43 -0
- package/index.js +24 -1
- package/lib/store.js +11 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -142,6 +142,21 @@ $CDDSC,12,3380400790,12,05,00,1423108312,2019,,,S,E*69
|
|
|
142
142
|
$CDDSE,1,1,A,3380400790,00,45894494*1B
|
|
143
143
|
```
|
|
144
144
|
|
|
145
|
+
### Clearing an alarm
|
|
146
|
+
|
|
147
|
+
A received distress/urgency/safety call raises `notifications.dsc.<category>` and is
|
|
148
|
+
re-raised for up to an hour across server restarts. To clear an active alarm — dropping
|
|
149
|
+
the live notification and stopping the restart re-raise:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
SIGNALK_TOKEN=<readwrite-token> npm run clear-dsc -- --category distress
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`--category all` clears all three. Clearing is a write, so it needs a readwrite token
|
|
156
|
+
(the same one used to fire a test MOB). A new incoming call still alarms normally.
|
|
157
|
+
This clears the `self`-context alarm; the transient per-caller notification raised under
|
|
158
|
+
the sender's vessel context is not persisted or re-raised, so it is left untouched.
|
|
159
|
+
|
|
145
160
|
## Limitations
|
|
146
161
|
|
|
147
162
|
- Distress relays, acknowledgements, and cancellations are stored (with
|
|
@@ -151,6 +166,34 @@ $CDDSE,1,1,A,3380400790,00,45894494*1B
|
|
|
151
166
|
- A raised distress notification stays active until cleared from the server —
|
|
152
167
|
deliberate: a received MAYDAY should not silently expire.
|
|
153
168
|
|
|
169
|
+
## Future work
|
|
170
|
+
|
|
171
|
+
This plugin is **receive-only**: it reads, logs, and alarms on DSC calls a radio
|
|
172
|
+
puts on the bus, and never transmits. The obvious next capability is the *write*
|
|
173
|
+
path — initiating a DSC call from SignalK, e.g. relaying a MAYDAY or sending a
|
|
174
|
+
distress/MOB alert *to* the radio to broadcast.
|
|
175
|
+
|
|
176
|
+
The blocker is hardware, not software. Almost no marine VHF exposes an interface
|
|
177
|
+
to be **commanded to transmit** a DSC call:
|
|
178
|
+
|
|
179
|
+
- NMEA 0183 radios take a GPS position *in* and emit received calls *out*, but
|
|
180
|
+
there's no standard sentence to initiate a transmission.
|
|
181
|
+
- On NMEA 2000, PGN 129808 carries received call info; there's no
|
|
182
|
+
widely-implemented PGN to command a transmit. Where "send distress from the
|
|
183
|
+
chartplotter" exists at all, it's proprietary same-vendor MFD↔radio
|
|
184
|
+
integration, not an open standard a third-party plugin can drive.
|
|
185
|
+
|
|
186
|
+
The closest exception we've found is Icom's networked VHFs — the **M510 EVO** and
|
|
187
|
+
**M605** — which expose external/remote DSC initiation, where most radios only
|
|
188
|
+
let you initiate a DSC call on the radio itself. That makes them the realistic
|
|
189
|
+
target for a transmit path.
|
|
190
|
+
|
|
191
|
+
So a SignalK-driven relay is gated on a radio that actually supports external DSC
|
|
192
|
+
transmission — still rare — plus the care that initiating a distress demands (it
|
|
193
|
+
broadcasts on behalf of a real, licensed MMSI). Until such hardware is common
|
|
194
|
+
this stays out of scope and the plugin remains a passive receiver. If you have a
|
|
195
|
+
radio that exposes a transmit interface, open an issue.
|
|
196
|
+
|
|
154
197
|
## License
|
|
155
198
|
|
|
156
199
|
MIT
|
package/index.js
CHANGED
|
@@ -153,6 +153,15 @@ module.exports = function makePlugin(app) {
|
|
|
153
153
|
});
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
/** Clear an active DSC alarm: drop the live notification from our own source
|
|
157
|
+
* and stamp the stored events so the restart reannounce skips them. */
|
|
158
|
+
function clearCategory(category) {
|
|
159
|
+
app.handleMessage(plugin.id, {
|
|
160
|
+
updates: [{ values: [{ path: `notifications.dsc.${category}`, value: null }] }],
|
|
161
|
+
});
|
|
162
|
+
store.markCleared((e) => e.category === category, new Date().toISOString());
|
|
163
|
+
}
|
|
164
|
+
|
|
156
165
|
function shouldLogbook(event) {
|
|
157
166
|
if (options.logbookEnabled === false) return false;
|
|
158
167
|
if (!options.logbookToken) return false;
|
|
@@ -196,7 +205,9 @@ module.exports = function makePlugin(app) {
|
|
|
196
205
|
if (event.mmsi && event.mmsi === selfMmsi()) event.self = true;
|
|
197
206
|
|
|
198
207
|
// Re-transmission of the same call (distress alerts auto-repeat until
|
|
199
|
-
// 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.
|
|
200
211
|
const duplicate = store.findRecent(
|
|
201
212
|
(e) =>
|
|
202
213
|
e.mmsi === event.mmsi &&
|
|
@@ -354,6 +365,17 @@ module.exports = function makePlugin(app) {
|
|
|
354
365
|
app.emitPropertyValue('nmea0183sentenceParser', { sentence: 'DSE', parser: dseParser });
|
|
355
366
|
app.on('N2KAnalyzerOut', onPgn);
|
|
356
367
|
|
|
368
|
+
// Let an operator clear an active DSC alarm: a PUT to the notification path
|
|
369
|
+
// drops the live alert and marks the stored call(s) so a restart will not
|
|
370
|
+
// re-raise it. The readwrite device token authorizes this write.
|
|
371
|
+
for (const category of Object.keys(NOTIFICATION_STATES)) {
|
|
372
|
+
// Value ignored: any PUT to these paths means "clear" — no partial-update semantics.
|
|
373
|
+
app.registerPutHandler('vessels.self', `notifications.dsc.${category}`, () => {
|
|
374
|
+
clearCategory(category);
|
|
375
|
+
return { state: 'COMPLETED', statusCode: 200 };
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
|
|
357
379
|
started = true;
|
|
358
380
|
|
|
359
381
|
// Survive server restarts mid-incident: re-raise the newest alert per
|
|
@@ -369,6 +391,7 @@ module.exports = function makePlugin(app) {
|
|
|
369
391
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
370
392
|
const event = events[i];
|
|
371
393
|
if (!NOTIFICATION_STATES[event.category] || reannounced.has(event.category)) continue;
|
|
394
|
+
if (event.clearedAt) continue; // operator-cleared: never resurrect
|
|
372
395
|
const at = Date.parse(event.lastReceivedAt || event.receivedAt);
|
|
373
396
|
if (now - at <= REANNOUNCE_WINDOW_MS) {
|
|
374
397
|
notify(event);
|
package/lib/store.js
CHANGED
|
@@ -76,6 +76,17 @@ class EventStore {
|
|
|
76
76
|
return this.events.find((e) => e.id === id);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
/** Stamp `clearedAt` on every matching event not already cleared. Returns the
|
|
80
|
+
* count newly stamped; compacts once. */
|
|
81
|
+
markCleared(predicate, at) {
|
|
82
|
+
let touched = 0;
|
|
83
|
+
for (const e of this.events) {
|
|
84
|
+
if (predicate(e) && !e.clearedAt) { e.clearedAt = at; touched += 1; }
|
|
85
|
+
}
|
|
86
|
+
if (touched) this._compact();
|
|
87
|
+
return touched;
|
|
88
|
+
}
|
|
89
|
+
|
|
79
90
|
/** Newest event matching `predicate` received within `windowMs` of `nowMs`. */
|
|
80
91
|
findRecent(predicate, nowMs, windowMs) {
|
|
81
92
|
for (let i = this.events.length - 1; i >= 0; i--) {
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sailingnaturali/signalk-dsc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
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": {
|
|
7
7
|
"test": "node --test",
|
|
8
|
-
"send-test-dsc": "node scripts/send-test-dsc.js"
|
|
8
|
+
"send-test-dsc": "node scripts/send-test-dsc.js",
|
|
9
|
+
"clear-dsc": "node scripts/clear-dsc-alarm.js"
|
|
9
10
|
},
|
|
10
11
|
"keywords": [
|
|
11
12
|
"signalk-node-server-plugin",
|