@signalk/freeboard-sk 2.19.0-beta.1 → 2.19.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/package.json +1 -1
- package/plugin/alarms/alarms.js +486 -3
- package/plugin/index.js +15 -0
- package/plugin/openApi.json +123 -1
- package/public/assets/help/index.html +26 -0
- package/public/index.html +1 -1
- package/public/main.bf1f82cf3703dabc.js +1 -0
- package/plugin/flags/flags-service.js +0 -132
- package/plugin/flags/mid.js +0 -1745
- package/plugin/weather/openweather.js +0 -175
- package/plugin/weather/weather-service.js +0 -413
- package/public/main.30f0db8afdde9114.js +0 -1
package/package.json
CHANGED
package/plugin/alarms/alarms.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.initAlarms = void 0;
|
|
3
|
+
exports.shutdownAlarms = exports.initAlarms = void 0;
|
|
4
4
|
const server_api_1 = require("@signalk/server-api");
|
|
5
5
|
const uuid = require("uuid");
|
|
6
|
+
const geolib_1 = require("geolib");
|
|
7
|
+
const AREA_TRIGGERS = ['entry', 'exit'];
|
|
8
|
+
const AREA_GEOMETRIES = ['polygon', 'circle', 'region'];
|
|
6
9
|
const STANDARD_ALARMS = [
|
|
7
10
|
'mob',
|
|
8
11
|
'fire',
|
|
@@ -16,9 +19,126 @@ const STANDARD_ALARMS = [
|
|
|
16
19
|
'abandon',
|
|
17
20
|
'aground'
|
|
18
21
|
];
|
|
22
|
+
const ALARM_API_PATH = '/signalk/v2/api/alarms';
|
|
23
|
+
class AreaAlarmManager {
|
|
24
|
+
alarms;
|
|
25
|
+
constructor() {
|
|
26
|
+
this.alarms = new Map();
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Remove area from alarm manager
|
|
30
|
+
* @param id Area identifier
|
|
31
|
+
*/
|
|
32
|
+
delete(id) {
|
|
33
|
+
// clean up notification
|
|
34
|
+
this.alarms.delete(id);
|
|
35
|
+
emitNotification({
|
|
36
|
+
path: `notifications.area.${id}`,
|
|
37
|
+
value: null
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Trigger alarm status update assessment
|
|
42
|
+
* @param id Area identifier
|
|
43
|
+
* @param condition current condition
|
|
44
|
+
* @returns void
|
|
45
|
+
*/
|
|
46
|
+
update(id, condition) {
|
|
47
|
+
if (!alarmAreas.has(id)) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (!this.alarms.has(id)) {
|
|
51
|
+
this.alarms.set(id, {
|
|
52
|
+
alarmId: id,
|
|
53
|
+
active: false,
|
|
54
|
+
lastUpdate: Date.now() - 1000
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
this.assessStatus(id, condition);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Silence alarm with the supplied identifier
|
|
61
|
+
* @param id Area identifier
|
|
62
|
+
*/
|
|
63
|
+
silence(id) {
|
|
64
|
+
// clean up notification
|
|
65
|
+
const n = server.getSelfPath(`notifications.area.${id}`);
|
|
66
|
+
if (n.value && Array.isArray(n.value.method)) {
|
|
67
|
+
const m = n.value.method.filter((i) => i !== 'sound');
|
|
68
|
+
n.value.method = m;
|
|
69
|
+
}
|
|
70
|
+
emitNotification({
|
|
71
|
+
path: `notifications.area.${id}`,
|
|
72
|
+
value: n.value
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Assess and emit alarm based on supplied condition
|
|
77
|
+
* @param id alarm id
|
|
78
|
+
* @param condition current condition
|
|
79
|
+
*/
|
|
80
|
+
assessStatus(id, condition) {
|
|
81
|
+
if (!alarmAreas.has(id)) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const area = alarmAreas.get(id);
|
|
85
|
+
const alarm = this.alarms.get(id);
|
|
86
|
+
let notify = false;
|
|
87
|
+
if (area.trigger === 'entry') {
|
|
88
|
+
if (condition === 'inside' && !alarm.active) {
|
|
89
|
+
// transition to active
|
|
90
|
+
alarm.active = true;
|
|
91
|
+
notify = true;
|
|
92
|
+
server.debug(`*** inactive -> to active (${id})`);
|
|
93
|
+
}
|
|
94
|
+
if (condition === 'outside' && alarm.active) {
|
|
95
|
+
// transition to inactive
|
|
96
|
+
alarm.active = false;
|
|
97
|
+
notify = true;
|
|
98
|
+
server.debug(`*** active -> to inactive (${id})`);
|
|
99
|
+
}
|
|
100
|
+
alarm.lastUpdate == Date.now();
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
if (condition === 'outside' && !alarm.active) {
|
|
104
|
+
// transition to active
|
|
105
|
+
alarm.active = true;
|
|
106
|
+
notify = true;
|
|
107
|
+
server.debug(`*** inactive -> to active (${id})`);
|
|
108
|
+
}
|
|
109
|
+
if (condition === 'inside' && alarm.active) {
|
|
110
|
+
// transition to inactive
|
|
111
|
+
alarm.active = false;
|
|
112
|
+
notify = true;
|
|
113
|
+
server.debug(`*** active -> to inactive (${id})`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (notify) {
|
|
117
|
+
const msg = area.trigger === 'entry'
|
|
118
|
+
? alarm.active
|
|
119
|
+
? `Monitored area ${area.name ? area.name + ' ' : ''}has been entered.`
|
|
120
|
+
: ''
|
|
121
|
+
: alarm.active
|
|
122
|
+
? `Vessel has left the monitored area ${area.name ?? ''}`
|
|
123
|
+
: '';
|
|
124
|
+
const state = alarm.active ? server_api_1.ALARM_STATE.alarm : server_api_1.ALARM_STATE.normal;
|
|
125
|
+
emitNotification({
|
|
126
|
+
path: `notifications.area.${id}`,
|
|
127
|
+
value: {
|
|
128
|
+
message: msg,
|
|
129
|
+
method: [server_api_1.ALARM_METHOD.sound, server_api_1.ALARM_METHOD.visual],
|
|
130
|
+
state: state
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// ******************************************************************
|
|
19
137
|
let server;
|
|
20
138
|
let pluginId;
|
|
21
|
-
|
|
139
|
+
let unsubscribes = [];
|
|
140
|
+
const alarmAreas = new Map();
|
|
141
|
+
const alarmManager = new AreaAlarmManager();
|
|
22
142
|
const initAlarms = (app, id) => {
|
|
23
143
|
server = app;
|
|
24
144
|
pluginId = id;
|
|
@@ -31,10 +151,203 @@ const initAlarms = (app, id) => {
|
|
|
31
151
|
});
|
|
32
152
|
}
|
|
33
153
|
initAlarmEndpoints();
|
|
154
|
+
setTimeout(() => parseRegionList(), 5000);
|
|
155
|
+
// subscribe to deltas
|
|
156
|
+
const subCommand = {
|
|
157
|
+
context: 'vessels.self',
|
|
158
|
+
subscribe: [
|
|
159
|
+
{
|
|
160
|
+
path: 'resources.*',
|
|
161
|
+
policy: 'instant'
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
path: 'navigation.position',
|
|
165
|
+
policy: 'instant'
|
|
166
|
+
}
|
|
167
|
+
]
|
|
168
|
+
};
|
|
169
|
+
server.subscriptionmanager.subscribe(subCommand, unsubscribes, (err) => {
|
|
170
|
+
console.log(`error: ${err}`);
|
|
171
|
+
}, handleDeltaMessage);
|
|
34
172
|
};
|
|
35
173
|
exports.initAlarms = initAlarms;
|
|
174
|
+
const shutdownAlarms = () => {
|
|
175
|
+
unsubscribes.forEach((s) => s());
|
|
176
|
+
unsubscribes = [];
|
|
177
|
+
};
|
|
178
|
+
exports.shutdownAlarms = shutdownAlarms;
|
|
179
|
+
const handleDeltaMessage = (delta) => {
|
|
180
|
+
if (!delta.updates) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
delta.updates.forEach((u) => {
|
|
184
|
+
if (!(0, server_api_1.hasValues)(u)) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
u.values.forEach((v) => {
|
|
188
|
+
const t = v.path.split('.');
|
|
189
|
+
if (t[0] === 'resources' && t[1] === 'regions') {
|
|
190
|
+
processRegionUpdate(t[2], v.value);
|
|
191
|
+
}
|
|
192
|
+
if (t[0] === 'navigation' && t[1] === 'position') {
|
|
193
|
+
processVesselPositionUpdate(v.value);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
};
|
|
36
198
|
const initAlarmEndpoints = () => {
|
|
37
199
|
server.debug(`** Registering Alarm Action API endpoint(s) **`);
|
|
200
|
+
// list area alarms
|
|
201
|
+
server.get(`${ALARM_API_PATH}/area`, async (req, res, next) => {
|
|
202
|
+
server.debug(`** ${req.method} ${req.path}`);
|
|
203
|
+
const ar = Array.from(alarmAreas);
|
|
204
|
+
res.status(200).json(ar);
|
|
205
|
+
});
|
|
206
|
+
// new area alarm
|
|
207
|
+
server.post(`${ALARM_API_PATH}/area`, async (req, res, next) => {
|
|
208
|
+
try {
|
|
209
|
+
validateAreaBody(req.body);
|
|
210
|
+
}
|
|
211
|
+
catch (err) {
|
|
212
|
+
res.status(400).json({
|
|
213
|
+
state: 'FAILED',
|
|
214
|
+
statusCode: 400,
|
|
215
|
+
message: err.message
|
|
216
|
+
});
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (req.body.geometry === 'region') {
|
|
220
|
+
res.status(400).json({
|
|
221
|
+
state: 'FAILED',
|
|
222
|
+
statusCode: 400,
|
|
223
|
+
message: `Invalid geometry value 'region'. Use PUT request specifying a region identifier.`
|
|
224
|
+
});
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const id = uuid.v4();
|
|
228
|
+
alarmAreas.set(id, req.body);
|
|
229
|
+
res.status(200).json({
|
|
230
|
+
state: 'COMPLETE',
|
|
231
|
+
statusCode: 200,
|
|
232
|
+
message: `Alarm Area created: ${id}`
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
server.put(`${ALARM_API_PATH}/area/:id`, async (req, res, next) => {
|
|
236
|
+
server.debug(`** ${req.method} ${req.path}`);
|
|
237
|
+
try {
|
|
238
|
+
validateAreaBody(req.body);
|
|
239
|
+
}
|
|
240
|
+
catch (err) {
|
|
241
|
+
res.status(400).json({
|
|
242
|
+
state: 'FAILED',
|
|
243
|
+
statusCode: 400,
|
|
244
|
+
message: err.message
|
|
245
|
+
});
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
if (req.body.geometry === 'region') {
|
|
249
|
+
// use region resource as alarm area
|
|
250
|
+
try {
|
|
251
|
+
const reg = await fetchRegion(req.params.id);
|
|
252
|
+
const coords = parseRegionCoords(reg);
|
|
253
|
+
if (Array.isArray(coords)) {
|
|
254
|
+
alarmAreas.set(req.params.id, {
|
|
255
|
+
geometry: req.body.geometry,
|
|
256
|
+
trigger: req.body.trigger,
|
|
257
|
+
coords: coords,
|
|
258
|
+
name: reg.name
|
|
259
|
+
});
|
|
260
|
+
res.status(200).json({
|
|
261
|
+
state: 'COMPLETE',
|
|
262
|
+
statusCode: 200,
|
|
263
|
+
message: `Alarm set for region: ${req.params.id}`
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
res.status(400).json({
|
|
268
|
+
state: 'FAILED',
|
|
269
|
+
statusCode: 400,
|
|
270
|
+
message: `Region not found!`
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
catch (e) {
|
|
275
|
+
res.status(400).json({
|
|
276
|
+
state: 'FAILED',
|
|
277
|
+
statusCode: 400,
|
|
278
|
+
message: e.message
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
//updateArea(req.params.id)
|
|
284
|
+
// use supplied coords as alarm area
|
|
285
|
+
const msg = alarmAreas.has(req.params.id)
|
|
286
|
+
? `Alarm Area updated: ${req.params.id}`
|
|
287
|
+
: `Alarm Area created: ${req.params.id}`;
|
|
288
|
+
alarmAreas.set(req.params.id, req.body);
|
|
289
|
+
res.status(200).json({
|
|
290
|
+
state: 'COMPLETE',
|
|
291
|
+
statusCode: 200,
|
|
292
|
+
message: msg
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
server.delete(`${ALARM_API_PATH}/area/:id`, (req, res, next) => {
|
|
297
|
+
server.debug(`** ${req.method} ${req.path}`);
|
|
298
|
+
try {
|
|
299
|
+
if (alarmAreas.has(req.params.id)) {
|
|
300
|
+
deleteArea(req.params.id);
|
|
301
|
+
res.status(200).json({
|
|
302
|
+
state: 'COMPLETE',
|
|
303
|
+
statusCode: 200,
|
|
304
|
+
message: `Alarm Area Cleared: ${req.params.id}`
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
res.status(400).json({
|
|
309
|
+
state: 'FAILED',
|
|
310
|
+
statusCode: 400,
|
|
311
|
+
message: `Area not found!`
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
catch (e) {
|
|
316
|
+
res.status(400).json({
|
|
317
|
+
state: 'FAILED',
|
|
318
|
+
statusCode: 400,
|
|
319
|
+
message: e.message
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
server.post(`${ALARM_API_PATH}/area/:id/silence`, (req, res) => {
|
|
324
|
+
server.debug(`** ${req.method} ${req.path}`);
|
|
325
|
+
try {
|
|
326
|
+
if (alarmAreas.has(req.params.id)) {
|
|
327
|
+
alarmManager.silence(req.params.id);
|
|
328
|
+
res.status(200).json({
|
|
329
|
+
state: 'COMPLETE',
|
|
330
|
+
statusCode: 200,
|
|
331
|
+
message: `Alarm silenced: ${req.params.id}`
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
res.status(400).json({
|
|
336
|
+
state: 'FAILED',
|
|
337
|
+
statusCode: 400,
|
|
338
|
+
message: `Area not found!`
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
catch (e) {
|
|
343
|
+
res.status(400).json({
|
|
344
|
+
state: 'FAILED',
|
|
345
|
+
statusCode: 400,
|
|
346
|
+
message: e.message
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
// standard alarms
|
|
38
351
|
server.post(`${ALARM_API_PATH}/:alarmType`, (req, res, next) => {
|
|
39
352
|
server.debug(`** ${req.method} ${ALARM_API_PATH}/${req.params.alarmType}`);
|
|
40
353
|
if (!STANDARD_ALARMS.includes(req.params.alarmType)) {
|
|
@@ -171,10 +484,180 @@ const handleAlarm = (context, path, value) => {
|
|
|
171
484
|
};
|
|
172
485
|
}
|
|
173
486
|
};
|
|
174
|
-
//
|
|
487
|
+
// emit notification delta message **
|
|
175
488
|
const emitNotification = (msg) => {
|
|
176
489
|
const delta = {
|
|
177
490
|
updates: [{ values: [msg] }]
|
|
178
491
|
};
|
|
179
492
|
server.handleMessage(pluginId, delta, server_api_1.SKVersion.v2);
|
|
180
493
|
};
|
|
494
|
+
// ********** Area Alarm methods ***************
|
|
495
|
+
/**
|
|
496
|
+
* Remove Area from management
|
|
497
|
+
* @param id Area identifier
|
|
498
|
+
*/
|
|
499
|
+
const deleteArea = (id) => {
|
|
500
|
+
alarmAreas.delete(id);
|
|
501
|
+
alarmManager.delete(id);
|
|
502
|
+
};
|
|
503
|
+
/**
|
|
504
|
+
* Validate Area Alarm request parameters
|
|
505
|
+
* @param body request body
|
|
506
|
+
*/
|
|
507
|
+
const validateAreaBody = (body) => {
|
|
508
|
+
if (!body.trigger) {
|
|
509
|
+
body.trigger = 'entry';
|
|
510
|
+
}
|
|
511
|
+
else if (!AREA_TRIGGERS.includes(body.trigger)) {
|
|
512
|
+
throw new Error(`Area alarm trigger is invalid!`);
|
|
513
|
+
}
|
|
514
|
+
if (!body.geometry) {
|
|
515
|
+
body.geometry = 'polygon';
|
|
516
|
+
}
|
|
517
|
+
else if (!AREA_GEOMETRIES.includes(body.geometry)) {
|
|
518
|
+
throw new Error(`Area alarm geometry is invalid!`);
|
|
519
|
+
}
|
|
520
|
+
if (body.geometry === 'polygon') {
|
|
521
|
+
if (!Array.isArray(body.coords)) {
|
|
522
|
+
throw new Error(`Area coordinates not provided or are invalid!`);
|
|
523
|
+
}
|
|
524
|
+
if (body.coords.length === 0) {
|
|
525
|
+
throw new Error(`Area coordinates not provided!`);
|
|
526
|
+
}
|
|
527
|
+
else if (!isValidPosition(body.coords[0])) {
|
|
528
|
+
throw new Error(`Area coordinates are invalid!`);
|
|
529
|
+
}
|
|
530
|
+
delete body.center;
|
|
531
|
+
delete body.radius;
|
|
532
|
+
}
|
|
533
|
+
if (body.geometry === 'circle') {
|
|
534
|
+
if (!body.center || !isValidPosition(body.center)) {
|
|
535
|
+
throw new Error(`Center coordinate not provided or is invalid!`);
|
|
536
|
+
}
|
|
537
|
+
if (typeof body.radius !== 'number') {
|
|
538
|
+
throw new Error(`Radius not provided or invalid!`);
|
|
539
|
+
}
|
|
540
|
+
delete body.coords;
|
|
541
|
+
}
|
|
542
|
+
if (body.geometry === 'region') {
|
|
543
|
+
delete body.coords;
|
|
544
|
+
delete body.center;
|
|
545
|
+
delete body.radius;
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
/**
|
|
549
|
+
* Determines if supplied position is valid
|
|
550
|
+
* @param position
|
|
551
|
+
* @returns true if valid
|
|
552
|
+
*/
|
|
553
|
+
const isValidPosition = (position) => {
|
|
554
|
+
return 'latitude' in position &&
|
|
555
|
+
'longitude' in position &&
|
|
556
|
+
typeof position.latitude === 'number' &&
|
|
557
|
+
position.latitude >= -90 &&
|
|
558
|
+
position.latitude <= 90 &&
|
|
559
|
+
typeof position.longitude === 'number' &&
|
|
560
|
+
position.longitude >= -180 &&
|
|
561
|
+
position.longitude <= 180
|
|
562
|
+
? true
|
|
563
|
+
: false;
|
|
564
|
+
};
|
|
565
|
+
/**
|
|
566
|
+
* Fetch region resource details
|
|
567
|
+
* @param id Region identifier
|
|
568
|
+
* @returns coordinates array
|
|
569
|
+
*/
|
|
570
|
+
const fetchRegion = async (id) => {
|
|
571
|
+
const reg = await server.resourcesApi.getResource('regions', id);
|
|
572
|
+
return reg;
|
|
573
|
+
};
|
|
574
|
+
/**
|
|
575
|
+
* Fetch list of region resources and parse them to assign alarm area
|
|
576
|
+
* @returns void
|
|
577
|
+
*/
|
|
578
|
+
const parseRegionList = async () => {
|
|
579
|
+
const regList = await server.resourcesApi.listResources('regions', undefined);
|
|
580
|
+
Object.entries(regList).forEach((r) => processRegionUpdate(r[0], r[1]));
|
|
581
|
+
};
|
|
582
|
+
/**
|
|
583
|
+
* Extract and format region coordinates
|
|
584
|
+
* @param region Region data
|
|
585
|
+
* @returns coordinates array
|
|
586
|
+
*/
|
|
587
|
+
const parseRegionCoords = (region) => {
|
|
588
|
+
let c;
|
|
589
|
+
if (region.feature.geometry?.type === 'MultiPolygon') {
|
|
590
|
+
c = region.feature.geometry?.coordinates[0][0];
|
|
591
|
+
}
|
|
592
|
+
else {
|
|
593
|
+
c = region.feature.geometry?.coordinates[0];
|
|
594
|
+
}
|
|
595
|
+
return c.map((i) => {
|
|
596
|
+
return { latitude: i[1], longitude: i[0] };
|
|
597
|
+
});
|
|
598
|
+
};
|
|
599
|
+
/**
|
|
600
|
+
* CrUD area alarm from Region delta
|
|
601
|
+
* @param id Region identifier
|
|
602
|
+
* @param region Region data
|
|
603
|
+
*/
|
|
604
|
+
const processRegionUpdate = (id, region) => {
|
|
605
|
+
if (alarmAreas.has(id)) {
|
|
606
|
+
if (!region) {
|
|
607
|
+
deleteArea(id);
|
|
608
|
+
}
|
|
609
|
+
else if (region.feature.properties.skIcon !== 'hazard') {
|
|
610
|
+
deleteArea(id);
|
|
611
|
+
}
|
|
612
|
+
else {
|
|
613
|
+
const r = alarmAreas.get(id);
|
|
614
|
+
r.coords = parseRegionCoords(region);
|
|
615
|
+
r.name = region.name;
|
|
616
|
+
alarmAreas.set(id, r);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
else {
|
|
620
|
+
if (region.feature.properties.skIcon === 'hazard') {
|
|
621
|
+
alarmAreas.set(id, {
|
|
622
|
+
trigger: 'entry',
|
|
623
|
+
geometry: 'region',
|
|
624
|
+
coords: parseRegionCoords(region),
|
|
625
|
+
name: region.name
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
/**
|
|
631
|
+
* Process received vessel.position update delta and
|
|
632
|
+
* determine the current each managed area's trigger condition
|
|
633
|
+
* @param position Vessel position
|
|
634
|
+
*/
|
|
635
|
+
const processVesselPositionUpdate = (position) => {
|
|
636
|
+
if (!isValidPosition(position)) {
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
alarmAreas.forEach((v, k) => {
|
|
640
|
+
let condition;
|
|
641
|
+
if (v.geometry === 'circle') {
|
|
642
|
+
if ((0, geolib_1.isPointWithinRadius)(position, v.center, v.radius)) {
|
|
643
|
+
condition = 'inside';
|
|
644
|
+
server.debug(`Vessel inside alarm radius ${k}`);
|
|
645
|
+
}
|
|
646
|
+
else {
|
|
647
|
+
condition = 'outside';
|
|
648
|
+
server.debug(`Vessel outside alarm radius ${k}`);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
else {
|
|
652
|
+
if ((0, geolib_1.isPointInPolygon)(position, v.coords)) {
|
|
653
|
+
condition = 'inside';
|
|
654
|
+
server.debug(`Vessel inside alarm area ${k}`);
|
|
655
|
+
}
|
|
656
|
+
else {
|
|
657
|
+
condition = 'outside';
|
|
658
|
+
server.debug(`Vessel outside alarm area ${k}`);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
alarmManager.update(k, condition);
|
|
662
|
+
});
|
|
663
|
+
};
|
package/plugin/index.js
CHANGED
|
@@ -67,6 +67,7 @@ module.exports = (server) => {
|
|
|
67
67
|
const doShutdown = () => {
|
|
68
68
|
server.debug('** shutting down **');
|
|
69
69
|
server.debug('** Un-subscribing from events **');
|
|
70
|
+
(0, alarms_1.shutdownAlarms)();
|
|
70
71
|
const msg = 'Stopped';
|
|
71
72
|
server.setPluginStatus(msg);
|
|
72
73
|
};
|
|
@@ -185,6 +186,13 @@ module.exports = (server) => {
|
|
|
185
186
|
units: 'ratio'
|
|
186
187
|
}
|
|
187
188
|
});
|
|
189
|
+
metas.push({
|
|
190
|
+
path: `${pathRoot}.outside.precipitationVolume`,
|
|
191
|
+
value: {
|
|
192
|
+
description: 'Precipitation Volume.',
|
|
193
|
+
units: 'm'
|
|
194
|
+
}
|
|
195
|
+
});
|
|
188
196
|
metas.push({
|
|
189
197
|
path: `${pathRoot}.wind.averageSpeed`,
|
|
190
198
|
value: {
|
|
@@ -241,6 +249,13 @@ module.exports = (server) => {
|
|
|
241
249
|
units: 'K'
|
|
242
250
|
}
|
|
243
251
|
});
|
|
252
|
+
metas.push({
|
|
253
|
+
path: `${pathRoot}.water.salinity`,
|
|
254
|
+
value: {
|
|
255
|
+
description: 'Water salinity.',
|
|
256
|
+
units: 'ratio'
|
|
257
|
+
}
|
|
258
|
+
});
|
|
244
259
|
metas.push({
|
|
245
260
|
path: `${pathRoot}.water.levelTendency`,
|
|
246
261
|
value: {
|