@sailingnaturali/signalk-dsc 0.4.0 → 0.5.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.
- package/README.md +11 -0
- package/index.js +36 -0
- package/lib/markers.js +77 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,6 +33,16 @@ For every DSC call heard by a connected radio:
|
|
|
33
33
|
`GET /signalk/v2/api/resources/dsc-calls` (anonymously readable when the server
|
|
34
34
|
allows read-only access). Raw sentence/PGN is always kept alongside the parsed
|
|
35
35
|
fields: time, MMSI, category, nature of distress, position, UTC time.
|
|
36
|
+
- **A chart-marker layer** — `GET /signalk/v2/api/resources/dsc-call-markers`
|
|
37
|
+
serves logged calls as [Freeboard-SK](https://github.com/SignalK/freeboard-sk)
|
|
38
|
+
ResourceSets, one per category (distress/urgency/safety/routine), each a
|
|
39
|
+
GeoJSON `FeatureCollection` of Point markers whose popup carries the nature,
|
|
40
|
+
caller name/MMSI, and times. To use it in Freeboard: Settings → Resources
|
|
41
|
+
(Custom) → add resource type `dsc-call-markers`, reload, then toggle the
|
|
42
|
+
per-category layers. Non-distress calls drop off after `markerWindowHours`
|
|
43
|
+
(default 24); an active (un-cleared) distress call stays until you clear the alarm.
|
|
44
|
+
This is the *detail* layer — distinct from the prominent live SaR marker a
|
|
45
|
+
distress call also draws via the `sar.` context (see Remote-vessel deltas).
|
|
36
46
|
- **Alarms under your own vessel** — `notifications.dsc.distress` (state
|
|
37
47
|
`emergency`), `notifications.dsc.urgency` (`alarm`), `notifications.dsc.safety`
|
|
38
48
|
(`alert`). Routine calls never alarm. Repeated re-transmissions of the same
|
|
@@ -85,6 +95,7 @@ For every DSC call heard by a connected radio:
|
|
|
85
95
|
| Option | Default | Notes |
|
|
86
96
|
| --- | --- | --- |
|
|
87
97
|
| `maxEvents` | `1000` | Oldest calls dropped beyond this. |
|
|
98
|
+
| `markerWindowHours` | `24` | Non-distress calls leave the `dsc-call-markers` chart layer after this many hours; active distress stays until cleared. |
|
|
88
99
|
| `logbookEnabled` | `true` | Requires signalk-logbook and a token. |
|
|
89
100
|
| `logbookRoutine` | `false` | Also log routine calls. |
|
|
90
101
|
| `logbookUrl` | `http://localhost:3000/plugins/signalk-logbook/logs` | |
|
package/index.js
CHANGED
|
@@ -28,6 +28,7 @@ const { parseDsc } = require('./lib/dsc');
|
|
|
28
28
|
const { parseDse, refinePosition } = require('./lib/dse');
|
|
29
29
|
const { normalizePgn129808 } = require('./lib/pgn129808');
|
|
30
30
|
const { EventStore } = require('./lib/store');
|
|
31
|
+
const { buildMarkerResourceSets } = require('./lib/markers');
|
|
31
32
|
const { buildMessage, buildLogbookText } = require('./lib/format');
|
|
32
33
|
const { captureOwnShip, buildObservations, unwrap } = require('./lib/snapshot');
|
|
33
34
|
|
|
@@ -56,6 +57,13 @@ module.exports = function makePlugin(app) {
|
|
|
56
57
|
description: 'Oldest calls are dropped beyond this count.',
|
|
57
58
|
default: 1000,
|
|
58
59
|
},
|
|
60
|
+
markerWindowHours: {
|
|
61
|
+
type: 'number',
|
|
62
|
+
title: 'Chart marker window (hours)',
|
|
63
|
+
description:
|
|
64
|
+
'Non-distress calls drop off the dsc-call-markers chart layer after this many hours. Active (un-cleared) distress calls always remain.',
|
|
65
|
+
default: 24,
|
|
66
|
+
},
|
|
59
67
|
logbookEnabled: {
|
|
60
68
|
type: 'boolean',
|
|
61
69
|
title: 'Write received calls to the ship\'s log (signalk-logbook)',
|
|
@@ -336,6 +344,7 @@ module.exports = function makePlugin(app) {
|
|
|
336
344
|
plugin.start = function (opts) {
|
|
337
345
|
options = {
|
|
338
346
|
maxEvents: 1000,
|
|
347
|
+
markerWindowHours: 24,
|
|
339
348
|
logbookEnabled: true,
|
|
340
349
|
logbookRoutine: false,
|
|
341
350
|
logbookUrl: 'http://localhost:3000/plugins/signalk-logbook/logs',
|
|
@@ -370,6 +379,33 @@ module.exports = function makePlugin(app) {
|
|
|
370
379
|
},
|
|
371
380
|
});
|
|
372
381
|
|
|
382
|
+
const buildSets = () =>
|
|
383
|
+
buildMarkerResourceSets(store.list(), {
|
|
384
|
+
now: Date.now(),
|
|
385
|
+
windowHours: options.markerWindowHours,
|
|
386
|
+
nameFor: vesselName,
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
app.registerResourceProvider({
|
|
390
|
+
type: 'dsc-call-markers',
|
|
391
|
+
methods: {
|
|
392
|
+
async listResources() {
|
|
393
|
+
return buildSets();
|
|
394
|
+
},
|
|
395
|
+
async getResource(id) {
|
|
396
|
+
const sets = buildSets();
|
|
397
|
+
if (!sets[id]) throw new Error(`No DSC calls in category: ${id}`);
|
|
398
|
+
return sets[id];
|
|
399
|
+
},
|
|
400
|
+
setResource() {
|
|
401
|
+
throw new Error('dsc-call-markers is read-only');
|
|
402
|
+
},
|
|
403
|
+
deleteResource() {
|
|
404
|
+
throw new Error('dsc-call-markers is read-only');
|
|
405
|
+
},
|
|
406
|
+
},
|
|
407
|
+
});
|
|
408
|
+
|
|
373
409
|
app.emitPropertyValue('nmea0183sentenceParser', { sentence: 'DSC', parser: dscParser });
|
|
374
410
|
app.emitPropertyValue('nmea0183sentenceParser', { sentence: 'DSE', parser: dseParser });
|
|
375
411
|
app.on('N2KAnalyzerOut', onPgn);
|
package/lib/markers.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Per-category marker colour, emitted as the ResourceSet `styles.default`.
|
|
4
|
+
// Freeboard styles a feature from styles[properties.styleRef] and falls back to
|
|
5
|
+
// styles.default, so one default per category colours all of that set's markers.
|
|
6
|
+
const CATEGORY_COLORS = {
|
|
7
|
+
distress: 'rgba(211,47,47,1)', // red
|
|
8
|
+
urgency: 'rgba(245,124,0,1)', // orange
|
|
9
|
+
safety: 'rgba(251,192,45,1)', // amber
|
|
10
|
+
routine: 'rgba(117,117,117,1)', // grey
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const HOUR_MS = 60 * 60 * 1000;
|
|
14
|
+
|
|
15
|
+
// Should this call appear at `now`? Un-cleared distress is always shown — a
|
|
16
|
+
// MAYDAY must not age off the chart. Everything else (and cleared distress)
|
|
17
|
+
// must fall within `windowHours` of receipt.
|
|
18
|
+
function withinWindow(event, now, windowHours) {
|
|
19
|
+
if (event.category === 'distress' && !event.clearedAt) return true;
|
|
20
|
+
const received = Date.parse(event.receivedAt);
|
|
21
|
+
if (Number.isNaN(received)) return false;
|
|
22
|
+
return now - received <= windowHours * HOUR_MS;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function toFeature(event, nameFor) {
|
|
26
|
+
const mmsi = event.distressedMmsi || event.mmsi;
|
|
27
|
+
const vesselName = nameFor ? nameFor(mmsi) : undefined;
|
|
28
|
+
const properties = {
|
|
29
|
+
name: event.natureOfDistress
|
|
30
|
+
? `${event.category}: ${event.natureOfDistress}`
|
|
31
|
+
: event.category,
|
|
32
|
+
category: event.category,
|
|
33
|
+
mmsi,
|
|
34
|
+
utcTime: event.utcTime,
|
|
35
|
+
receivedAt: event.receivedAt,
|
|
36
|
+
};
|
|
37
|
+
if (event.natureOfDistress) properties.natureOfDistress = event.natureOfDistress;
|
|
38
|
+
if (vesselName) properties.vesselName = vesselName;
|
|
39
|
+
return {
|
|
40
|
+
type: 'Feature',
|
|
41
|
+
geometry: {
|
|
42
|
+
type: 'Point',
|
|
43
|
+
coordinates: [event.position.longitude, event.position.latitude],
|
|
44
|
+
},
|
|
45
|
+
properties,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Turn stored DSC call events into freeboard ResourceSets keyed by category.
|
|
50
|
+
// Empty categories are omitted.
|
|
51
|
+
function buildMarkerResourceSets(events, { now, windowHours = 24, nameFor } = {}) {
|
|
52
|
+
const buckets = {};
|
|
53
|
+
for (const event of events) {
|
|
54
|
+
if (
|
|
55
|
+
!event.position ||
|
|
56
|
+
typeof event.position.latitude !== 'number' ||
|
|
57
|
+
typeof event.position.longitude !== 'number'
|
|
58
|
+
)
|
|
59
|
+
continue;
|
|
60
|
+
if (!withinWindow(event, now, windowHours)) continue;
|
|
61
|
+
const category = event.category || 'routine';
|
|
62
|
+
(buckets[category] = buckets[category] || []).push(toFeature(event, nameFor));
|
|
63
|
+
}
|
|
64
|
+
const out = {};
|
|
65
|
+
for (const [category, features] of Object.entries(buckets)) {
|
|
66
|
+
const color = CATEGORY_COLORS[category] || CATEGORY_COLORS.routine;
|
|
67
|
+
out[category] = {
|
|
68
|
+
name: `DSC — ${category}`,
|
|
69
|
+
description: `DSC ${category} calls heard on channel 70`,
|
|
70
|
+
styles: { default: { width: 2, stroke: color, fill: color } },
|
|
71
|
+
values: { type: 'FeatureCollection', features },
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = { buildMarkerResourceSets, CATEGORY_COLORS };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sailingnaturali/signalk-dsc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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": {
|