speedandcurrent 1.0.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/index.js ADDED
@@ -0,0 +1,592 @@
1
+ const { PolarStat, DeltaStat, DeltaBase, DeltaSubscribe, PolarDeltaBase, PolarDeltaCatch, PolarDeltaSubscribe, SI, Reporter } = require('signalkutilities');
2
+ const { CorrectionTable } = require('./correctionTable.js');
3
+ const { KalmanFilter } = require('kalman-filter');
4
+
5
+
6
+
7
+
8
+ module.exports = function (app) {
9
+
10
+ let unsubscribes = [];
11
+ let options = {};
12
+ let isRunning = false;
13
+ let corrTable = null;
14
+ let lastSave = null;
15
+ var residual, heading, attitude, boatSpeed, boatSpeedPolar, correctedBoatSpeed, groundSpeed, current, currentDelta, speedCorrection, reporter;
16
+
17
+
18
+ const plugin = {};
19
+ plugin.id = "SpeedAndCurrent";
20
+ plugin.name = "Speed and current";
21
+ plugin.description = "An experimental plugin that uses sensor fusion to get boat speed, current and leeway.";
22
+
23
+
24
+
25
+ plugin.schema = {
26
+ type: "object",
27
+ properties: {
28
+ mode: {
29
+ type: "string",
30
+ title: "Plugin mode",
31
+ description: "The mode of the plugin.",
32
+ enum: ["Start with new correction table, no current",
33
+ "Start with new correction table, with current",
34
+ "Fresh correction table, no current",
35
+ "Fresh correction table, with current",
36
+ "Mature correction table, no current",
37
+ "Mature correction table, with current",
38
+ "Locked correction table",
39
+ "Manual configuration"],
40
+ default: "Start with new correction table, no current",
41
+ },
42
+ heelStep: {
43
+ type: "number",
44
+ title: "Step size for heel",
45
+ description: "Correction table stepsize for heel.",
46
+ default: 8
47
+ },
48
+ maxHeel: {
49
+ type: "number",
50
+ title: "Maximum heel in correction table",
51
+ description: "Correction table maximum heel.",
52
+ default: 32
53
+ },
54
+ speedStep: {
55
+ type: "number",
56
+ title: "Step size for speed",
57
+ description: "Correction table stepsize for speed.",
58
+ default: 1
59
+ },
60
+ maxSpeed: {
61
+ type: "number",
62
+ title: "Maximum speed in correction table",
63
+ description: "Correction table maximum speed.",
64
+ default: 9
65
+ },
66
+ preventDuplication: {
67
+ type: "boolean",
68
+ title: "Prevent duplication of boat speed",
69
+ description: "Overwrite boat speed from sensor with corrected boat speed.",
70
+ default: true,
71
+ },
72
+
73
+ updateCorrectionTable: {
74
+ type: "boolean",
75
+ title: "Update correction table",
76
+ description: "Update estimations in the correction table.",
77
+ default: true,
78
+ },
79
+ estimateBoatSpeed: {
80
+ type: "boolean",
81
+ title: "Correct boat speed",
82
+ description: "Correct boat speed using correction table. This includes adding leeway.",
83
+ default: true,
84
+ },
85
+ correctionStability: {
86
+ type: "number",
87
+ title: "Stability of correction estimation",
88
+ description: `The stability of the correction estimation indicates how much the estimation is trusted
89
+ opposed to the calculation of . Biggeboat speed. Bigger values give more trust to the estimation and more stable estimations`,
90
+ default: 7,
91
+ minimum: 5,
92
+ maximum: 12
93
+ },
94
+ doStartFresh: {
95
+ type: "boolean",
96
+ title: "Empty correction table",
97
+ description: "Restart with a fresh correction table. WARNING: all current corrections will be lost permanently!",
98
+ default: false
99
+ },
100
+ assumeCurrent: {
101
+ type: "boolean",
102
+ title: "assume current",
103
+ description: "The estimator assumes that there is current.",
104
+ default: false,
105
+ },
106
+ estimateCurrent: {
107
+ type: "boolean",
108
+ title: "Estimate current",
109
+ description: "Estimate water current.",
110
+ default: true,
111
+ },
112
+ currentStability: {
113
+ type: "number",
114
+ title: "Stability of current estimation ",
115
+ description: `The stability of the current estimation indicates how much the estimation is trusted
116
+ opposed to the calculation of current. Bigger values give more trust to the estimation and more sable estimations`,
117
+ default: 5,
118
+ minimum: 1,
119
+ maximum: 8
120
+ }
121
+ }
122
+ };
123
+
124
+ plugin.uiSchema = {
125
+ 'ui:order': ["mode", "maxSpeed", "speedStep", "maxHeel", "heelStep", "preventDuplication", "updateCorrectionTable", "estimateBoatSpeed", "correctionStability", "doStartFresh", "assumeCurrent", "estimateCurrent", "currentStability"],
126
+ doStartFresh: {
127
+ "ui:widget": "hidden",
128
+ },
129
+ heelStep: {
130
+ "ui:widget": "updown",
131
+ },
132
+ maxHeel: {
133
+ "ui:widget": "updown",
134
+ },
135
+ speedStep: {
136
+ "ui:widget": "updown",
137
+ },
138
+ maxSpeed: {
139
+ "ui:widget": "updown",
140
+ },
141
+ mode: {
142
+ "ui:widget": "select",
143
+ },
144
+ updateCorrectionTable: {
145
+ "ui:widget": "hidden",
146
+ },
147
+ estimateBoatSpeed: {
148
+ "ui:widget": "hidden",
149
+ },
150
+ correctionStability: {
151
+ "ui:widget": "hidden",
152
+ },
153
+ preventDuplication: {
154
+ "ui:widget": "checkbox",
155
+ },
156
+ assumeCurrent: {
157
+ "ui:widget": "hidden",
158
+ },
159
+ estimateCurrent: {
160
+ "ui:widget": "hidden",
161
+ },
162
+ currentStability: {
163
+ "ui:widget": "hidden",
164
+ }
165
+ };
166
+
167
+ plugin.registerWithRouter = function (router) {
168
+ app.debug('registerWithRouter');
169
+
170
+ router.get('/getResults', (req, res) => {
171
+ if (!isRunning) {
172
+ res.status(503).json({ error: "Plugin is not running" });
173
+ }
174
+ else {
175
+ res.json(reporter?.getReport());
176
+ }
177
+
178
+ });
179
+
180
+ router.get('/getVectors', (req, res) => {
181
+ if (!isRunning) {
182
+ res.status(503).json({ error: "Plugin is not running" });
183
+ }
184
+ else {
185
+ const v = { deltas: [], polars: [] };
186
+ const d = [heading, attitude];
187
+ const p = [groundSpeed, boatSpeedPolar, correctedBoatSpeed, current, speedCorrection, residual];
188
+ p.forEach(polar => {
189
+ v.polars.push({
190
+ id: polar.id,
191
+ plane: polar.plane,
192
+ label: polar.label,
193
+ speed: polar.pValue.speed,
194
+ angle: polar.pValue.angle,
195
+ });
196
+ });
197
+ d.forEach(delta => {
198
+ v.deltas.push({
199
+ id: delta.id,
200
+ value: delta.value,
201
+ });
202
+ });
203
+ res.json(v);
204
+ }
205
+ });
206
+
207
+ }
208
+
209
+ function saveTable(options, correctionTable) {
210
+ options.correctionTable = correctionTable.toJSON();
211
+ app.savePluginOptions(options, () => { app.debug('Correction table saved') });
212
+ return new Date();
213
+ }
214
+
215
+
216
+ plugin.start = (opts) => {
217
+ app.debug("plugin started");
218
+
219
+ // make options survive to the stop function
220
+ options = opts;
221
+ loadPresets();
222
+
223
+ // correction table
224
+ const table = loadTable(options);
225
+
226
+
227
+ // heading
228
+ // TBD: headingTrue or headingMagnetic
229
+ heading = new DeltaSubscribe("navigation.headingTrue");
230
+ heading.subscribe(app, plugin.id, unsubscribes);
231
+ heading.setDisplayAttributes("heading","heading");
232
+
233
+ //attitude
234
+ attitude = new DeltaSubscribe("navigation.attitude");
235
+ attitude.value = { pitch: 0, roll: 0, yaw: 0 }; //prevents errors when there is no attitude sensor
236
+ attitude.setDisplayAttributes("attitude","attitude");
237
+ attitude.subscribe(app, plugin.id, unsubscribes);
238
+
239
+ // current
240
+ currentDelta = new DeltaBase("environment.current");
241
+ current = new PolarDeltaBase();
242
+ current.setDisplayAttributes("current", "ref_ground", "current");
243
+
244
+ const currentStat = new PolarStat(current);
245
+
246
+ // magnetic variance
247
+ variance = new DeltaSubscribe("navigation.magneticVariation");
248
+
249
+ // boat speed
250
+ if (options.preventDuplication) {
251
+ // DeltaCatch needed here
252
+ boatSpeed = new DeltaSubscribe("navigation.speedThroughWater");
253
+ boatSpeed.subscribe(app, plugin.id, unsubscribes, calculate);
254
+ }
255
+ else {
256
+ boatSpeed = new DeltaSubscribe("navigation.speedThroughWater");
257
+ boatSpeed.subscribe(app, plugin.id, unsubscribes, calculate);
258
+ }
259
+
260
+ boatSpeedPolar = new PolarDeltaBase("navigation.speedThroughWater", "environment.wind.directionTruenavigation.leewayAngle");
261
+ boatSpeedPolar.setDisplayAttributes("boatSpeed", "ref_boat", "observed boat speed");
262
+ const boatSpeedStat = new PolarStat(boatSpeedPolar);
263
+
264
+ correctedBoatSpeed = new PolarDeltaBase("navigation.speedThroughWater", "environment.wind.directionTruenavigation.leewayAngle");
265
+ correctedBoatSpeed.setDisplayAttributes("correctedSpeed", "ref_boat", "estimated boat Speed");
266
+
267
+ boatSpeedRefGround = new PolarDeltaBase();
268
+ boatSpeedRefGround.setDisplayAttributes("boatSpeedRefGround", "ref_ground","boat speed over ground");
269
+
270
+
271
+ // ground speed
272
+ groundSpeed = new PolarDeltaSubscribe("navigation.speedOverGround", "navigation.courseOverGroundTrue");
273
+ groundSpeed.subscribe(app, plugin.id, unsubscribes);
274
+ groundSpeed.setDisplayAttributes("groundSpeed", "ref_ground", "observed speed over ground");
275
+ const groundSpeedStat = new PolarStat(groundSpeed);
276
+
277
+ // correction vector
278
+ speedCorrection = new PolarDeltaBase();
279
+ speedCorrection.setDisplayAttributes("correction", "ref_boat", "correction");
280
+
281
+ // residual
282
+ residual = new PolarDeltaBase();
283
+ residual.setDisplayAttributes("residual", "ref_ground", "residual");
284
+
285
+ // Make reporting object for webApp
286
+ reporter = new Reporter();
287
+
288
+ // filter for current
289
+ stability = 10 ** (options.currentStability);
290
+ const currentEstimator = new KalmanFilter({
291
+ observation: {
292
+ stateProjection: [[1, 0], [0, 1]], // observation matrix H
293
+ covariance: [[1 , 0], [0, 1 ]], //measurement noise R
294
+ dimension: 2
295
+ },
296
+ dynamic: {
297
+ transition: [[1, 0], [0, 1]], // state transition matrix F
298
+ covariance: [1 / stability, 1 / stability],// process noise covariance matrix Q
299
+ init: {
300
+ mean: [[0], [0]],
301
+ covariance: [[1 / stability, 0], [0, 1 / stability]]
302
+ }
303
+ }
304
+ });
305
+ let currentEstimation = null;
306
+
307
+ // filter for boat speed
308
+ stability = 10 ** (1);
309
+ const boatSpeedEstimator = new KalmanFilter({
310
+ observation: {
311
+ stateProjection: [[1, 0], [0, 1]], // observation matrix H
312
+ covariance: [[1 * stability, 0], [0, 1 * stability]], //measurement noise R
313
+ dimension: 2
314
+ },
315
+ dynamic: {
316
+ transition: [[1, 0], [0, 1]], // state transition matrix F
317
+ covariance: [1, 1],// process noise covariance matrix Q
318
+ }
319
+ });
320
+ let boatSpeedEstimation = null;
321
+
322
+
323
+ isRunning = true;
324
+
325
+
326
+
327
+ // Main function, estimates boatSpeed, leeway and current
328
+ function calculate(timestamp) {
329
+ // prepare iteration
330
+ boatSpeedPolar.setVValue({ x: boatSpeed.value, y: 0 });
331
+ groundSpeedStat.sample();
332
+ currentStat.sample();
333
+ boatSpeedStat.sample();
334
+ heel = attitude.value.roll;
335
+ speed = boatSpeed.value;
336
+ theta = heading.value;
337
+ if (!Number.isFinite(heel)) {
338
+ app.debug("Heel is not a valid number, skipping calculation");
339
+ return;
340
+ }
341
+ if (!Number.isFinite(speed)) {
342
+ app.debug("Speed is not a valid number, skipping calculation");
343
+ return;
344
+ }
345
+ if (!Number.isFinite(theta)) {
346
+ app.debug("Heading is not a valid number, skipping calculation");
347
+ return;
348
+ }
349
+ reporter.newReport();
350
+ reporter.addOptions(options);
351
+ reporter.addPolar( groundSpeed);
352
+ reporter.addPolar( boatSpeedPolar);
353
+ reporter.addAttitude( attitude);
354
+ reporter.addDelta( heading);
355
+
356
+ // update correction table
357
+ if (options.updateCorrectionTable) {
358
+ table.update(speed, heel, groundSpeedStat, currentStat, boatSpeedStat, theta);
359
+ }
360
+ reporter.addTable(table, speed, heel);
361
+
362
+ // correct and estimate boat speed
363
+ estimateBoatSpeed();
364
+
365
+ if (options.assumeCurrent) {
366
+ // estimate current ;
367
+ estimateCurrent();
368
+ }
369
+ calcResidual();
370
+
371
+
372
+ // Send calculated delta's
373
+ if (options.estimateCurrent) {
374
+ // translate current into signalK data structure
375
+ const c = current.getPValue();
376
+ const cc = { drift: current.speed, setTrue: current.angle, setMagnetic: (current.angle - variance.value + Math.PI) % (2 * Math.PI) - Math.PI };
377
+ currentDelta.setValue(cc);
378
+ DeltaBase.sendDeltas(app, plugin.id, [currentDelta]);
379
+ }
380
+ if (options.estimateBoatSpeed) PolarDeltaBase.sendDeltas(app, plugin.id, [correctedBoatSpeed]);
381
+
382
+ // Save correction table
383
+ // periodically
384
+ if (options.updateCorrectionTable && new Date() - lastSave > 5 * 60 * 1000) {
385
+ lastSave = saveTable(options, table);
386
+ }
387
+ }
388
+
389
+ // estimates boat speed from observed boatSpeed and correction
390
+ function estimateBoatSpeed() {
391
+ cor = table.getKalmanCorrection(speed, heel);
392
+ // app.debug(cor);
393
+ correctedBoatSpeed.copyFrom(boatSpeedPolar);
394
+ speedCorrection.setVValue(cor);
395
+
396
+ reporter.addPolar(speedCorrection);
397
+ correctedBoatSpeed.add(speedCorrection);
398
+ const v = correctedBoatSpeed.getVValue();
399
+ const observation = [v.x, v.y];
400
+ boatSpeedEstimation = boatSpeedEstimator.filter({ previousCorrected: boatSpeedEstimation, observation });
401
+ correctedBoatSpeed.setKValue(boatSpeedEstimation);
402
+ reporter.addPolar(correctedBoatSpeed);
403
+ }
404
+
405
+ // estimates current from observed groundspeed and estimated boatspeed
406
+ function estimateCurrent() {
407
+ boatSpeedRefGround.copyFrom(correctedBoatSpeed);
408
+ boatSpeedRefGround.rotate(theta);
409
+ reporter.addPolar( boatSpeedRefGround);
410
+ current.copyFrom(groundSpeed);
411
+ current.substract(boatSpeedRefGround);
412
+ const v = current.getVValue();
413
+ const observation = [v.x, v.y];
414
+ currentEstimation = currentEstimator.filter({ previousCorrected: currentEstimation, observation });
415
+ current.setKValue(currentEstimation);
416
+ reporter.addPolar( current);
417
+ }
418
+
419
+ // Calculate what ground speed is observed that is not attributed to estimated boat speed or estimated current
420
+ function calcResidual() {
421
+ residual.copyFrom(groundSpeed);
422
+ boatSpeedRefGround.copyFrom(correctedBoatSpeed);
423
+ boatSpeedRefGround.rotate(theta);
424
+ residual.substract(boatSpeedRefGround);
425
+ residual.substract(current);
426
+ reporter.addPolar(residual);
427
+ }
428
+
429
+ // Make sure the table matches the settings
430
+ function enforceConsistancy(row, col ) {
431
+ // function is bugged due to IS units vs knots and degrees
432
+ if (!options?.correctionTable) return false;
433
+ const table = options.correctionTable;
434
+ if (!table) return false;
435
+ if (!table.row) return false;
436
+ if (table.row.min != row.min) return false;
437
+ if (table.row.max != row.max) return false;
438
+ if (table.row.step != row.step) return false;
439
+ if (table.col.min != col.min) return false;
440
+ if (table.col.max != col.max) return false;
441
+ if (table.col.step != col.step) return false;
442
+ return true;
443
+ }
444
+
445
+ function loadTable(options) {
446
+ const row = { min: 0, max: SI.fromKnots(options.maxSpeed), step: SI.fromKnots(options.speedStep) };
447
+ const col = { min: -SI.fromDegrees(options.maxHeel), max: SI.fromDegrees(options.maxHeel), step: SI.fromDegrees(options.heelStep) };
448
+ let table;
449
+ if ( enforceConsistancy(row, col) && !options.doStartFresh) {
450
+ table = CorrectionTable.fromJSON(options.correctionTable, options.correctionStability);
451
+ app.debug("Correction table loaded");
452
+ lastSave = new Date();
453
+ }
454
+ else {
455
+ table = new CorrectionTable(row, col, options.correctionStability);
456
+ app.debug("Correction table created");
457
+ options.doStartFresh = false;
458
+ lastSave = saveTable(options, table);
459
+ }
460
+ table.setDisplayAttributes("correctionTable", "Speed / Heel");
461
+ return table;
462
+ }
463
+
464
+
465
+ function loadPresets() {
466
+ const modeEnum = plugin.schema.properties.mode.enum;
467
+ app.debug("Loading presests for mode: " + options.mode);
468
+
469
+ switch (modeEnum.indexOf(options.mode)) {
470
+ case -1:
471
+ app.debug("Invalid mode");
472
+ app.debug(options);
473
+ //options.mode = modeEnum[2];
474
+ break;
475
+ case 0:
476
+ // new table, no current
477
+ options.doStartFresh = true;
478
+ options.updateCorrectionTable = true;
479
+ options.estimateBoatSpeed = false;
480
+ options.assumeCurrent = false;
481
+ options.estimateCurrent = false;
482
+ options.correctionStability = 5;
483
+ options.mode = modeEnum[2];
484
+ break;
485
+ case 1:
486
+ // new table, with current
487
+ options.doStartFresh = true;
488
+ options.updateCorrectionTable = true;
489
+ options.estimateBoatSpeed = false;
490
+ options.assumeCurrent = true;
491
+ options.estimateCurrent = false;
492
+ options.correctionStability = 5;
493
+ options.currentStability = 7;
494
+ options.mode = modeEnum[3];
495
+ break;
496
+ case 2:
497
+ // fresh table, no current
498
+ options.doStartFresh = false;
499
+ options.updateCorrectionTable = true;
500
+ options.estimateBoatSpeed = false;
501
+ options.assumeCurrent = false;
502
+ options.estimateCurrent = false;
503
+ options.correctionStability = 6;
504
+ break;
505
+ case 3:
506
+ // fresh table, with current
507
+ options.doStartFresh = false;
508
+ options.updateCorrectionTable = true;
509
+ options.estimateBoatSpeed = false;
510
+ options.assumeCurrent = true;
511
+ options.estimateCurrent = false;
512
+ options.correctionStability = 6;
513
+ options.currentStability = 6;
514
+ break;
515
+ case 4:
516
+ // mature table, no current
517
+ options.doStartFresh = false;
518
+ options.updateCorrectionTable = true;
519
+ options.estimateBoatSpeed = true;
520
+ options.assumeCurrent = false;
521
+ options.estimateCurrent = false;
522
+ options.correctionStability = 8;
523
+ break;
524
+ case 5:
525
+ // mature table, with current
526
+ options.doStartFresh = false;
527
+ options.updateCorrectionTable = true;
528
+ options.estimateBoatSpeed = true;
529
+ options.assumeCurrent = true;
530
+ options.estimateCurrent = true;
531
+ options.correctionStability = 8;
532
+ options.currentStability = 3;
533
+ break;
534
+ case 6:
535
+ // locked table
536
+ options.doStartFresh = false;
537
+ options.updateCorrectionTable = false;
538
+ options.estimateBoatSpeed = false;
539
+ options.assumeCurrent = true;
540
+ options.estimateCurrent = true;
541
+ options.currentStability = 2;
542
+ break;
543
+ case 7:
544
+ // all manual configuration;
545
+ break;
546
+ }
547
+ }
548
+
549
+ // compare floats
550
+ function isEqual(a, b) {
551
+ const TOLERANCE = 1e-5;
552
+ if (Math.abs(a - b) < TOLERANCE) {
553
+ return true;
554
+ }
555
+ return false;
556
+ }
557
+ }
558
+
559
+
560
+ plugin.stop = () => {
561
+ return new Promise((resolve, reject) => {
562
+ try {
563
+ unsubscribes.forEach(f => f());
564
+ unsubscribes = [];
565
+ //corrTable.save(app);
566
+ if (corrTable != null) {
567
+ lastSave = saveTable(options, corrTable);
568
+ }
569
+ options = {};
570
+
571
+ residual = null;
572
+ heading = null;
573
+ attitude = null;
574
+ boatSpeed = null;
575
+ boatSpeedPolar = null;
576
+ correctedBoatSpeed = null;
577
+ groundSpeed = null;
578
+ current = null;
579
+ currentDelta = null;
580
+ speedCorrection = null;
581
+ reporter = null;
582
+ isRunning = false;
583
+ corrTable = null;
584
+
585
+ resolve();
586
+ } catch (error) {
587
+ reject(error);
588
+ }
589
+ });
590
+ };
591
+ return plugin;
592
+ };
package/models.md ADDED
@@ -0,0 +1,26 @@
1
+ ## Correction model
2
+ ### raw
3
+ \vec{correction} = \mathbb{R}(-heading) \cdot \vec{groundSpeed} - \vec{boatSpeed} - \mathbb{R}(-heading) \cdot \vec{waterSpeed}
4
+
5
+ ### link
6
+ https://latex.codecogs.com/svg.image?\vec{correction}=\mathbb{R}(-heading)\cdot\vec{groundSpeed}-\vec{boatSpeed}-\mathbb{R}(-heading)\cdot\vec{waterSpeed}
7
+
8
+ ### html
9
+ <img src="https://latex.codecogs.com/png.image?\dpi{110}\vec{correction}=\mathbb{R}(-heading)\cdot\vec{groundSpeed}-\vec{boatSpeed}-\mathbb{R}(-heading)\cdot\vec{waterSpeed}" title="\vec{correction}=\mathbb{R}(-heading)\cdot\vec{groundSpeed}-\vec{boatSpeed}-\mathbb{R}(-heading)\cdot\vec{waterSpeed}" />
10
+
11
+ ### image
12
+
13
+ ![correction model](correctionModel.png)
14
+
15
+ ## current model
16
+ ### raw
17
+ \vec{waterSpeed} = \vec{groundSpeed} - \mathbb{R}(heading) \cdot(\vec{boatSpeed}+\vec{correction})
18
+ ### link
19
+ https://latex.codecogs.com/svg.image?\vec{waterSpeed}=\vec{groundSpeed}-\mathbb{R}(heading)\cdot(\vec{boatSpeed}&plus;\vec{correction})
20
+ ### html
21
+ <img src="https://latex.codecogs.com/svg.image?\vec{waterSpeed}=\vec{groundSpeed}-\mathbb{R}(heading)\cdot(\vec{boatSpeed}&plus;\vec{correction})" title="\vec{waterSpeed}=\vec{groundSpeed}-\mathbb{R}(heading)\cdot(\vec{boatSpeed}+\vec{correction})" />
22
+
23
+
24
+ ### image
25
+
26
+ ![correction model](currentModel.png)
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "speedandcurrent",
3
+ "version": "1.0.0",
4
+ "description": "An experimental plugin that uses sensor fusion to get boat speed, current and leeway.",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "author": "aswin.bouwmeester@gmail.com",
10
+ "license": "ISC",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/Asw1n/speedandcurrent.git",
14
+ "directory": "./CHANGELOG.md"
15
+ },
16
+ "bugs": {
17
+ "url": "https://github.com/Asw1n/speedandcurrent/issues"
18
+ },
19
+ "homepage": "https://github.com/Asw1n/speedandcurrent#readme",
20
+ "keywords": [
21
+ "signalk-webapp",
22
+ "signalk-node-server-plugin",
23
+ "signalk-category-utility"
24
+ ],
25
+ "signalk-plugin-enabled-by-default": false,
26
+ "signalk": {
27
+ "appIcon": "./icon.png",
28
+ "displayName": "Speed and current"
29
+ },
30
+ "dependencies": {
31
+ "kalman-filter": "^2.3.0",
32
+ "signalkutilities": "^1.0.0"
33
+ }
34
+ }