speedandcurrent 2.5.0 → 2.6.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/CHANGELOG.md +9 -0
- package/README.md +6 -1
- package/index.js +69 -0
- package/package.json +1 -1
- package/public/app.js +7 -4
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
|
|
7
|
+
## [2.6.0] - 2026-11-05
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- Current estimates are also published as an `environment.current` object, with magnetic set calculated from `navigation.magneticVariation` and published at `environment.current.setMagnetic`.
|
|
11
|
+
- The webapp now shows magnetic variation as an estimation input and magnetic current set as an output.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- Magnetic variation is no longer marked idle or stale, since it may update too infrequently for telemetry staleness checks.
|
|
15
|
+
|
|
7
16
|
## [2.5.0] - 2026-10-26
|
|
8
17
|
|
|
9
18
|
### Added
|
package/README.md
CHANGED
|
@@ -62,6 +62,7 @@ Once configured, all consumers on the Signal K bus — KIP, OpenCPN, Instrument
|
|
|
62
62
|
| `navigation.courseOverGroundTrue` | GPS ground speed direction |
|
|
63
63
|
| `navigation.headingTrue` | True heading — used to rotate the boat-frame correction into the ground frame |
|
|
64
64
|
| `navigation.attitude` | Roll angle — provides the heel index into the correction table |
|
|
65
|
+
| `navigation.magneticVariation` | Magnetic variation — used to convert estimated current set to magnetic north |
|
|
65
66
|
|
|
66
67
|
---
|
|
67
68
|
|
|
@@ -73,6 +74,8 @@ Once configured, all consumers on the Signal K bus — KIP, OpenCPN, Instrument
|
|
|
73
74
|
| `navigation.leewayAngle` | rad | Leeway angle (starboard positive). Derived from the lateral correction component. |
|
|
74
75
|
| `environment.current.drift` | m/s | Estimated current speed. |
|
|
75
76
|
| `environment.current.setTrue` | rad | Estimated current direction (the direction the water moves *toward*). |
|
|
77
|
+
| `environment.current.setMagnetic` | rad | Estimated current direction relative to magnetic north, calculated from `environment.current.setTrue` and `navigation.magneticVariation`. |
|
|
78
|
+
| `environment.current` | object | Current estimate containing `drift`, `setTrue`, and, when magnetic variation is available, `setMagnetic`. |
|
|
76
79
|
|
|
77
80
|
A 60-second stabilisation period applies after startup. Corrected boatspeed may be published during this window, but current estimation and table learning wait until it ends.
|
|
78
81
|
|
|
@@ -107,6 +110,7 @@ The **Inputs** section shows live readings from the raw sensors as they arrive f
|
|
|
107
110
|
| Boat speed | `navigation.speedThroughWater` |
|
|
108
111
|
| Ground speed | `navigation.speedOverGround` + `navigation.courseOverGroundTrue` |
|
|
109
112
|
| Attitude (heel) | `navigation.attitude` (roll) |
|
|
113
|
+
| Magnetic variation | `navigation.magneticVariation` |
|
|
110
114
|
|
|
111
115
|
### Settings
|
|
112
116
|
|
|
@@ -124,7 +128,7 @@ The **Boatspeed Estimation** section is enabled with the toggle in the panel hea
|
|
|
124
128
|
|
|
125
129
|
The panel is organised into three groups:
|
|
126
130
|
|
|
127
|
-
**Inputs** — the raw
|
|
131
|
+
**Inputs** — the raw signals used by the estimation calculations: heading, boat speed, ground speed, attitude, and magnetic variation. Warnings appear here if any are unavailable.
|
|
128
132
|
|
|
129
133
|
**Intermediates** — computed vectors that give insight into what the plugin is doing:
|
|
130
134
|
- *Speed correction* — the correction vector currently being applied (longitudinal + lateral components).
|
|
@@ -134,6 +138,7 @@ The panel is organised into three groups:
|
|
|
134
138
|
**Outputs** — what is being published to Signal K:
|
|
135
139
|
- *Corrected boatspeed / Leeway* — corrected STW magnitude and leeway angle.
|
|
136
140
|
- *Current* — estimated current drift and set.
|
|
141
|
+
- *Magnetic set* — estimated current direction relative to magnetic north, calculated using magnetic variation.
|
|
137
142
|
|
|
138
143
|
### Settings
|
|
139
144
|
|
package/index.js
CHANGED
|
@@ -295,6 +295,9 @@ module.exports = function (app) {
|
|
|
295
295
|
let smoothedAttitude = null;
|
|
296
296
|
let rawCurrent = null;
|
|
297
297
|
let smoothedCurrent = null;
|
|
298
|
+
let magneticVariationHandler = null;
|
|
299
|
+
let setMagneticHandler = null;
|
|
300
|
+
let setMagneticPublished = false;
|
|
298
301
|
let smoothedBoatSpeed = null;
|
|
299
302
|
let correctedBoatSpeed = null;
|
|
300
303
|
let lrnBoatSpeed = null;
|
|
@@ -684,6 +687,25 @@ module.exports = function (app) {
|
|
|
684
687
|
// send metadata for current
|
|
685
688
|
MessageHandler.setMeta(app, plugin.id, "environment.current.drift", {units: "m/s", type: "number", description: "Speed of the current"});
|
|
686
689
|
MessageHandler.setMeta(app, plugin.id, "environment.current.setTrue", { units: "rad", type: "number", description: "Direction of the current" });
|
|
690
|
+
MessageHandler.setMeta(app, plugin.id, "environment.current", { type: "object", description: "Estimated water current" });
|
|
691
|
+
MessageHandler.setMeta(app, plugin.id, "environment.current.setMagnetic", {
|
|
692
|
+
units: "rad",
|
|
693
|
+
type: "number",
|
|
694
|
+
description: "Direction of the current relative to magnetic north",
|
|
695
|
+
displayName: "Magnetic set",
|
|
696
|
+
displayUnits: { category: "angle" }
|
|
697
|
+
});
|
|
698
|
+
magneticVariationHandler = new MessageHandler(app, plugin.id, "magneticVariation");
|
|
699
|
+
magneticVariationHandler.configure("navigation.magneticVariation");
|
|
700
|
+
magneticVariationHandler.idlePeriod = 0;
|
|
701
|
+
magneticVariationHandler.stalePeriod = 0;
|
|
702
|
+
magneticVariationHandler.onDelta = () => {
|
|
703
|
+
clearLifecycleWarning("magneticVariation");
|
|
704
|
+
publishCurrentObject();
|
|
705
|
+
};
|
|
706
|
+
magneticVariationHandler.subscribe();
|
|
707
|
+
setMagneticHandler = new MessageHandler(app, plugin.id, "setMagnetic");
|
|
708
|
+
setMagneticHandler.configure("environment.current.setMagnetic");
|
|
687
709
|
rawCurrent = new Polar(app, plugin.id, "current");
|
|
688
710
|
rawCurrent.configureMagnitude("environment.current.drift");
|
|
689
711
|
rawCurrent.configureAngle("environment.current.setTrue");
|
|
@@ -843,6 +865,8 @@ module.exports = function (app) {
|
|
|
843
865
|
reportFull.addPolar(residual);
|
|
844
866
|
reportFull.addPolar(smoothedResidual);
|
|
845
867
|
}
|
|
868
|
+
reportFull.addDelta(magneticVariationHandler);
|
|
869
|
+
reportFull.addDelta(setMagneticHandler);
|
|
846
870
|
// Smoothed learning inputs are always subscribed regardless of updateCorrectionTable,
|
|
847
871
|
// so always add them to the report — otherwise warnings never clear when
|
|
848
872
|
// learning was disabled at startup and later toggled on.
|
|
@@ -874,12 +898,14 @@ module.exports = function (app) {
|
|
|
874
898
|
}
|
|
875
899
|
// Clear all active output paths from the SK bus before teardown.
|
|
876
900
|
if (smoothedCurrent) PolarSmoother.clear(app, plugin.id, [smoothedCurrent]);
|
|
901
|
+
clearCurrentObjectOutputs();
|
|
877
902
|
if (options.estimateBoatSpeed && correctedBoatSpeed) Polar.clear(app, plugin.id, [correctedBoatSpeed]);
|
|
878
903
|
smoothedHeading = smoothedHeading?.terminate();
|
|
879
904
|
smoothedAttitude = smoothedAttitude?.terminate();
|
|
880
905
|
rawCurrent = rawCurrent?.terminate();
|
|
881
906
|
smoothedCurrent = smoothedCurrent?.terminate?.();
|
|
882
907
|
navigationStateHandler = navigationStateHandler?.terminate();
|
|
908
|
+
magneticVariationHandler = magneticVariationHandler?.terminate();
|
|
883
909
|
smoothedBoatSpeed = smoothedBoatSpeed?.terminate();
|
|
884
910
|
correctedBoatSpeed = correctedBoatSpeed?.terminate();
|
|
885
911
|
lrnBoatSpeed = lrnBoatSpeed?.terminate();
|
|
@@ -895,6 +921,8 @@ module.exports = function (app) {
|
|
|
895
921
|
noCurrent = null;
|
|
896
922
|
rawBoatSpeed = null;
|
|
897
923
|
rawGroundSpeed = null;
|
|
924
|
+
setMagneticHandler = null;
|
|
925
|
+
setMagneticPublished = false;
|
|
898
926
|
lastNavigationStateValue = null;
|
|
899
927
|
learningStabilizingUntil = 0;
|
|
900
928
|
learningStabilizingReason = 'startup';
|
|
@@ -986,10 +1014,50 @@ module.exports = function (app) {
|
|
|
986
1014
|
}
|
|
987
1015
|
|
|
988
1016
|
PolarSmoother.send(app, plugin.id, [smoothedCurrent]);
|
|
1017
|
+
publishCurrentObject();
|
|
989
1018
|
|
|
990
1019
|
Polar.send(app, plugin.id, [correctedBoatSpeed]);
|
|
991
1020
|
}
|
|
992
1021
|
|
|
1022
|
+
function publishCurrentObject() {
|
|
1023
|
+
if (!options.estimateBoatSpeed || !smoothedCurrent?.ready) return;
|
|
1024
|
+
const drift = smoothedCurrent.magnitude;
|
|
1025
|
+
const setTrue = smoothedCurrent.angle;
|
|
1026
|
+
if (!Number.isFinite(drift) || !Number.isFinite(setTrue)) return;
|
|
1027
|
+
|
|
1028
|
+
const current = { drift, setTrue };
|
|
1029
|
+
const values = [{ path: "environment.current", value: current }];
|
|
1030
|
+
const magneticVariation = magneticVariationHandler?.value;
|
|
1031
|
+
if (magneticVariationHandler?.ready && Number.isFinite(magneticVariation)) {
|
|
1032
|
+
const setMagnetic = ((setTrue - magneticVariation) % (2 * Math.PI) + 2 * Math.PI) % (2 * Math.PI);
|
|
1033
|
+
current.setMagnetic = setMagnetic;
|
|
1034
|
+
setMagneticHandler.value = setMagnetic;
|
|
1035
|
+
values.push({ path: "environment.current.setMagnetic", value: setMagnetic });
|
|
1036
|
+
setMagneticPublished = true;
|
|
1037
|
+
} else {
|
|
1038
|
+
setMagneticHandler.invalidate();
|
|
1039
|
+
if (setMagneticPublished) {
|
|
1040
|
+
values.push({ path: "environment.current.setMagnetic", value: null });
|
|
1041
|
+
}
|
|
1042
|
+
setMagneticPublished = false;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
app.handleMessage(plugin.id, { updates: [{ values }] });
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
function clearCurrentObjectOutputs() {
|
|
1049
|
+
setMagneticHandler?.invalidate();
|
|
1050
|
+
setMagneticPublished = false;
|
|
1051
|
+
app.handleMessage(plugin.id, {
|
|
1052
|
+
updates: [{
|
|
1053
|
+
values: [
|
|
1054
|
+
{ path: "environment.current", value: null },
|
|
1055
|
+
{ path: "environment.current.setMagnetic", value: null }
|
|
1056
|
+
]
|
|
1057
|
+
}]
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
|
|
993
1061
|
/**
|
|
994
1062
|
* Updates the correction table from the current smoothed inputs.
|
|
995
1063
|
* Reads minSpeed from module-level state;
|
|
@@ -1188,6 +1256,7 @@ module.exports = function (app) {
|
|
|
1188
1256
|
if (key === 'estimateBoatSpeed' && !value && correctedBoatSpeed) {
|
|
1189
1257
|
Polar.clear(app, plugin.id, [correctedBoatSpeed]);
|
|
1190
1258
|
PolarSmoother.clear(app, plugin.id, [smoothedCurrent]);
|
|
1259
|
+
clearCurrentObjectOutputs();
|
|
1191
1260
|
}
|
|
1192
1261
|
|
|
1193
1262
|
delete changedOptions[key];
|
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -118,7 +118,7 @@ async function loadMeta() {
|
|
|
118
118
|
|
|
119
119
|
const computedItemIds = new Set([
|
|
120
120
|
'current.smoothed', 'boatSpeedRefGround', 'speedCorrection',
|
|
121
|
-
'correctedBoatSpeed', 'residual', 'residual.smoothed'
|
|
121
|
+
'correctedBoatSpeed', 'residual', 'residual.smoothed', 'setMagnetic'
|
|
122
122
|
]);
|
|
123
123
|
|
|
124
124
|
function getPathStatus(pathState, hasValue, computed = false, computedReady = false) {
|
|
@@ -564,6 +564,8 @@ function renderLiveSections() {
|
|
|
564
564
|
const inputAttitudes = filterById(state.attitudesAll, ['attitude']);
|
|
565
565
|
const fallbackInputPolars = inputPolars.length ? inputPolars : filterById(state.polarsAll, ['groundSpeed.smoothed']);
|
|
566
566
|
const fallbackInputDeltas = inputDeltas.length ? inputDeltas : filterById(state.deltasAll, ['heading.smoothed', 'boatSpeed.smoothed']);
|
|
567
|
+
const magneticVariationInput = filterById(state.deltasAll, ['magneticVariation']);
|
|
568
|
+
fallbackInputDeltas.push(...magneticVariationInput);
|
|
567
569
|
const fallbackInputAttitudes = inputAttitudes.length ? inputAttitudes : filterById(state.attitudesAll, ['attitude.smoothed']);
|
|
568
570
|
const displayedInputs = [...fallbackInputPolars, ...fallbackInputDeltas, ...fallbackInputAttitudes];
|
|
569
571
|
|
|
@@ -584,7 +586,7 @@ function renderLiveSections() {
|
|
|
584
586
|
? [...groundSpeedInputs, ...boatSpeedInputs]
|
|
585
587
|
: [...boatSpeedInputs, ...groundSpeedInputs];
|
|
586
588
|
estimationInputs.push(
|
|
587
|
-
...filterById(state.deltasAll, ['heading.angle']),
|
|
589
|
+
...filterById(state.deltasAll, ['heading.angle', 'magneticVariation']),
|
|
588
590
|
...filterById(state.attitudesAll, ['attitude'])
|
|
589
591
|
);
|
|
590
592
|
renderItemsInto('estimation-inputs', estimationInputs);
|
|
@@ -599,9 +601,10 @@ function renderLiveSections() {
|
|
|
599
601
|
renderGroupInto('estimation-intermediates', estimationIntermediates, [], [], statisticRows);
|
|
600
602
|
// Estimation — outputs
|
|
601
603
|
const estimationOutputs = filterById(state.polarsAll, ['correctedBoatSpeed', 'current.smoothed']);
|
|
602
|
-
|
|
604
|
+
const estimationOutputDeltas = filterById(state.deltasAll, ['setMagnetic']);
|
|
605
|
+
renderGroupInto('estimation-outputs', estimationOutputs, estimationOutputDeltas, []);
|
|
603
606
|
// Estimation — warnings
|
|
604
|
-
renderWarnings('estimation-warnings', [...estimationInputs, ...estimationIntermediates, ...estimationOutputs]);
|
|
607
|
+
renderWarnings('estimation-warnings', [...estimationInputs, ...estimationIntermediates, ...estimationOutputs, ...estimationOutputDeltas]);
|
|
605
608
|
|
|
606
609
|
// Learning — inputs
|
|
607
610
|
const learningPolars = filterById(state.polarsAll, ['groundSpeed.smoothed']);
|