speedandcurrent 2.3.4 → 2.4.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/README.md +74 -45
- 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 +285 -127
- package/package.json +2 -2
- 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/README.md
CHANGED
|
@@ -66,7 +66,7 @@ Once configured, all consumers on the Signal K bus — KIP, OpenCPN, Instrument
|
|
|
66
66
|
| `environment.current.drift` | m/s | Estimated current speed. |
|
|
67
67
|
| `environment.current.setTrue` | rad | Estimated current direction (the direction the water moves *toward*). |
|
|
68
68
|
|
|
69
|
-
A 60-second stabilisation period applies after startup.
|
|
69
|
+
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
70
|
|
|
71
71
|
---
|
|
72
72
|
|
|
@@ -104,7 +104,7 @@ The **Inputs** section shows live readings from the raw sensors as they arrive f
|
|
|
104
104
|
|
|
105
105
|
| Setting | Default | Description |
|
|
106
106
|
|---------|---------|-------------|
|
|
107
|
-
| **
|
|
107
|
+
| **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
108
|
|
|
109
109
|
---
|
|
110
110
|
|
|
@@ -144,23 +144,20 @@ The **Correction Table Learning** section has its own independent toggle. Learni
|
|
|
144
144
|
|
|
145
145
|
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
146
|
|
|
147
|
-
When **Assume Current** is enabled, the smoothed current estimate is also shown as a learning input.
|
|
148
|
-
|
|
149
147
|
Warnings appear here if any smoothed input is unavailable.
|
|
150
148
|
|
|
151
|
-
The panel also shows whether learning is currently active, suspended, or skipped for the latest observation. Learning is
|
|
149
|
+
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
150
|
|
|
153
151
|
### Settings
|
|
154
152
|
|
|
155
153
|
| Setting | Default | Description |
|
|
156
154
|
|---------|---------|-------------|
|
|
157
155
|
| **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. |
|
|
156
|
+
| **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. |
|
|
157
|
+
| **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
158
|
| **Show Statistics (σ)** | Off | Display standard deviation alongside each smoothed value. Useful for spotting noisy sensors. |
|
|
162
159
|
|
|
163
|
-
###
|
|
160
|
+
### Smoothing and learning cadence
|
|
164
161
|
|
|
165
162
|
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
163
|
|
|
@@ -168,12 +165,9 @@ The smoother serves a second purpose beyond noise reduction: its output variance
|
|
|
168
165
|
|
|
169
166
|
| Setting | Default | Description |
|
|
170
167
|
|---------|---------|-------------|
|
|
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. |
|
|
168
|
+
| **Window size** | 5 s | Moving-average integration window. Larger = smoother but slower to respond. Minimum 5 s. |
|
|
175
169
|
|
|
176
|
-
A 5-second moving average window suits typical 1 Hz instrument update rates. In rough conditions with high sensor noise, increasing
|
|
170
|
+
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
171
|
|
|
178
172
|
---
|
|
179
173
|
|
|
@@ -183,16 +177,19 @@ The **Correction Table** section displays the active correction table. Rows are
|
|
|
183
177
|
|
|
184
178
|
### Reading the table
|
|
185
179
|
|
|
186
|
-
Each cell that has received at least one observation shows
|
|
180
|
+
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:
|
|
181
|
+
|
|
182
|
+
- **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.
|
|
183
|
+
- **Leeway** (e.g. `+4°`): the observed lateral correction for that speed/heel combination. Positive is starboard.
|
|
184
|
+
- **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
185
|
|
|
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.
|
|
186
|
+
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
187
|
|
|
191
188
|
Empty cells have not yet received any observations and show no correction.
|
|
192
189
|
|
|
193
|
-
### Active cell
|
|
190
|
+
### Active cell
|
|
194
191
|
|
|
195
|
-
The **active cell** — the one most recently updated by an incoming STW sample — is
|
|
192
|
+
The **active cell** — the one most recently updated by an incoming STW sample — is shown with a black border.
|
|
196
193
|
|
|
197
194
|
### Table management
|
|
198
195
|
|
|
@@ -205,13 +202,19 @@ The **active cell** — the one most recently updated by an incoming STW sample
|
|
|
205
202
|
|
|
206
203
|
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
204
|
|
|
205
|
+
### Correction-table file formats
|
|
206
|
+
|
|
207
|
+
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`.
|
|
208
|
+
|
|
209
|
+
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.
|
|
210
|
+
|
|
208
211
|
---
|
|
209
212
|
|
|
210
213
|
## Operating notes
|
|
211
214
|
|
|
212
215
|
**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
216
|
|
|
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
|
|
217
|
+
**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
218
|
|
|
216
219
|
**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
220
|
|
|
@@ -239,17 +242,42 @@ When a new paddle wheel sample arrives the plugin:
|
|
|
239
242
|
|
|
240
243
|
### Neighbour interpolation
|
|
241
244
|
|
|
242
|
-
|
|
245
|
+
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:
|
|
246
|
+
|
|
247
|
+
```text
|
|
248
|
+
cell index > 0
|
|
249
|
+
cell distance <= 2.5
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
For each participating cell, distance is converted into additional uncertainty:
|
|
243
253
|
|
|
244
|
-
|
|
254
|
+
```text
|
|
255
|
+
effective covariance = cell covariance + q * distance² * identity matrix
|
|
256
|
+
```
|
|
245
257
|
|
|
246
|
-
|
|
258
|
+
The inverse effective covariance is the cell's precision. The correction and its covariance are obtained by full 2×2 precision fusion:
|
|
247
259
|
|
|
248
|
-
|
|
260
|
+
```text
|
|
261
|
+
combined precision = sum(inverse(effective covariance))
|
|
262
|
+
correction covariance = inverse(combined precision)
|
|
263
|
+
correction = correction covariance
|
|
264
|
+
* sum(inverse(effective covariance) * cell correction)
|
|
265
|
+
```
|
|
249
266
|
|
|
250
|
-
|
|
267
|
+
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.
|
|
251
268
|
|
|
252
|
-
|
|
269
|
+
#### Automatic spatial variance (`q`)
|
|
270
|
+
|
|
271
|
+
`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:
|
|
272
|
+
|
|
273
|
+
```text
|
|
274
|
+
pair q = (
|
|
275
|
+
squared distance between the two correction vectors
|
|
276
|
+
- trace(cell covariance 1 + cell covariance 2)
|
|
277
|
+
) / 2
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
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
281
|
|
|
254
282
|
### How the correction table is populated
|
|
255
283
|
|
|
@@ -257,43 +285,44 @@ Each cell is an independent **2-dimensional Kalman filter** tracking the correct
|
|
|
257
285
|
|
|
258
286
|
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
287
|
|
|
260
|
-
|
|
288
|
+
```text
|
|
289
|
+
observation = R(ψ)⁻¹ · V_SOG − R(ψ)⁻¹ · V_current − V_STW
|
|
290
|
+
```
|
|
261
291
|
|
|
262
|
-
where R(ψ) rotates from ground frame to boat frame using true heading
|
|
292
|
+
where `R(ψ)` rotates from ground frame to boat frame using true heading `ψ`. In plain terms: rotate GPS velocity into the boat frame, subtract the raw paddle wheel velocity. The residual is the implied sensor error for the current speed and heel.
|
|
263
293
|
|
|
264
294
|
The Kalman update combines this observation with the cell's existing belief:
|
|
265
295
|
|
|
266
|
-
|
|
267
|
-
|
|
296
|
+
```text
|
|
297
|
+
K = P · (P + R_obs)⁻¹
|
|
298
|
+
x_new = x_old + K · (observation − x_old)
|
|
299
|
+
```
|
|
268
300
|
|
|
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 +
|
|
301
|
+
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 + 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
302
|
|
|
271
|
-
###
|
|
303
|
+
### Time-scaled aging and correction drift
|
|
272
304
|
|
|
273
|
-
Each cell has a small **process
|
|
305
|
+
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. For an elapsed interval `dt`, the process covariance added to each diagonal is:
|
|
274
306
|
|
|
275
|
-
|
|
307
|
+
```text
|
|
308
|
+
effectiveDt = min(dt, 90 days)
|
|
309
|
+
Q(dt) = Q_rate × effectiveDt × I
|
|
310
|
+
```
|
|
276
311
|
|
|
277
|
-
|
|
312
|
+
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
313
|
|
|
279
|
-
In practical terms:
|
|
314
|
+
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
315
|
|
|
281
316
|
### How current is estimated
|
|
282
317
|
|
|
283
318
|
Current is estimated as:
|
|
284
319
|
|
|
285
|
-
|
|
320
|
+
```text
|
|
321
|
+
V_current = V_SOG − R(ψ) · V_STW_corrected
|
|
322
|
+
```
|
|
286
323
|
|
|
287
324
|
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
325
|
|
|
289
326
|
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
327
|
|
|
291
328
|
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.
|