speedandcurrent 2.3.4 → 2.5.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 +204 -0
- package/README.md +83 -46
- package/correctionTable.js +296 -66
- package/docs/correction-table-learning.md +33 -0
- package/docs/correction-table-v1.schema.json +219 -0
- package/docs/correction-table-v2.schema.json +134 -0
- package/docs/correction-table.schema.json +219 -0
- package/index.js +295 -147
- package/package.json +4 -3
- package/public/TableRenderer.js +89 -14
- package/public/app.js +126 -78
- package/public/index.html +14 -6
- package/public/main.css +19 -5
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
|
|
7
|
+
## [2.5.0] - 2026-10-26
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- Correction-table files now use version 2 with per-cell update timestamps, capped time-based aging, and silent migration of existing version-1 files.
|
|
11
|
+
- The webapp now polls at the plugin's actual sample rate instead of a fixed 1s interval, reported via a new `pollIntervalMs` field on `/api/report`.
|
|
12
|
+
- Correction estimates now fuse mature nearby table cells using their full covariance and normalized distance, with an automatically estimated spatial variance, a two-cell search radius, and fusion-weight diagnostics in the webapp.
|
|
13
|
+
- The correction table now overlays the current uncorrected (raw) and corrected speed/heel as red and blue dots positioned directly on the grid, with a matching legend entry.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- The npm package now includes this changelog.
|
|
17
|
+
- Updated the `signalkutilities` dependency to `^3.2.0`, including non-finite sample protection, explicit-null smoother resets, and subscription-manager-controlled startup values.
|
|
18
|
+
- Requires signalk-server >=2.35.0 for the upstream metadata-first subscription fix ([SignalK/signalk-server#3216](https://github.com/SignalK/signalk-server/pull/3216)).
|
|
19
|
+
- Read-only Signal K users can now access live reports, status, and metadata; settings and correction-table listing or changes remain administrator-only.
|
|
20
|
+
- Correction-table learning no longer incorporates the estimated current, and the related setting has been removed.
|
|
21
|
+
- Correction-table process noise is now configured as a user-facing correction drift rate in knots/month, with a conservative default of 0.3 and an SI conversion internally. Legacy stability settings migrate silently.
|
|
22
|
+
- Correction interpolation now includes mature cells within 2.5 normalized grid-cell units.
|
|
23
|
+
- Correction learning now always uses a moving-average window of at least 5 seconds, with a one-second gap between observation windows. Older smoother selections migrate silently.
|
|
24
|
+
- Cells become eligible after one accepted observation; the explicit maturity gate remains in place at zero for future tuning.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- Removed idle-triggered input resubscriptions, including boat-speed inputs, to avoid replaying cached values during sensor outages. Idle/stale warnings remain, and existing subscriptions receive data when the source resumes. Synchronous startup safeguards are preserved.
|
|
28
|
+
- The boat-speed estimation inputs now prioritize Boatspeed unless groundspeed fallback is selected, and the correction table marks its most recently updated cell with a thin black border reflected in the legend.
|
|
29
|
+
- The learning status Reason row is now hidden when the last observation was accepted, instead of showing a redundant "Observation recorded" message.
|
|
30
|
+
- `navigation.state` no longer misreports as "not ready" a few seconds after each update; it is an event path that only publishes on transition, so the generic telemetry staleness timer no longer applies to it. The Inputs tab now also displays its current value.
|
|
31
|
+
- Learning and leeway are now gated on the vessel's own SOG-based moving/not-moving state, rather than solely on `navigation.state` freshness. Previously, an infrequent `navigation.state` update could go stale within seconds, silently letting learning continue while anchored or moored. `navigation.state`, when known and `suspendLearningOnNavigationState` is enabled, can still override to "not moving" (anchored/moored/motoring), but can no longer force "moving" against the vessel's own SOG.
|
|
32
|
+
- Webapp report parsing failures now remain visible and still refresh plugin status instead of aborting the polling tick.
|
|
33
|
+
- Disabled current estimation no longer publishes a zero-current value during plugin startup.
|
|
34
|
+
- Malformed correction tables now recover safely at startup, while explicit loads reject invalid files without replacing the active table.
|
|
35
|
+
- The correction-table Kalman filter now uses each observation's own measurement-noise covariance (already computed from live sensor variances) instead of a fixed identity matrix, so anisotropic and rotated observation uncertainty is correctly reflected in cell covariances. Persisted tables store a serializable model descriptor rather than the runtime model; existing table files still load unchanged.
|
|
36
|
+
|
|
37
|
+
## [2.3.4] - 2026-09-17
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
- Boat-speed startup now preserves synchronously replayed cached values, avoids restart errors, and keeps current estimation behind the startup stabilization period regardless of table-learning settings.
|
|
41
|
+
- Attitude roll handling now treats incomplete or stale values as invalid during correction-table learning, preventing fabricated heel-based corrections while the input is not yet usable.
|
|
42
|
+
- Webapp values and warnings now share per-path lifecycle status, distinguish subscribed and computed data, report polar magnitude and angle independently, and show stale raw inputs consistently across all pages.
|
|
43
|
+
|
|
44
|
+
## [2.3.3] - 2026-09-06
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
- `navigation.leewayAngle` no longer reports a correction-table artifact when the vessel is stationary. The lateral component of the table correction is now suppressed below the table speed step and while `navigation.state` is `anchored` or `moored`, so leeway is published as 0 instead of an angle derived from a near-zero speed. The longitudinal speed correction, and leeway while motoring, are unaffected.
|
|
48
|
+
- Updated `signalkutilities` to 3.1.2, which completes the 3.1.1 recovery fix for polar-backed inputs. Smoothed heading and the ground-speed vector are built on `Polar`/`PolarSmoother`, where the idle timer was still cleared rather than re-armed on each delta, so those inputs did not recover after going silent following a healthy start.
|
|
49
|
+
|
|
50
|
+
## [2.3.2] - 2026-09-04
|
|
51
|
+
|
|
52
|
+
### Fixed
|
|
53
|
+
- Updated `signalkutilities` to 3.1.1, so an input that goes silent *after* having delivered data is now detected and resubscribed. Previously recovery only ran for subscriptions that had never received a delta, meaning an upstream outage after a healthy start could leave the plugin dead until it was restarted.
|
|
54
|
+
|
|
55
|
+
## [2.3.1] - 2026-08-18
|
|
56
|
+
|
|
57
|
+
### Fixed
|
|
58
|
+
- Signal K admin styling now loads from standalone CSS records in current Vite manifests while retaining support for entry-associated CSS and older server fallbacks.
|
|
59
|
+
|
|
60
|
+
## [2.3.0] - 2026-08-16
|
|
61
|
+
|
|
62
|
+
### Added
|
|
63
|
+
- Learning status now distinguishes between active learning and observations that were skipped, rejected, or invalid, and the webapp reports the current reason more clearly.
|
|
64
|
+
|
|
65
|
+
### Changed
|
|
66
|
+
- Correction-table learning now uses automatic gates based on `navigation.state`, with `anchored` and `moored` always suspending learning when available and `motoring` optionally suspending it.
|
|
67
|
+
- The learning threshold for both STW and SOG now follows the active table speed-step size.
|
|
68
|
+
- The webapp no longer exposes a separate minimum-SOG learning setting.
|
|
69
|
+
|
|
70
|
+
### Fixed
|
|
71
|
+
- When learning is off or suspended, the webapp no longer keeps showing a stale previous observation result.
|
|
72
|
+
|
|
73
|
+
## [2.2.1] - 2026-08-14
|
|
74
|
+
|
|
75
|
+
### Fixed
|
|
76
|
+
- Input subscriptions now recover automatically after idle or stale periods, and the webapp exposes lifecycle warnings for affected paths.
|
|
77
|
+
- Corrected output paths are now explicitly cleared with `null` when estimation is disabled or when the plugin stops, preventing stale values from lingering on the Signal K bus.
|
|
78
|
+
- The webapp-facing status and report endpoints now reflect the plugin's current runtime state more directly.
|
|
79
|
+
- SOG fallback now checks raw STW (`rawBoatSpeed`) directly when detecting a zero paddlewheel reading, avoiding dependence on the derived corrected speed value.
|
|
80
|
+
|
|
81
|
+
## [2.1.2] - 2026-07-23
|
|
82
|
+
|
|
83
|
+
### Fixed
|
|
84
|
+
- App Store icon was missing (monogram fallback shown): added `icon.png` at the package root so the App Store CDN can fetch it from the npm tarball via unpkg.com. The runtime `signalk.appIcon: ./icon.png` path is unchanged - the server still serves it from `public/icon.png` via the webapp mount.
|
|
85
|
+
- Updated `signalkutilities` to 3.0.1, fixing continuous 401 log flooding on SK servers with security enabled and anonymous read access disabled.
|
|
86
|
+
|
|
87
|
+
## [2.1.1] - 2026-07-20
|
|
88
|
+
|
|
89
|
+
### Fixed
|
|
90
|
+
- Sidebar toggler was unreachable on mobile viewports.
|
|
91
|
+
|
|
92
|
+
### Changed
|
|
93
|
+
- Updated `signalkutilities` dependency to `^3.0.0` (removes `PolarTable` from the shared library; no behaviour change for this plugin).
|
|
94
|
+
|
|
95
|
+
## [2.1.0] - 2026-07-10
|
|
96
|
+
|
|
97
|
+
### Added
|
|
98
|
+
- `null` is written to corrected boat speed (`navigation.speedThroughWater`, `navigation.leewayAngle`) and current (`environment.current.drift`, `environment.current.setTrue`) paths when `estimateBoatSpeed` is toggled off.
|
|
99
|
+
- `null` is written to all active output paths (current, corrected boat speed) when the plugin stops, preventing stale values on the SK bus.
|
|
100
|
+
|
|
101
|
+
## [2.0.2] - 2026-6-18
|
|
102
|
+
### Fixed
|
|
103
|
+
- The plugin icon was not displayed in the Signal K App Store because the icon path in the package metadata was incorrect.
|
|
104
|
+
- Test files are no longer included in the published npm package.
|
|
105
|
+
|
|
106
|
+
## [2.0.1] - 2026-6-17
|
|
107
|
+
### Fixed
|
|
108
|
+
- Correction table reporting was doing unnecessary work on every update, causing higher CPU use especially on low-power hardware such as a Raspberry Pi.
|
|
109
|
+
- The variance values used for interpolating between correction table cells were calculated with an incorrect formula, causing them to be overestimated. This made the Kalman outlier gate slightly less accurate.
|
|
110
|
+
|
|
111
|
+
## [2.0.0] - 2026-6-17
|
|
112
|
+
### Changed
|
|
113
|
+
- Moving average smoother is now O(1) in both CPU and memory: it no longer slows down or uses more memory as the window size grows.
|
|
114
|
+
|
|
115
|
+
- **Breaking:** Requires `signalkutilities` v2.0.0. Source-selection and pass-through machinery has been removed from the library; Signal K Server now handles source priorities natively.
|
|
116
|
+
- Per-path source settings (`headingSource`, `boatSpeedSource`, `SOGSource`, `attitudeSource`) have been removed. Use Signal K Server's built-in source priority configuration instead.
|
|
117
|
+
- The **Prevent Speed Duplication** setting has been removed. The server's `excludeSelf` behaviour and source priorities replace it.
|
|
118
|
+
|
|
119
|
+
### Removed
|
|
120
|
+
- Source dropdowns in the webapp Inputs section.
|
|
121
|
+
- Source-related fields from persisted plugin configuration. A one-time migration strips these fields automatically on first start with the new version.
|
|
122
|
+
|
|
123
|
+
### Added
|
|
124
|
+
- Documentation explaining how to configure Signal K source priorities so that the corrected `navigation.speedThroughWater` is delivered to all consumers.
|
|
125
|
+
|
|
126
|
+
## [1.7.10] - 2026-5-14
|
|
127
|
+
### Fixed
|
|
128
|
+
- Removed incorrect `self.` prefix from Signal K paths.
|
|
129
|
+
|
|
130
|
+
## [1.7.9] - 2026-5-14
|
|
131
|
+
### Added
|
|
132
|
+
- Learning status indicator in the webapp showing whether the correction table is active, stabilising, or off, and whether the last observation was accepted, rejected, invalid, or below threshold.
|
|
133
|
+
- Outlier rejection using Mahalanobis distance: bad observations are discarded before being fed into the Kalman filter, preventing a single bad sample from corrupting a cell.
|
|
134
|
+
|
|
135
|
+
### Fixed
|
|
136
|
+
- Smoothed learning inputs are now always registered in the reporter regardless of whether `updateCorrectionTable` is enabled, so warnings clear correctly when learning is toggled on at runtime.
|
|
137
|
+
|
|
138
|
+
## [1.7.8] - 2026-5-14
|
|
139
|
+
### Added
|
|
140
|
+
- Legend below the correction table explaining factor / leeway values, background colour coding, stripe direction, active cell, and neighbour cell indicators.
|
|
141
|
+
|
|
142
|
+
### Fixed
|
|
143
|
+
- Correction table learning never started when `assumeCurrent` is off.
|
|
144
|
+
- Active cell highlighting (most recently updated cell) was not visible.
|
|
145
|
+
|
|
146
|
+
## [1.7.7] - 2026-5-13
|
|
147
|
+
### Added
|
|
148
|
+
- Optional staleness detection for inputs.
|
|
149
|
+
- Precise warnings on state of inputs.
|
|
150
|
+
|
|
151
|
+
## [1.7.5] - 2025-11-19
|
|
152
|
+
### Added
|
|
153
|
+
- New webApp.
|
|
154
|
+
- WebApp allows to change plugin configuration while the plugin is running.
|
|
155
|
+
- Support for multiple correction tables (new, copy, delete).
|
|
156
|
+
- Support for altering the dimensions of a correction table (alter).
|
|
157
|
+
- Better edge case handling.
|
|
158
|
+
- User selectable options for smoothing method and smoothing parameters.
|
|
159
|
+
- Support for unitPreferences.
|
|
160
|
+
|
|
161
|
+
### Fixed
|
|
162
|
+
- Bug where navigation.speedThroughWater was removed from the signalk bus.
|
|
163
|
+
|
|
164
|
+
### Removed
|
|
165
|
+
- Possibility to set options via the signalk plugin configuration.
|
|
166
|
+
|
|
167
|
+
## [1.6.0] - 2025-11-19
|
|
168
|
+
### Added
|
|
169
|
+
- Option to copy navigation.speedOverGround to navigation.speedThroughWater when speedThroughWater is not properly measured. Thanks to Jean-Laurent Girod for the suggestion.
|
|
170
|
+
|
|
171
|
+
## [1.5.1] - 2025-10-06
|
|
172
|
+
### Fixed
|
|
173
|
+
- Bug preventing current to be displayed in KIP.
|
|
174
|
+
|
|
175
|
+
## [1.5.0] - 2025-10-06
|
|
176
|
+
### Changed
|
|
177
|
+
- Separate loops for correcting speed and for updating correction factors.
|
|
178
|
+
- Speed correction loop uses raw values.
|
|
179
|
+
- Updating correction factors using moving averages.
|
|
180
|
+
- Unified cell rendering driven by "Show" selections; removed the previous Table style toggle.
|
|
181
|
+
|
|
182
|
+
### Added
|
|
183
|
+
- Option to set the source for input paths.
|
|
184
|
+
- Full integration of uncertainty estimations (variance) in estimations and corrections.
|
|
185
|
+
- Ability to resize the correction table without losing all correction data.
|
|
186
|
+
- WebApp: "Show" multi-select to choose per-cell metrics (correction X/Y, factor, leeway, trace, N) for the correction table.
|
|
187
|
+
- WebApp: Color mode options for the correction table.
|
|
188
|
+
- WebApp: Persistence for speed unit, angle unit, color mode, and shown metrics via localStorage.
|
|
189
|
+
|
|
190
|
+
### Fixed
|
|
191
|
+
- Angles are now reported in the appropriate range, 0 to 2 * PI or -PI to PI, depending on path.
|
|
192
|
+
|
|
193
|
+
## [1.1.0] - 2025-8-20
|
|
194
|
+
### Changed
|
|
195
|
+
- Path names for current. It now is self.environment.current.setTrue and self.environment.current.drift.
|
|
196
|
+
|
|
197
|
+
### Fixed
|
|
198
|
+
- Case error in filename that prevented the webapp from displaying the correction table.
|
|
199
|
+
- Corrected speed through water is now calculated when the correction table is fixed.
|
|
200
|
+
|
|
201
|
+
## [1.0.1] - 2025-1-27
|
|
202
|
+
### Added
|
|
203
|
+
- Basic functionality to estimate speed, leeway and current using Kalman filters.
|
|
204
|
+
- Basic functionality for a webapp that shows what is going on.
|
package/README.md
CHANGED
|
@@ -6,7 +6,9 @@ Most paddle wheel logs carry a systematic error that varies with boat speed and
|
|
|
6
6
|
|
|
7
7
|
This plugin builds and maintains a **2-dimensional correction table** — indexed by speed and heel — that is populated automatically from real sailing observations. No calibration runs, no spreadsheets, no manual entry. Once the table has enough coverage the plugin applies per-point corrections to `navigation.speedThroughWater`, derives leeway from the lateral component of the correction, and estimates water current by subtracting the corrected boat speed vector from the GPS ground speed vector.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Requires signalk-server >=2.35.0 for reliable metadata-first input subscriptions.
|
|
10
|
+
|
|
11
|
+
If an input goes idle or stale, the webapp shows a warning while the subscription stays attached, ready for the next delta; when estimation is disabled or the plugin stops, the active output paths are cleared with `null` so downstream consumers do not keep stale values.
|
|
10
12
|
|
|
11
13
|
In broad terms the plugin:
|
|
12
14
|
|
|
@@ -31,6 +33,12 @@ The plugin requires no further configuration in the Signal K admin UI. All setti
|
|
|
31
33
|
|
|
32
34
|
---
|
|
33
35
|
|
|
36
|
+
## HTTP endpoint access
|
|
37
|
+
|
|
38
|
+
Signal K read-only users can access the live report (`/api/report`), metadata (`/api/meta`), and status (`/api/status`) endpoints. Settings (`/api/settings`) and correction-table listing or management (`/api/tables...`) require administrator access. Anonymous requests still follow the server's configured security policy; this plugin does not enable anonymous access.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
34
42
|
## Configuring Signal K source priorities
|
|
35
43
|
|
|
36
44
|
Signal K Server manages source priorities natively. When multiple devices publish the same path, the server delivers the highest-priority source to all subscribers — including this plugin. No per-path source selection is needed inside the plugin itself.
|
|
@@ -66,7 +74,7 @@ Once configured, all consumers on the Signal K bus — KIP, OpenCPN, Instrument
|
|
|
66
74
|
| `environment.current.drift` | m/s | Estimated current speed. |
|
|
67
75
|
| `environment.current.setTrue` | rad | Estimated current direction (the direction the water moves *toward*). |
|
|
68
76
|
|
|
69
|
-
A 60-second stabilisation period applies after startup.
|
|
77
|
+
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.
|
|
70
78
|
|
|
71
79
|
---
|
|
72
80
|
|
|
@@ -104,7 +112,7 @@ The **Inputs** section shows live readings from the raw sensors as they arrive f
|
|
|
104
112
|
|
|
105
113
|
| Setting | Default | Description |
|
|
106
114
|
|---------|---------|-------------|
|
|
107
|
-
| **
|
|
115
|
+
| **Input staleness handling** | Built in | Inputs are marked stale when they stop updating, and the webapp displays a warning. `navigation.state` is an event path and is not treated as stale between state transitions. |
|
|
108
116
|
|
|
109
117
|
---
|
|
110
118
|
|
|
@@ -144,23 +152,20 @@ The **Correction Table Learning** section has its own independent toggle. Learni
|
|
|
144
152
|
|
|
145
153
|
The panel shows the **smoothed** sensor inputs used for table updates — heading, boat speed, ground speed, and attitude. These are the same signals as in the Estimation panel, but averaged over the smoother window before being fed into the learning algorithm. This averaging reduces the influence of short-term fluctuations on the table update.
|
|
146
154
|
|
|
147
|
-
When **Assume Current** is enabled, the smoothed current estimate is also shown as a learning input.
|
|
148
|
-
|
|
149
155
|
Warnings appear here if any smoothed input is unavailable.
|
|
150
156
|
|
|
151
|
-
The panel also shows whether learning is currently active, suspended, or skipped for the latest observation. Learning is
|
|
157
|
+
The panel also shows whether learning is currently active, suspended, or skipped for the latest observation. Learning is normally gated by SOG: observations below the current table speed-step threshold are skipped. When **Suspend on navigation.state = motoring** is enabled and `navigation.state` is known, `anchored`, `moored`, and `motoring` suspend learning; an unavailable or unknown state falls back to the SOG gate.
|
|
152
158
|
|
|
153
159
|
### Settings
|
|
154
160
|
|
|
155
161
|
| Setting | Default | Description |
|
|
156
162
|
|---------|---------|-------------|
|
|
157
163
|
| **Update Correction Table** | On | Master toggle. Allow the table to update from current observations. |
|
|
158
|
-
| **Suspend on navigation.state = motoring** | Off |
|
|
159
|
-
| **
|
|
160
|
-
| **Assume Current (experimental)** | Off | Include the running current estimate in the table update calculation. Only enable once the current estimate has had time to stabilise and tidal conditions are relatively steady. |
|
|
164
|
+
| **Suspend on navigation.state = motoring** | Off | When enabled, suspend learning when a known `navigation.state` reports `anchored`, `moored`, or `motoring`. When disabled, `navigation.state` does not override the SOG-based moving check. |
|
|
165
|
+
| **Correction drift** | 0.3 knots/month | How quickly the correction may change over time (knots/month). 0.3 knots/month means the correction can typically change by about 0.3 knots over a month. Higher values adapt faster but may be less stable. Range: 0.0–3.0 knots/month. |
|
|
161
166
|
| **Show Statistics (σ)** | Off | Display standard deviation alongside each smoothed value. Useful for spotting noisy sensors. |
|
|
162
167
|
|
|
163
|
-
###
|
|
168
|
+
### Smoothing and learning cadence
|
|
164
169
|
|
|
165
170
|
Controls how raw sensor values are averaged before being used for table updates. These settings have no effect on the published corrected values — only on the inputs to the learning algorithm.
|
|
166
171
|
|
|
@@ -168,12 +173,9 @@ The smoother serves a second purpose beyond noise reduction: its output variance
|
|
|
168
173
|
|
|
169
174
|
| Setting | Default | Description |
|
|
170
175
|
|---------|---------|-------------|
|
|
171
|
-
| **
|
|
172
|
-
| **Window size** | 5 s | (Moving average) Integration window. Larger = smoother but slower to respond. Minimum 2 s. |
|
|
173
|
-
| **Time constant (τ)** | 3 s | (Exponential) Decay time constant. Larger = smoother. Minimum 1 s. |
|
|
174
|
-
| **Kalman gain** | 0.2 | (Kalman) Steady-state gain. Lower = smoother / slower. Range 0.01–0.99. |
|
|
176
|
+
| **Window size** | 5 s | Moving-average integration window. Larger = smoother but slower to respond. Minimum 5 s. |
|
|
175
177
|
|
|
176
|
-
A 5-second moving average window suits typical 1 Hz instrument update rates. In rough conditions with high sensor noise, increasing
|
|
178
|
+
A 5-second moving average window suits typical 1 Hz instrument update rates. Learning attempts occur every `window + 1 second`, leaving a one-second gap between observation windows. In rough conditions with high sensor noise, increasing the window to 8–10 seconds may improve table quality at the cost of temporal resolution. Older configurations that selected another smoother are silently converted to moving average; their valid window duration is retained and shorter values are clamped to 5 seconds.
|
|
177
179
|
|
|
178
180
|
---
|
|
179
181
|
|
|
@@ -183,16 +185,19 @@ The **Correction Table** section displays the active correction table. Rows are
|
|
|
183
185
|
|
|
184
186
|
### Reading the table
|
|
185
187
|
|
|
186
|
-
Each cell that has received at least one observation shows
|
|
188
|
+
Each cell that has received at least one observation shows its correction values. Cells participating in the latest estimate also show their normalized fusion weight as a blue bar along the bottom:
|
|
189
|
+
|
|
190
|
+
- **Speed factor** (e.g. `+3.2%` or `−1.5%`): how much faster or slower the true speed is compared to what the paddle wheel reads.
|
|
191
|
+
- **Leeway** (e.g. `+4°`): the observed lateral correction for that speed/heel combination. Positive is starboard.
|
|
192
|
+
- **Fusion weight**: the blue bar is that cell's normalized share of the current precision-weighted estimate. A full-width bar represents 100%, and the displayed weights sum to approximately 100%.
|
|
187
193
|
|
|
188
|
-
|
|
189
|
-
- **Leeway** (e.g. `+4°`): the observed lateral correction for that speed/heel combination. Positive is starboard. When leeway is non-zero the cell background shows diagonal stripes; the stripe angle encodes the leeway direction visually.
|
|
194
|
+
Cell backgrounds encode the speed factor: green means the paddlewheel reads slow and orange means it reads fast. When leeway is available, stripe angle encodes its direction.
|
|
190
195
|
|
|
191
196
|
Empty cells have not yet received any observations and show no correction.
|
|
192
197
|
|
|
193
|
-
### Active cell
|
|
198
|
+
### Active cell
|
|
194
199
|
|
|
195
|
-
The **active cell** — the one most recently updated by an incoming STW sample — is
|
|
200
|
+
The **active cell** — the one most recently updated by an incoming STW sample — is shown with a black border.
|
|
196
201
|
|
|
197
202
|
### Table management
|
|
198
203
|
|
|
@@ -205,13 +210,19 @@ The **active cell** — the one most recently updated by an incoming STW sample
|
|
|
205
210
|
|
|
206
211
|
Multiple tables can coexist on disk; only the active one is used. This makes it straightforward to keep separate tables for different configurations such as racing vs. cruising sails, or before and after antifouling.
|
|
207
212
|
|
|
213
|
+
### Correction-table file formats
|
|
214
|
+
|
|
215
|
+
The original format is schema version 1. It remains available as [`docs/correction-table-v1.schema.json`](docs/correction-table-v1.schema.json), while [`docs/correction-table.schema.json`](docs/correction-table.schema.json) is retained as the compatible legacy filename. New and saved tables use schema version 2, documented in [`docs/correction-table-v2.schema.json`](docs/correction-table-v2.schema.json). Version 2 adds a top-level `schemaVersion: 2`, the current serializable model descriptor, and `lastAcceptedAt` epoch milliseconds on every cell; empty cells use `null`.
|
|
216
|
+
|
|
217
|
+
Version 1 files, including older files without a version field, are migrated silently on load. Since individual legacy cell ages are unknown, the file modification time is assigned to every learned cell as a conservative approximation. If that timestamp is unavailable, load time is used and the fallback is written only to debug logging. Migration preserves table identity, dimensions, display attributes, means, covariance, and indices, and is persisted atomically only after the table has loaded successfully.
|
|
218
|
+
|
|
208
219
|
---
|
|
209
220
|
|
|
210
221
|
## Operating notes
|
|
211
222
|
|
|
212
223
|
**Be patient with the correction table.** A fresh table has no data and produces no corrections. Cover a range of speeds and heel angles over a few sails and the table fills in progressively. Upwind sailing covers the heel bins well; downwind and reaching fill the low-heel, varying-speed bins.
|
|
213
224
|
|
|
214
|
-
**The table learns while sailing normally.** No dedicated calibration runs are needed. Just sail with **Update Correction Table** on. Learning pauses automatically during startup stabilisation
|
|
225
|
+
**The table learns while sailing normally.** No dedicated calibration runs are needed. Just sail with **Update Correction Table** on. Learning pauses automatically during startup stabilisation and when SOG or STW are below the current table speed-step threshold. If **Suspend on navigation.state = motoring** is enabled, a known `navigation.state` of `anchored`, `moored`, or `motoring` also pauses learning.
|
|
215
226
|
|
|
216
227
|
**Port and starboard are tracked independently.** Heel is signed: starboard positive, port negative. An asymmetric paddle wheel installation will show different corrections on each tack, and the table captures this naturally.
|
|
217
228
|
|
|
@@ -239,17 +250,42 @@ When a new paddle wheel sample arrives the plugin:
|
|
|
239
250
|
|
|
240
251
|
### Neighbour interpolation
|
|
241
252
|
|
|
242
|
-
|
|
253
|
+
The plugin combines learned cells using both their Kalman covariance and their distance from the requested speed and heel. Speed and heel distances are divided by their respective grid steps, so distance is measured in dimensionless cell units. A cell participates only when:
|
|
243
254
|
|
|
244
|
-
|
|
255
|
+
```text
|
|
256
|
+
cell index > 0
|
|
257
|
+
cell distance <= 2.5
|
|
258
|
+
```
|
|
245
259
|
|
|
246
|
-
|
|
260
|
+
For each participating cell, distance is converted into additional uncertainty:
|
|
247
261
|
|
|
248
|
-
|
|
262
|
+
```text
|
|
263
|
+
effective covariance = cell covariance + q * distance² * identity matrix
|
|
264
|
+
```
|
|
249
265
|
|
|
250
|
-
|
|
266
|
+
The inverse effective covariance is the cell's precision. The correction and its covariance are obtained by full 2×2 precision fusion:
|
|
251
267
|
|
|
252
|
-
|
|
268
|
+
```text
|
|
269
|
+
combined precision = sum(inverse(effective covariance))
|
|
270
|
+
correction covariance = inverse(combined precision)
|
|
271
|
+
correction = correction covariance
|
|
272
|
+
* sum(inverse(effective covariance) * cell correction)
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
This gives nearby, mature cells the greatest influence. A fresh table borrows information more broadly because cell covariance is still large; a mature table becomes more local as its cell covariance falls. If no eligible cell exists, the plugin applies zero correction rather than extrapolating beyond the configured radius.
|
|
276
|
+
|
|
277
|
+
#### Automatic spatial variance (`q`)
|
|
278
|
+
|
|
279
|
+
`q` describes how much genuine correction variation is expected between adjacent cells, in correction variance per squared cell. It is calculated without user input from horizontal and vertical pairs where both cells have an index greater than 0. A cell with one accepted observation is therefore eligible while the explicit maturity threshold remains available for future tuning. For each adjacent pair:
|
|
280
|
+
|
|
281
|
+
```text
|
|
282
|
+
pair q = (
|
|
283
|
+
squared distance between the two correction vectors
|
|
284
|
+
- trace(cell covariance 1 + cell covariance 2)
|
|
285
|
+
) / 2
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
The table uses the median pair estimate, which limits the effect of isolated rough or immature cells. At least three eligible adjacent pairs and a positive median are required. A zero or negative estimate means the observed differences do not resolve spatial variation above the cells' estimated uncertainty; because using zero would disable distance weighting, the plugin then uses a conservative fallback of `0.002 (m/s)²` per squared cell. `q` is calculated when a table is loaded and recalculated after every 600 accepted learning observations. The applied `q`, raw estimate, source, and supporting pair count are runtime diagnostics and are not saved in the correction-table file.
|
|
253
289
|
|
|
254
290
|
### How the correction table is populated
|
|
255
291
|
|
|
@@ -257,43 +293,44 @@ Each cell is an independent **2-dimensional Kalman filter** tracking the correct
|
|
|
257
293
|
|
|
258
294
|
Every time conditions are right — plugin running for >60 seconds, smoothers settled, speed above minimum threshold — the plugin computes an **observation** of what the correction should be:
|
|
259
295
|
|
|
260
|
-
|
|
296
|
+
```text
|
|
297
|
+
observation = R(ψ)⁻¹ · V_SOG − R(ψ)⁻¹ · V_current − V_STW
|
|
298
|
+
```
|
|
261
299
|
|
|
262
|
-
where R(ψ) rotates from ground frame to boat frame using true heading
|
|
300
|
+
where `R(ψ)` rotates from ground frame to boat frame using true heading `ψ`. In plain terms: rotate GPS velocity into the boat frame, subtract the current estimate (also rotated), subtract the raw paddle wheel velocity. The residual is the implied sensor error for the current speed and heel.
|
|
263
301
|
|
|
264
302
|
The Kalman update combines this observation with the cell's existing belief:
|
|
265
303
|
|
|
266
|
-
|
|
267
|
-
|
|
304
|
+
```text
|
|
305
|
+
K = P · (P + R_obs)⁻¹
|
|
306
|
+
x_new = x_old + K · (observation − x_old)
|
|
307
|
+
```
|
|
268
308
|
|
|
269
|
-
where P is the cell's current covariance and R_obs is the observation covariance derived from the measurement uncertainty of all contributing signals (SOG variance + current variance + STW variance, rotated appropriately). **Noisy observations produce a smaller gain and move the cell estimate less.**
|
|
309
|
+
where P is the cell's current covariance and R_obs is the observation covariance derived from the measurement uncertainty of all contributing signals (SOG variance + current variance + STW variance, rotated appropriately). It also includes heading uncertainty in radians squared. For $u = R(-heading)(groundSpeed - current)$, the heading contribution is $J \sigma_h^2 J^T$, where $J = [u_y, -u_x]^T$. This is applied once to the combined ground-speed-minus-current vector because both vectors share the same heading error. **Noisy observations produce a smaller gain and move the cell estimate less.**
|
|
270
310
|
|
|
271
|
-
###
|
|
311
|
+
### Time-scaled aging and correction drift
|
|
272
312
|
|
|
273
|
-
Each cell has a small **process
|
|
313
|
+
Each cell has a small **process-noise rate** that allows it to drift slowly over time, reflecting that a paddle wheel's error can change with fouling, recalibration, or crew weight distribution. For an elapsed interval `dt`, the process covariance added to each diagonal is:
|
|
274
314
|
|
|
275
|
-
|
|
315
|
+
```text
|
|
316
|
+
effectiveDt = min(dt, 90 days)
|
|
317
|
+
Q(dt) = Q_rate × effectiveDt × I
|
|
318
|
+
```
|
|
276
319
|
|
|
277
|
-
|
|
320
|
+
The user-facing `Correction drift` value is expressed in knots per month, using a 30-day month. Internally, it is converted to the SI process-noise rate in $(m/s)^2/s$. The default 0.3 knots/month corresponds to approximately $9.2 × 10^{-9}\ (m/s)^2/s$. Offline time counts toward aging, but aging stops after the internal 90-day cap. Aged covariance is calculated when gating, filtering, interpolation, spatial-q estimation, and reporting; stored covariance is not mutated by reads. Rejected observations do not advance timestamps; accepted observations store the posterior covariance and current UTC epoch-millisecond timestamp.
|
|
278
321
|
|
|
279
|
-
In practical terms:
|
|
322
|
+
In practical terms: a lower correction-drift rate trusts accumulated history more; a higher rate allows the table to adapt more quickly to long-term sensor changes.
|
|
280
323
|
|
|
281
324
|
### How current is estimated
|
|
282
325
|
|
|
283
326
|
Current is estimated as:
|
|
284
327
|
|
|
285
|
-
|
|
328
|
+
```text
|
|
329
|
+
V_current = V_SOG − R(ψ) · V_STW_corrected
|
|
330
|
+
```
|
|
286
331
|
|
|
287
332
|
The corrected STW vector is rotated into the ground frame using heading, then subtracted from the GPS velocity. The residual is the water velocity.
|
|
288
333
|
|
|
289
334
|
This raw estimate is fed into a **Kalman smoother with very low process noise** (process variance ≈ 10⁻⁶), so it changes very slowly, integrating over many minutes rather than chasing individual GPS fluctuations. At startup the estimate is strongly initialised to zero.
|
|
290
335
|
|
|
291
336
|
Current estimation requires an accurate boat speed, so it is gated by the 60-second stabilisation period and only runs when **Estimate Boat Speed** is enabled.
|
|
292
|
-
|
|
293
|
-
### Current and table learning
|
|
294
|
-
|
|
295
|
-
Current estimation always runs alongside speed correction — it is a direct byproduct of comparing the GPS velocity with the corrected STW vector. You cannot have one without the other.
|
|
296
|
-
|
|
297
|
-
For table *learning*, however, current can be left out of the equation without significant harm. When a boat sails different headings over time — tacking, gybing, reaching — any steady current appears as an error in one direction on one heading and the opposite direction on another. These errors cancel in the long-term average, so the table converges on the correct speed correction regardless. This is why the default (**Assume Current** off) uses a zero-current placeholder rather than the live estimate.
|
|
298
|
-
|
|
299
|
-
Including the current estimate (**Assume Current** on) would be more accurate in principle, but it introduces a dependency: the current estimate is only as good as the speed correction that produced it. Early in the table's life, when corrections are rough, the current estimate is also rough, and feeding it back into learning can amplify rather than reduce error. There is also a circularity: better speed correction → better current estimate → better speed correction. Enabling this before the table has had time to settle can cause the two to pull each other in the wrong direction, particularly in changing tidal conditions. Use it only once the table is reasonably well populated and current conditions are stable.
|