@sailingnaturali/signalk-dsc 0.5.1 → 0.5.3
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 +3 -0
- package/docs/screenshots/freeboard-distress-markers.png +0 -0
- package/lib/dsc.js +41 -11
- package/lib/format.js +9 -2
- package/lib/pgn129808.js +9 -0
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -43,6 +43,9 @@ For every DSC call heard by a connected radio:
|
|
|
43
43
|
(default 24); an active (un-cleared) distress call stays until you clear the alarm.
|
|
44
44
|
This is the *detail* layer — distinct from the prominent live SaR marker a
|
|
45
45
|
distress call also draws via the `sar.` context (see Remote-vessel deltas).
|
|
46
|
+
|
|
47
|
+

|
|
48
|
+
|
|
46
49
|
- **Alarms under your own vessel** — `notifications.dsc.distress` (state
|
|
47
50
|
`emergency`), `notifications.dsc.urgency` (`alarm`), `notifications.dsc.safety`
|
|
48
51
|
(`alert`). Routine calls never alarm. Repeated re-transmissions of the same
|
|
Binary file
|
package/lib/dsc.js
CHANGED
|
@@ -56,6 +56,32 @@ const NATURES = {
|
|
|
56
56
|
// Telecommand 21 on a non-distress call = "ship position" — field 5 holds a position.
|
|
57
57
|
const TELECOMMAND_SHIP_POSITION = '21';
|
|
58
58
|
|
|
59
|
+
// AIS device-beacon MMSI prefixes (ITU 97x). A modern SART / MOB / EPIRB can
|
|
60
|
+
// send a DSC distress directly; the prefix lets a consumer correlate it with
|
|
61
|
+
// the matching AIS target.
|
|
62
|
+
const DEVICE_BEACONS = {
|
|
63
|
+
'970': 'sart',
|
|
64
|
+
'972': 'mob',
|
|
65
|
+
'974': 'epirb',
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// Tag the AIS device class (sart/mob/epirb) for a 97x device-beacon MMSI, else
|
|
69
|
+
// undefined. Shared with the N2K (PGN 129808) path so both transports agree.
|
|
70
|
+
function deviceBeaconFor(mmsi) {
|
|
71
|
+
if (typeof mmsi !== 'string') return undefined;
|
|
72
|
+
return DEVICE_BEACONS[mmsi.substring(0, 3)];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Resolve a nature-of-distress code to its name. The code arrives over the air
|
|
76
|
+
// unsanitised: only a clean 1–2 digit numeric code may reach the NATURES lookup
|
|
77
|
+
// or the code-NN fallback. A key like "__proto__" would otherwise resolve to
|
|
78
|
+
// Object.prototype, and any dotted value would inject extra segments into the
|
|
79
|
+
// notification path the server walks unguarded (SignalK/signalk-server#2768).
|
|
80
|
+
// Anything else collapses to a safe constant.
|
|
81
|
+
function parseNature(raw) {
|
|
82
|
+
return /^\d{1,2}$/.test(raw) ? NATURES[raw] || `code-${raw}` : 'undesignated';
|
|
83
|
+
}
|
|
84
|
+
|
|
59
85
|
function field(parts, i) {
|
|
60
86
|
const v = parts[i];
|
|
61
87
|
return typeof v === 'string' ? v.trim() : '';
|
|
@@ -126,18 +152,22 @@ function parseDsc(parts) {
|
|
|
126
152
|
mmsi: parseMmsi(parts[1]),
|
|
127
153
|
};
|
|
128
154
|
|
|
155
|
+
const beacon = deviceBeaconFor(event.mmsi);
|
|
156
|
+
if (beacon) event.deviceBeacon = beacon;
|
|
157
|
+
|
|
129
158
|
const distress = event.category === 'distress';
|
|
130
159
|
if (distress) {
|
|
131
|
-
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
160
|
+
// A distress *relay* (all-ships / area / individual format carrying a
|
|
161
|
+
// distress category) reports the casualty in field 7 and the nature of
|
|
162
|
+
// distress in field 8 — field 3 holds the relay telecommand, not a nature
|
|
163
|
+
// code. A first-party alert (format distressAlert) carries the nature in
|
|
164
|
+
// field 3.
|
|
165
|
+
if (event.format !== 'distressAlert') {
|
|
166
|
+
event.relay = true;
|
|
167
|
+
event.natureOfDistress = parseNature(field(parts, 8));
|
|
168
|
+
} else {
|
|
169
|
+
event.natureOfDistress = parseNature(field(parts, 3));
|
|
170
|
+
}
|
|
141
171
|
event.distressedMmsi = parseMmsi(parts[7]);
|
|
142
172
|
}
|
|
143
173
|
|
|
@@ -156,4 +186,4 @@ function parseDsc(parts) {
|
|
|
156
186
|
return event;
|
|
157
187
|
}
|
|
158
188
|
|
|
159
|
-
module.exports = { parseDsc, parsePosition, parseMmsi, parseUtcTime, parseChannel, NATURES };
|
|
189
|
+
module.exports = { parseDsc, parsePosition, parseMmsi, parseUtcTime, parseChannel, deviceBeaconFor, NATURES };
|
package/lib/format.js
CHANGED
|
@@ -64,7 +64,8 @@ function buildMessage(event, { ownPosition, vesselName } = {}) {
|
|
|
64
64
|
const where = wherePhrase(event, ownPosition, { spoken: true });
|
|
65
65
|
if (event.category === 'distress') {
|
|
66
66
|
const nature = NATURE_TEXT[event.natureOfDistress] || event.natureOfDistress || 'undesignated distress';
|
|
67
|
-
|
|
67
|
+
const lead = event.relay ? 'DSC distress relay' : 'DSC distress alert';
|
|
68
|
+
return `${lead}: ${who}, ${nature}, ${where}. Monitor channel 16.`;
|
|
68
69
|
}
|
|
69
70
|
const kind = event.category === 'unknown' ? 'call' : `${event.category} call`;
|
|
70
71
|
return `DSC ${kind}: ${who}, ${where}.`;
|
|
@@ -75,7 +76,13 @@ function buildLogbookText(event, { ownPosition, vesselName } = {}) {
|
|
|
75
76
|
const name = vesselName ? `${vesselName} (MMSI ${event.mmsi || 'unknown'})` : `MMSI ${event.mmsi || 'unknown'}`;
|
|
76
77
|
if (event.category === 'distress') {
|
|
77
78
|
const nature = NATURE_TEXT[event.natureOfDistress] || event.natureOfDistress || 'undesignated distress';
|
|
78
|
-
|
|
79
|
+
if (event.relay) {
|
|
80
|
+
// Name the casualty (distressedMmsi) and the relaying station (name).
|
|
81
|
+
const casualty = event.distressedMmsi ? `MMSI ${event.distressedMmsi}` : 'an unidentified vessel';
|
|
82
|
+
parts.push(`DISTRESS RELAY from ${name} reporting ${casualty}: ${nature}`);
|
|
83
|
+
} else {
|
|
84
|
+
parts.push(`DISTRESS alert from ${name}: ${nature}`);
|
|
85
|
+
}
|
|
79
86
|
} else {
|
|
80
87
|
parts.push(`${event.category} call from ${name}`);
|
|
81
88
|
}
|
package/lib/pgn129808.js
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* ITU symbol number (1xx) comes through instead — handle both.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
const { deviceBeaconFor } = require('./dsc');
|
|
13
|
+
|
|
12
14
|
// Lookup-name → canonical (lowercased keys); ITU symbols as numeric fallback.
|
|
13
15
|
const FORMATS = new Map([
|
|
14
16
|
['geographical area', 'area'],
|
|
@@ -109,9 +111,16 @@ function normalizePgn129808(pgnData) {
|
|
|
109
111
|
mmsi: normalizeMmsi(f.dscMessageAddress),
|
|
110
112
|
};
|
|
111
113
|
|
|
114
|
+
const beacon = deviceBeaconFor(event.mmsi);
|
|
115
|
+
if (beacon) event.deviceBeacon = beacon;
|
|
116
|
+
|
|
112
117
|
if (event.category === 'distress') {
|
|
113
118
|
const nature = f.natureOfDistress ?? f['1stTelecommand'];
|
|
114
119
|
event.natureOfDistress = lookup(NATURES, nature) || 'undesignated';
|
|
120
|
+
// A distress *relay* arrives under a calling format (all-ships / area /
|
|
121
|
+
// individual), not the distressAlert format of a first-party alert. The
|
|
122
|
+
// PGN already carries the casualty's nature + MMSI in dedicated fields.
|
|
123
|
+
if (event.format !== 'distressAlert') event.relay = true;
|
|
115
124
|
}
|
|
116
125
|
|
|
117
126
|
const lat = f.latitudeOfVesselReported;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sailingnaturali/signalk-dsc",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
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": {
|
|
@@ -29,6 +29,9 @@
|
|
|
29
29
|
},
|
|
30
30
|
"signalk": {
|
|
31
31
|
"displayName": "DSC",
|
|
32
|
+
"recommends": [
|
|
33
|
+
"@meri-imperiumi/signalk-logbook"
|
|
34
|
+
],
|
|
32
35
|
"screenshots": [
|
|
33
36
|
"./docs/screenshots/config.png",
|
|
34
37
|
"./docs/screenshots/data-table.png"
|