signalk-vessels-to-ais 1.1.2 → 1.2.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 (4) hide show
  1. package/.eslintrc.js +24 -24
  2. package/README.md +25 -20
  3. package/index.js +442 -309
  4. package/package.json +26 -24
package/.eslintrc.js CHANGED
@@ -1,24 +1,24 @@
1
- module.exports = {
2
- env: {
3
- commonjs: true,
4
- es2021: true,
5
- node: true,
6
- browser: true,
7
- jquery: true,
8
- },
9
- extends: [
10
- 'airbnb-base',
11
- ],
12
- parserOptions: {
13
- ecmaVersion: 12,
14
- },
15
- rules: {
16
- 'linebreak-style': 0,
17
- 'no-console': 0,
18
- 'func-names': 0,
19
- 'prefer-destructuring': 0,
20
- 'one-var-declaration-per-line': 0,
21
- 'one-var': 0,
22
- 'no-plusplus': 0,
23
- },
24
- };
1
+ module.exports = {
2
+ env: {
3
+ commonjs: true,
4
+ es2021: true,
5
+ node: true,
6
+ browser: true,
7
+ jquery: true,
8
+ },
9
+ extends: [
10
+ 'airbnb-base',
11
+ ],
12
+ parserOptions: {
13
+ ecmaVersion: 12,
14
+ },
15
+ rules: {
16
+ 'linebreak-style': 0,
17
+ 'no-console': 0,
18
+ 'func-names': 0,
19
+ 'prefer-destructuring': 0,
20
+ 'one-var-declaration-per-line': 0,
21
+ 'one-var': 0,
22
+ 'no-plusplus': 0,
23
+ },
24
+ };
package/README.md CHANGED
@@ -1,20 +1,25 @@
1
- # signalk-vessels-to-ais
2
- [![npm version](https://badge.fury.io/js/signalk-vessels-to-ais.svg)](https://badge.fury.io/js/signalk-vessels-to-ais)
3
- [![Known Vulnerabilities](https://snyk.io/test/github/KEGustafsson/signalk-vessels-to-ais/badge.svg)](https://snyk.io/test/github/KEGustafsson/signalk-vessels-to-ais)
4
-
5
- SignalK server plugin to convert other vessel data to NMEA0183 AIS format and forward it out to 3rd party applications.
6
-
7
- User can configure:
8
- - How often data is sent out
9
-
10
- New:
11
- - v1.1.2, fix: http/https url selection and better error info
12
- - v1.1.1, fix: current status of the plugin updated
13
- - v1.1.0, fix: numeric value test for text strings of AIS
14
- - v1.0.0, v1 release
15
- - v0.0.6, fix: node-fetch issue with self signed cert
16
- - v0.0.5, fix: callSign default value
17
- - v0.0.4, fix: beam calc
18
- - v0.0.3, fix: ais path
19
- - v0.0.2, fix: data parsing
20
- - v0.0.1, 1st version
1
+ # signalk-vessels-to-ais
2
+ [![npm version](https://badge.fury.io/js/signalk-vessels-to-ais.svg)](https://badge.fury.io/js/signalk-vessels-to-ais)
3
+ [![Known Vulnerabilities](https://snyk.io/test/github/KEGustafsson/signalk-vessels-to-ais/badge.svg)](https://snyk.io/test/github/KEGustafsson/signalk-vessels-to-ais)
4
+
5
+ SignalK server plugin to convert other vessel data to NMEA0183 AIS format and forward it out to 3rd party applications.
6
+
7
+ User can configure:
8
+ - How often data is sent out
9
+ - Own data can be added to AIS sending
10
+
11
+ New:
12
+ - v1.2.0, updated fetch method, no need for NODE_TLS_REJECT_UNAUTHORIZED=0 anymore
13
+ - v1.1.5, updated vessels within selected timeframe are sent out, radius filtering around own vessel and tag-block option added
14
+ - v1.1.4, small fix
15
+ - v1.1.3, add: own vessel data and sending interval modified
16
+ - v1.1.2, fix: http/https url selection and better error info
17
+ - v1.1.1, fix: current status of the plugin updated
18
+ - v1.1.0, fix: numeric value test for text strings of AIS
19
+ - v1.0.0, v1 release
20
+ - v0.0.6, fix: node-fetch issue with self signed cert
21
+ - v0.0.5, fix: callSign default value
22
+ - v0.0.4, fix: beam calc
23
+ - v0.0.3, fix: ais path
24
+ - v0.0.2, fix: data parsing
25
+ - v0.0.1, 1st version
package/index.js CHANGED
@@ -1,309 +1,442 @@
1
- /*
2
- MIT License
3
-
4
- Copyright (c) 2020 Karl-Erik Gustafsson
5
-
6
- Permission is hereby granted, free of charge, to any person obtaining a copy
7
- of this software and associated documentation files (the "Software"), to deal
8
- in the Software without restriction, including without limitation the rights
9
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
- copies of the Software, and to permit persons to whom the Software is
11
- furnished to do so, subject to the following conditions:
12
-
13
- The above copyright notice and this permission notice shall be included in all
14
- copies or substantial portions of the Software.
15
-
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
- SOFTWARE.
23
- */
24
-
25
- const fetch = require('node-fetch');
26
- const AisEncode = require('ggencoder').AisEncode;
27
-
28
- module.exports = function createPlugin(app) {
29
- const plugin = {};
30
- plugin.id = 'signalk-vessels-to-ais';
31
- plugin.name = 'Other vessels data to AIS NMEA0183';
32
- plugin.description = 'SignalK server plugin to convert other vessel data to NMEA0183 AIS format and forward it out to 3rd party applications';
33
-
34
- let positionUpdate = null;
35
- let url;
36
- let intervalStart;
37
- let intervalRun;
38
- let readInfo;
39
- const setStatus = app.setPluginStatus || app.setProviderStatus;
40
-
41
- plugin.start = function (options, restartPlugin) {
42
- process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
43
- url = 'https://localhost:3443/signalk/v1/api/vessels';
44
- fetch(url, { method: 'GET' })
45
- .then((res) => {
46
- console.log(`${plugin.id}: SSL enabled, using https`);
47
- if (!res.ok) {
48
- console.error(`${plugin.id}: SSL enabled, but error accessing server. Check 'Allow Readonly Access' and enable it.`);
49
- setStatus("Error accessing server. Check 'Allow Readonly Access' and enable it");
50
- }
51
- })
52
- .catch(() => {
53
- url = 'http://localhost:3000/signalk/v1/api/vessels';
54
- fetch(url, { method: 'GET' })
55
- .then((res) => {
56
- console.log(`${plugin.id}: SSL disabled, using http`);
57
- if (!res.ok) {
58
- console.error(`${plugin.id}: SSL disabled, but error accessing server. Check 'Allow Readonly Access' and enable it.`);
59
- setStatus("Error accessing server. Check 'Allow Readonly Access' and enable it");
60
- }
61
- })
62
- .catch(() => {
63
- });
64
- });
65
-
66
- positionUpdate = options.position_update;
67
- app.debug('Plugin started');
68
-
69
- function clear() {
70
- clearInterval(intervalStart);
71
- }
72
-
73
- intervalStart = setInterval(readInfo, (15000));
74
- setTimeout(clear, 15000);
75
- intervalRun = setInterval(readInfo, (positionUpdate * 60000));
76
- };
77
-
78
- //----------------------------------------------------------------------------
79
- // State Mapping
80
-
81
- const stateMapping = {
82
- motoring: 0,
83
- anchored: 1,
84
- 'not under command': 2,
85
- 'restricted manouverability': 3,
86
- 'constrained by draft': 4,
87
- moored: 5,
88
- aground: 6,
89
- fishing: 7,
90
- sailing: 8,
91
- 'hazardous material high speed': 9,
92
- 'hazardous material wing in ground': 10,
93
- 'ais-sart': 14,
94
- default: 15,
95
- };
96
-
97
- //----------------------------------------------------------------------------
98
- // Rad to Deg
99
- function radToDegrees(radians) {
100
- const pi = Math.PI;
101
- return ((radians * 180) / pi);
102
- }
103
-
104
- //----------------------------------------------------------------------------
105
- // m/s to knots
106
- function msToKnots(speed) {
107
- return ((speed * 3.6) / 1.852);
108
- }
109
-
110
- //----------------------------------------------------------------------------
111
- // nmea out
112
-
113
- function aisOut(encMsg) {
114
- const enc = new AisEncode(encMsg);
115
- const sentence = enc.nmea;
116
- if (sentence && sentence.length > 0) {
117
- app.debug(sentence);
118
- app.emit('nmea0183out', sentence);
119
- }
120
- }
121
-
122
- //----------------------------------------------------------------------------
123
- // Read and parse AIS data
124
-
125
- readInfo = function readData() {
126
- let i, mmsi, shipName, lat, lon, sog, cog, rot, navStat, hdg, dst, callSign, imo, id, type;
127
- let draftCur, length, beam, ais, encMsg3, encMsg5, encMsg18, encMsg240, encMsg241;
128
- fetch(url, { method: 'GET' })
129
- .then((res) => res.json())
130
- .then((json) => {
131
- const jsonContent = JSON.parse(JSON.stringify(json));
132
- const numberAIS = Object.keys(jsonContent).length;
133
- for (i = 1; i < numberAIS; i++) {
134
- const jsonKey = Object.keys(jsonContent)[i];
135
-
136
- try {
137
- mmsi = jsonContent[jsonKey].mmsi;
138
- } catch (error) { mmsi = null; }
139
- try {
140
- shipName = jsonContent[jsonKey].name;
141
- } catch (error) { shipName = ''; }
142
- try {
143
- lat = jsonContent[jsonKey].navigation.position.value.latitude;
144
- } catch (error) { lat = null; }
145
- try {
146
- lon = jsonContent[jsonKey].navigation.position.value.longitude;
147
- } catch (error) { lon = null; }
148
- try {
149
- sog = msToKnots(jsonContent[jsonKey].navigation.speedOverGround.value);
150
- } catch (error) { sog = null; }
151
- try {
152
- cog = radToDegrees(jsonContent[jsonKey].navigation.courseOverGroundTrue.value);
153
- } catch (error) { cog = null; }
154
- try {
155
- rot = radToDegrees(jsonContent[jsonKey].navigation.rateOfTurn.value);
156
- } catch (error) { rot = null; }
157
- try {
158
- navStat = stateMapping[jsonContent[jsonKey].navigation.state.value];
159
- } catch (error) { navStat = ''; }
160
- try {
161
- hdg = radToDegrees(jsonContent[jsonKey].navigation.headingTrue.value);
162
- } catch (error) { hdg = null; }
163
- try {
164
- dst = jsonContent[jsonKey].navigation.destination.commonName.value;
165
- } catch (error) { dst = ''; }
166
- try {
167
- callSign = jsonContent[jsonKey].communication.callsignVhf;
168
- } catch (error) { callSign = ''; }
169
- try {
170
- imo = (jsonContent[jsonKey].registrations.imo).substring(4, 20);
171
- } catch (error) { imo = null; }
172
- try {
173
- id = jsonContent[jsonKey].design.aisShipType.value.id;
174
- } catch (error) { id = null; }
175
- try {
176
- type = jsonContent[jsonKey].design.aisShipType.value.name;
177
- } catch (error) { type = ''; }
178
- try {
179
- draftCur = (jsonContent[jsonKey].design.draft.value.current) / 10;
180
- } catch (error) { draftCur = null; }
181
- try {
182
- length = jsonContent[jsonKey].design.length.value.overall;
183
- } catch (error) { length = null; }
184
- try {
185
- beam = (jsonContent[jsonKey].design.beam.value) / 2;
186
- } catch (error) { beam = null; }
187
- try {
188
- ais = jsonContent[jsonKey].sensors.ais.class.value;
189
- } catch (error) { ais = null; }
190
-
191
- if (shipName % 1 === 0) {
192
- shipName = '';
193
- }
194
- if (dst % 1 === 0) {
195
- dst = '';
196
- }
197
- if (callSign % 1 === 0) {
198
- callSign = '';
199
- }
200
- if (type % 1 === 0) {
201
- type = '';
202
- }
203
-
204
- encMsg3 = {
205
- aistype: 3, // class A position report
206
- repeat: 0,
207
- mmsi,
208
- navstatus: navStat,
209
- sog,
210
- lon,
211
- lat,
212
- cog,
213
- hdg,
214
- rot,
215
- };
216
-
217
- encMsg5 = {
218
- aistype: 5, // class A static
219
- repeat: 0,
220
- mmsi,
221
- imo,
222
- cargo: id,
223
- callsign: callSign,
224
- shipname: shipName,
225
- draught: draftCur,
226
- destination: dst,
227
- dimA: 0,
228
- dimB: length,
229
- dimC: beam,
230
- dimD: beam,
231
- };
232
-
233
- encMsg18 = {
234
- aistype: 18, // class B position report
235
- repeat: 0,
236
- mmsi,
237
- sog,
238
- accuracy: 0,
239
- lon,
240
- lat,
241
- cog,
242
- hdg,
243
- };
244
-
245
- encMsg240 = {
246
- aistype: 24, // class B static
247
- repeat: 0,
248
- part: 0,
249
- mmsi,
250
- shipname: shipName,
251
- };
252
-
253
- encMsg241 = {
254
- aistype: 24, // class B static
255
- repeat: 0,
256
- part: 1,
257
- mmsi,
258
- cargo: id,
259
- callsign: callSign,
260
- dimA: 0,
261
- dimB: length,
262
- dimC: beam,
263
- dimD: beam,
264
- };
265
-
266
- if (ais === 'A') {
267
- app.debug(`class A ${i}`);
268
- aisOut(encMsg3);
269
- aisOut(encMsg5);
270
- }
271
- if (ais === 'B') {
272
- app.debug(`class B ${i}`);
273
- aisOut(encMsg18);
274
- aisOut(encMsg240);
275
- aisOut(encMsg241);
276
- }
277
- }
278
- const dateobj = new Date(Date.now());
279
- const date = dateobj.toISOString();
280
- app.handleMessage(plugin.id, {
281
- context: `vessels.${app.selfId}`,
282
- updates: [
283
- ],
284
- });
285
- setStatus(`Number of AIS targets sent: ${numberAIS - 1} (${date})`);
286
- })
287
- .catch((err) => console.error(err));
288
- };
289
-
290
- //----------------------------------------------------------------------------
291
-
292
- plugin.stop = function stop() {
293
- clearInterval(intervalRun);
294
- app.debug('Stopped');
295
- };
296
-
297
- plugin.schema = {
298
- type: 'object',
299
- properties: {
300
- position_update: {
301
- type: 'integer',
302
- default: 1,
303
- title: 'How often AIS data is sent to NMEA0183 out (in minutes)',
304
- },
305
- },
306
- };
307
-
308
- return plugin;
309
- };
1
+ /*
2
+ MIT License
3
+
4
+ Copyright (c) 2020 Karl-Erik Gustafsson
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
23
+ */
24
+
25
+ const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));
26
+ const https = require('https');
27
+ const AisEncode = require('ggencoder').AisEncode;
28
+ const moment = require('moment');
29
+ const haversine = require('haversine-distance');
30
+
31
+ module.exports = function createPlugin(app) {
32
+ const plugin = {};
33
+ plugin.id = 'signalk-vessels-to-ais';
34
+ plugin.name = 'Other vessels data to AIS NMEA0183';
35
+ plugin.description = 'SignalK server plugin to convert other vessel data to NMEA0183 AIS format and forward it out to 3rd party applications';
36
+
37
+ let positionUpdate = null;
38
+ let distance;
39
+ let sendOwn;
40
+ let url;
41
+ let intervalRun;
42
+ const setStatus = app.setPluginStatus || app.setProviderStatus;
43
+
44
+ let position_update;
45
+ let useTag;
46
+
47
+ const httpsAgent = new https.Agent({
48
+ rejectUnauthorized: false,
49
+ });
50
+
51
+ let getParam;
52
+
53
+ plugin.start = function (options, restartPlugin) {
54
+ position_update = options.position_update * 60;
55
+ useTag = options.useTag;
56
+
57
+ positionUpdate = options.position_update;
58
+ distance = options.distance;
59
+ sendOwn = options.sendOwn;
60
+
61
+ let port = options.port || 3000;
62
+ let portSec = options.portSec || 3443;
63
+
64
+ url = 'https://localhost:' + portSec + '/signalk/v1/api/vessels';
65
+ getParam = { method: 'GET', agent: httpsAgent };
66
+ fetch(url, getParam)
67
+ .then((res) => {
68
+ console.log(`${plugin.id}: SSL enabled, using https`);
69
+ if (!res.ok) {
70
+ console.error(`${plugin.id}: SSL enabled, but error accessing server. Check 'Allow Readonly Access' and enable it.`);
71
+ setStatus("Error accessing server. Check 'Allow Readonly Access' and enable it");
72
+ }
73
+ })
74
+ .catch(() => {
75
+ url = 'http://localhost:' + port + '/signalk/v1/api/vessels';
76
+ getParam = { method: 'GET' };
77
+ fetch(url, getParam)
78
+ .then((res) => {
79
+ console.log(`${plugin.id}: SSL disabled, using http`);
80
+ if (!res.ok) {
81
+ console.error(`${plugin.id}: SSL disabled, but error accessing server. Check 'Allow Readonly Access' and enable it.`);
82
+ setStatus("Error accessing server. Check 'Allow Readonly Access' and enable it");
83
+ }
84
+ })
85
+ })
86
+ .finally(() => {
87
+ intervalRun = setInterval(readData, (positionUpdate * 60000), getParam);
88
+ });
89
+
90
+ app.debug('Plugin started');
91
+
92
+ };
93
+
94
+ //----------------------------------------------------------------------------
95
+ // State Mapping
96
+
97
+ const stateMapping = {
98
+ motoring: 0,
99
+ anchored: 1,
100
+ 'not under command': 2,
101
+ 'restricted manouverability': 3,
102
+ 'constrained by draft': 4,
103
+ moored: 5,
104
+ aground: 6,
105
+ fishing: 7,
106
+ sailing: 8,
107
+ 'hazardous material high speed': 9,
108
+ 'hazardous material wing in ground': 10,
109
+ 'ais-sart': 14,
110
+ default: 15,
111
+ };
112
+
113
+ //----------------------------------------------------------------------------
114
+ // Rad to Deg
115
+ function radToDegrees(radians) {
116
+ const pi = Math.PI;
117
+ return ((radians * 180) / pi);
118
+ }
119
+
120
+ //----------------------------------------------------------------------------
121
+ // m/s to knots
122
+ function msToKnots(speed) {
123
+ return ((speed * 3.6) / 1.852);
124
+ }
125
+
126
+ //----------------------------------------------------------------------------
127
+ // nmea out
128
+
129
+ function aisOut(encMsg, aisTime) {
130
+ const enc = new AisEncode(encMsg);
131
+ const sentence = enc.nmea;
132
+ let taggString = '';
133
+ if (useTag) {
134
+ taggString = createTagBlock(aisTime)
135
+ }
136
+ if (sentence && sentence.length > 0) {
137
+ app.debug(taggString+sentence);
138
+ app.emit('nmea0183out', taggString+sentence);
139
+ }
140
+ }
141
+
142
+ const m_hex = [
143
+ '0',
144
+ '1',
145
+ '2',
146
+ '3',
147
+ '4',
148
+ '5',
149
+ '6',
150
+ '7',
151
+ '8',
152
+ '9',
153
+ 'A',
154
+ 'B',
155
+ 'C',
156
+ 'D',
157
+ 'E',
158
+ 'F'
159
+ ]
160
+
161
+ function toHexString (v) {
162
+ let msn = (v >> 4) & 0x0f
163
+ let lsn = (v >> 0) & 0x0f
164
+ return m_hex[msn] + m_hex[lsn]
165
+ }
166
+
167
+ function createTagBlock (aisTime) {
168
+ let tagBlock = ''
169
+ tagBlock += 's:SK0001,'
170
+ //tagBlock += 'c:' + aisTime + ','
171
+ tagBlock += 'c:' + Date.now(aisTime) + ','
172
+ tagBlock = tagBlock.slice(0, - 1)
173
+ let tagBlockChecksum = 0
174
+ for (let i = 0; i < tagBlock.length; i++) {
175
+ tagBlockChecksum ^= tagBlock.charCodeAt(i)
176
+ }
177
+ return `\\${tagBlock}*` + toHexString(tagBlockChecksum) + `\\`
178
+ }
179
+
180
+ //----------------------------------------------------------------------------
181
+ // Read and parse AIS data
182
+
183
+ function readData(getParam) {
184
+ let i, mmsi, aisTime, aisDelay, shipName, lat, lon, sog, cog, rot, navStat, hdg, dst, callSign, imo, id, type;
185
+ let draftCur, length, beam, ais, encMsg3, encMsg5, encMsg18, encMsg240, encMsg241, own;
186
+ let ownLat = app.getSelfPath('navigation.position.value.latitude');
187
+ let ownLon = app.getSelfPath('navigation.position.value.longitude');
188
+ fetch(url, getParam)
189
+ .then((res) => res.json())
190
+ .then((json) => {
191
+ const jsonContent = JSON.parse(JSON.stringify(json));
192
+ const numberAIS = Object.keys(jsonContent).length;
193
+ for (i = 0; i < numberAIS; i++) {
194
+ const jsonKey = Object.keys(jsonContent)[i];
195
+
196
+ try {
197
+ aisTime = jsonContent[jsonKey].sensors.ais.class.timestamp;
198
+ if ((parseFloat((moment(new Date(Date.now())).diff(aisTime)/1000).toFixed(3))) < position_update) {
199
+ aisDelay = true;
200
+ } else {
201
+ aisDelay = false;
202
+ }
203
+ } catch (error) {
204
+ if (i === 0) {
205
+ aisTime = new Date(Date.now()).toISOString();
206
+ aisDelay = true;
207
+ } else {
208
+ aisTime = null;
209
+ aisDelay = false;
210
+ }
211
+ }
212
+ try {
213
+ mmsi = jsonContent[jsonKey].mmsi;
214
+ } catch (error) { mmsi = null; }
215
+ try {
216
+ shipName = jsonContent[jsonKey].name;
217
+ } catch (error) { shipName = ''; }
218
+ try {
219
+ lat = jsonContent[jsonKey].navigation.position.value.latitude;
220
+ } catch (error) { lat = null; }
221
+ try {
222
+ lon = jsonContent[jsonKey].navigation.position.value.longitude;
223
+ } catch (error) { lon = null; }
224
+ try {
225
+ sog = msToKnots(jsonContent[jsonKey].navigation.speedOverGround.value);
226
+ } catch (error) { sog = null; }
227
+ try {
228
+ cog = radToDegrees(jsonContent[jsonKey].navigation.courseOverGroundTrue.value);
229
+ } catch (error) { cog = null; }
230
+ try {
231
+ rot = radToDegrees(jsonContent[jsonKey].navigation.rateOfTurn.value);
232
+ } catch (error) { rot = null; }
233
+ try {
234
+ navStat = stateMapping[jsonContent[jsonKey].navigation.state.value];
235
+ } catch (error) { navStat = ''; }
236
+ try {
237
+ hdg = radToDegrees(jsonContent[jsonKey].navigation.headingTrue.value);
238
+ } catch (error) { hdg = null; }
239
+ try {
240
+ dst = jsonContent[jsonKey].navigation.destination.commonName.value;
241
+ } catch (error) { dst = ''; }
242
+ try {
243
+ if (i === 0) {
244
+ callSign = jsonContent[jsonKey].communication.callsignVhf;
245
+ } else {
246
+ callSign = jsonContent[jsonKey].communication.value.callsignVhf;
247
+ }
248
+ } catch (error) { callSign = ''; }
249
+ try {
250
+ imo = (jsonContent[jsonKey].registrations.value.imo).substring(4, 20);
251
+ } catch (error) { imo = null; }
252
+ try {
253
+ id = jsonContent[jsonKey].design.aisShipType.value.id;
254
+ } catch (error) { id = null; }
255
+ try {
256
+ type = jsonContent[jsonKey].design.aisShipType.value.name;
257
+ } catch (error) { type = ''; }
258
+ try {
259
+ draftCur = (jsonContent[jsonKey].design.draft.value.current) / 10;
260
+ } catch (error) { draftCur = null; }
261
+ try {
262
+ length = jsonContent[jsonKey].design.length.value.overall;
263
+ } catch (error) { length = null; }
264
+ try {
265
+ beam = (jsonContent[jsonKey].design.beam.value) / 2;
266
+ } catch (error) { beam = null; }
267
+ try {
268
+ ais = jsonContent[jsonKey].sensors.ais.class.value;
269
+ } catch (error) { ais = null; }
270
+
271
+ if (shipName % 1 === 0) {
272
+ shipName = '';
273
+ }
274
+ if (dst % 1 === 0) {
275
+ dst = '';
276
+ }
277
+ if (callSign % 1 === 0) {
278
+ callSign = '';
279
+ }
280
+ if (type % 1 === 0) {
281
+ type = '';
282
+ }
283
+
284
+ if (i === 0) {
285
+ own = true;
286
+ if (sendOwn) {
287
+ ais = 'A';
288
+ } else {
289
+ ais = '';
290
+ }
291
+ } else {
292
+ own = false;
293
+ }
294
+
295
+ const a = { lat: ownLat, lon: ownLon }
296
+ const b = { lat: lat, lon: lon }
297
+ let dist = (haversine(a, b)/1000).toFixed(2);
298
+
299
+ if (dist <= distance) {
300
+
301
+ encMsg3 = {
302
+ own,
303
+ aistype: 3, // class A position report
304
+ repeat: 0,
305
+ mmsi,
306
+ navstatus: navStat,
307
+ sog,
308
+ lon,
309
+ lat,
310
+ cog,
311
+ hdg,
312
+ rot,
313
+ };
314
+
315
+ encMsg5 = {
316
+ own,
317
+ aistype: 5, // class A static
318
+ repeat: 0,
319
+ mmsi,
320
+ imo,
321
+ cargo: id,
322
+ callsign: callSign,
323
+ shipname: shipName,
324
+ draught: draftCur,
325
+ destination: dst,
326
+ dimA: 0,
327
+ dimB: length,
328
+ dimC: beam,
329
+ dimD: beam,
330
+ };
331
+
332
+ encMsg18 = {
333
+ own,
334
+ aistype: 18, // class B position report
335
+ repeat: 0,
336
+ mmsi,
337
+ sog,
338
+ accuracy: 0,
339
+ lon,
340
+ lat,
341
+ cog,
342
+ hdg,
343
+ };
344
+
345
+ encMsg240 = {
346
+ own,
347
+ aistype: 24, // class B static
348
+ repeat: 0,
349
+ part: 0,
350
+ mmsi,
351
+ shipname: shipName,
352
+ };
353
+
354
+ encMsg241 = {
355
+ own,
356
+ aistype: 24, // class B static
357
+ repeat: 0,
358
+ part: 1,
359
+ mmsi,
360
+ cargo: id,
361
+ callsign: callSign,
362
+ dimA: 0,
363
+ dimB: length,
364
+ dimC: beam,
365
+ dimD: beam,
366
+ };
367
+
368
+ if (aisDelay && (ais === 'A' || ais === 'B')) {
369
+ app.debug("Distance range: " + distance + "km, AIS target distance: " + dist + "km" + ", Class " + ais + " Vessel" + ", MMSI:" + mmsi)
370
+ if (ais === 'A') {
371
+ app.debug(`class A, ${i}, time: ${aisTime}`);
372
+ aisOut(encMsg3, aisTime);
373
+ aisOut(encMsg5, aisTime);
374
+ }
375
+ if (ais === 'B') {
376
+ app.debug(`class B, ${i}, time: ${aisTime}`);
377
+ aisOut(encMsg18, aisTime);
378
+ aisOut(encMsg240, aisTime);
379
+ aisOut(encMsg241, aisTime);
380
+ }
381
+ app.debug("--------------------------------------------------------");
382
+
383
+ }
384
+ }
385
+ }
386
+ const dateobj = new Date(Date.now());
387
+ const date = dateobj.toISOString();
388
+ app.handleMessage(plugin.id, {
389
+ context: `vessels.${app.selfId}`,
390
+ updates: [
391
+ ],
392
+ });
393
+ setStatus(`AIS NMEA message send: ${date}`);
394
+ })
395
+ .catch((err) => console.error(err));
396
+ };
397
+
398
+ //----------------------------------------------------------------------------
399
+
400
+ plugin.stop = function stop() {
401
+ clearInterval(intervalRun);
402
+ app.debug('Stopped');
403
+ };
404
+
405
+ plugin.schema = {
406
+ type: 'object',
407
+ properties: {
408
+ position_update: {
409
+ type: 'number',
410
+ default: 1,
411
+ title: 'How often AIS data is sent to NMEA0183 out (in minutes). E.g. 0.5 = 30s, 1 = 1min',
412
+ },
413
+ port: {
414
+ type: 'number',
415
+ title: 'HTTP port',
416
+ default: 3000
417
+ },
418
+ portSec: {
419
+ type: 'number',
420
+ title: 'HTTPS port',
421
+ default: 3443
422
+ },
423
+ sendOwn: {
424
+ type: 'boolean',
425
+ title: 'Send own AIS data, VDO',
426
+ default: true
427
+ },
428
+ useTag: {
429
+ type: 'boolean',
430
+ title: 'Add Tag-block',
431
+ default: false
432
+ },
433
+ distance: {
434
+ type: 'integer',
435
+ default: 100,
436
+ title: 'AIS target within range [km]',
437
+ },
438
+ },
439
+ };
440
+
441
+ return plugin;
442
+ };
package/package.json CHANGED
@@ -1,24 +1,26 @@
1
- {
2
- "name": "signalk-vessels-to-ais",
3
- "version": "1.1.2",
4
- "description": "SignalK server plugin to convert other vessel data to NMEA0183 AIS format and forward it out to 3rd party applications",
5
- "main": "index.js",
6
- "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1"
8
- },
9
- "keywords": [
10
- "signalk-node-server-plugin"
11
- ],
12
- "repository": "https://github.com/KEGustafsson/signalk-vessels-to-ais",
13
- "author": "Karl-Erik Gustafsson",
14
- "license": "MIT",
15
- "dependencies": {
16
- "node-fetch": "^2.6.0",
17
- "ggencoder": "^1.0.4"
18
- },
19
- "devDependencies": {
20
- "eslint": "^7.17.0",
21
- "eslint-config-airbnb-base": "^14.2.1",
22
- "eslint-plugin-import": "^2.22.1"
23
- }
24
- }
1
+ {
2
+ "name": "signalk-vessels-to-ais",
3
+ "version": "1.2.0",
4
+ "description": "SignalK server plugin to convert other vessel data to NMEA0183 AIS format and forward it out to 3rd party applications",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "keywords": [
10
+ "signalk-node-server-plugin"
11
+ ],
12
+ "repository": "https://github.com/KEGustafsson/signalk-vessels-to-ais",
13
+ "author": "Karl-Erik Gustafsson",
14
+ "license": "MIT",
15
+ "dependencies": {
16
+ "haversine-distance": "^1.2.1",
17
+ "moment": "^2.29.1",
18
+ "node-fetch": "^3.1.1",
19
+ "ggencoder": "^1.0.5"
20
+ },
21
+ "devDependencies": {
22
+ "eslint": "^7.17.0",
23
+ "eslint-config-airbnb-base": "^14.2.1",
24
+ "eslint-plugin-import": "^2.22.1"
25
+ }
26
+ }