@meri-imperiumi/signalk-dead-reckoning 0.1.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/.editorconfig +5 -0
- package/.github/workflows/publish.yml +34 -0
- package/.github/workflows/signalk-ci.yml +14 -0
- package/.github/workflows/test.yml +17 -0
- package/CHANGELOG.md +411 -0
- package/README.md +5 -0
- package/SPEC.md +494 -0
- package/biome.json +6 -0
- package/doc/dr-bearing-to.png +0 -0
- package/doc/dr-celestial.png +0 -0
- package/doc/dr-map.png +0 -0
- package/package.json +53 -0
- package/plugin/bins.js +85 -0
- package/plugin/celestial.js +478 -0
- package/plugin/current.js +292 -0
- package/plugin/db.js +958 -0
- package/plugin/divergence.js +137 -0
- package/plugin/engine.js +163 -0
- package/plugin/fix-pipeline.js +408 -0
- package/plugin/fixes.js +622 -0
- package/plugin/geo.js +196 -0
- package/plugin/ground-track.js +124 -0
- package/plugin/index.js +2349 -0
- package/plugin/logbook.js +397 -0
- package/plugin/matrix.js +271 -0
- package/plugin/star-almanac.js +109 -0
- package/plugin/training.js +572 -0
- package/plugin/uncertainty.js +196 -0
- package/public/dr-app.js +913 -0
- package/public/dr-coord-fields.js +229 -0
- package/public/dr-current-panel.js +312 -0
- package/public/dr-detail-popover.js +409 -0
- package/public/dr-fix-panel.js +479 -0
- package/public/dr-history.js +135 -0
- package/public/dr-map-view.js +668 -0
- package/public/dr-pending-list.js +446 -0
- package/public/dr-position-format.js +246 -0
- package/public/dr-sight-panel.js +873 -0
- package/public/dr-signalk-stream.js +187 -0
- package/public/dr-theme.js +200 -0
- package/public/dr-viewmodel.js +865 -0
- package/public/icon.png +0 -0
- package/public/index.html +18 -0
- package/public/styles.css +61 -0
- package/public/vendor/leaflet/LICENSE +26 -0
- package/public/vendor/leaflet/README.md +9 -0
- package/public/vendor/leaflet/images/layers-2x.png +0 -0
- package/public/vendor/leaflet/images/layers.png +0 -0
- package/public/vendor/leaflet/images/marker-icon-2x.png +0 -0
- package/public/vendor/leaflet/images/marker-icon.png +0 -0
- package/public/vendor/leaflet/images/marker-shadow.png +0 -0
- package/public/vendor/leaflet/leaflet.css +661 -0
- package/public/vendor/leaflet/leaflet.js +7349 -0
- package/tests/bins.test.js +54 -0
- package/tests/celestial.test.js +324 -0
- package/tests/current.test.js +243 -0
- package/tests/db.test.js +536 -0
- package/tests/divergence.test.js +156 -0
- package/tests/dr-coord-fields.test.js +109 -0
- package/tests/dr-current.test.js +88 -0
- package/tests/dr-history.test.js +115 -0
- package/tests/dr-position-format.test.js +158 -0
- package/tests/dr-stopwatch.test.js +76 -0
- package/tests/dr-theme.test.js +93 -0
- package/tests/dr-viewmodel.test.js +721 -0
- package/tests/engine.test.js +92 -0
- package/tests/fake-app.js +167 -0
- package/tests/fix-pipeline.test.js +656 -0
- package/tests/fixes.test.js +363 -0
- package/tests/geo.test.js +80 -0
- package/tests/ground-track.test.js +107 -0
- package/tests/logbook.test.js +326 -0
- package/tests/matrix.test.js +169 -0
- package/tests/plugin.test.js +2762 -0
- package/tests/star-almanac.test.js +64 -0
- package/tests/training.test.js +523 -0
- package/tests/uncertainty.test.js +173 -0
package/.editorconfig
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: Publish Node.js Package
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "*"
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
id-token: write # Required for OIDC
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
build:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: actions/setup-node@v4
|
|
18
|
+
with:
|
|
19
|
+
node-version: 24
|
|
20
|
+
package-manager-cache: false # never use caching in release builds
|
|
21
|
+
- run: npm install
|
|
22
|
+
- run: npm test
|
|
23
|
+
|
|
24
|
+
publish-npm:
|
|
25
|
+
needs: build
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
steps:
|
|
28
|
+
- uses: actions/checkout@v4
|
|
29
|
+
- uses: actions/setup-node@v4
|
|
30
|
+
with:
|
|
31
|
+
node-version: 24
|
|
32
|
+
registry-url: https://registry.npmjs.org/
|
|
33
|
+
package-manager-cache: false # never use caching in release builds
|
|
34
|
+
- run: npm publish
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
name: SignalK Plugin CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, master]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main, master]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
uses: SignalK/signalk-server/.github/workflows/plugin-ci.yml@master
|
|
12
|
+
with:
|
|
13
|
+
enable-armv7: false
|
|
14
|
+
enable-signalk-integration: true
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
name: Node CI
|
|
2
|
+
|
|
3
|
+
on: [push, pull_request]
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
test:
|
|
7
|
+
name: Run test suite
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
steps:
|
|
10
|
+
- uses: actions/checkout@v4
|
|
11
|
+
- uses: actions/setup-node@v4
|
|
12
|
+
with:
|
|
13
|
+
node-version: 24.x
|
|
14
|
+
- run: npm install
|
|
15
|
+
- run: npm test
|
|
16
|
+
env:
|
|
17
|
+
CI: true
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,411 @@
|
|
|
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.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2026-08-27
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- **"Tactical sci-fi" visual theme** applied across the webapp (the
|
|
12
|
+
Lille Ø Signal K UI spec): the GitHub-dark palette is replaced by the
|
|
13
|
+
semantic neon token system (`--color-green/teal/orange/red/grey`, dark
|
|
14
|
+
canvas tokens) declared at the document `:root` and shared into every
|
|
15
|
+
shadow-root component via a new `public/dr-theme.js` module — flat
|
|
16
|
+
geometry (no radii, no shadows), 2px corner brackets on `.sk-card`
|
|
17
|
+
panels, hardware-style controls (transparent buttons with theme
|
|
18
|
+
borders that invert on use, 2px-bottom-rule monospace inputs, sharp
|
|
19
|
+
square checkboxes) with ≥48 px touch targets, massive tabular-nums
|
|
20
|
+
telemetry values, and uppercase tracked headers. Map overlays
|
|
21
|
+
(divergence chip, chart-pick menu, Leaflet controls/tooltips) use the
|
|
22
|
+
semi-transparent dark overlay treatment; the map aggressively fills
|
|
23
|
+
the desktop viewport with a 50 vh mobile floor. Map geometry colors
|
|
24
|
+
remapped to the semantic palette (GPS green, DR/ghost teal, LOP
|
|
25
|
+
orange, consumed grey). `dr-app` now subscribes to
|
|
26
|
+
`vessels.self.environment.mode` and reflects it as
|
|
27
|
+
`data-mode="night"|"day"` on `<html>`, lifting the canvas tokens for
|
|
28
|
+
daylight legibility.
|
|
29
|
+
- **"Fix at coordinates" uses the structured coordinate entry** — the
|
|
30
|
+
same deg/min/sec/hemisphere (or decimal) sub-fields as the sight
|
|
31
|
+
forms, driven by the server-configured position format. The fieldset
|
|
32
|
+
builder, seeding and reading logic now live in a shared
|
|
33
|
+
`public/dr-coord-fields.js` module used by both panels (the sight
|
|
34
|
+
panel's private copies were removed). Reading is now format-driven:
|
|
35
|
+
decimal mode reads the decimal field, DM/DMS assemble the visible
|
|
36
|
+
deg/min/sec/hem fields.
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
- **Traditional sun-run-sun support: 36 h running-fix window** —
|
|
40
|
+
classic single-sight-per-day practice advances yesterday's LOP by a
|
|
41
|
+
full day's run, so consecutive sights land ~24 h apart; the old 6 h
|
|
42
|
+
buffer couldn't span them. The ground-track window is now
|
|
43
|
+
configurable (`groundTrackHours`, default 36 h ≈ 129 600 samples at
|
|
44
|
+
1 Hz — a day plus drift margin for when the sight slips; the SQLite
|
|
45
|
+
persistence window follows automatically). Memory and database grow
|
|
46
|
+
~3.6 MB per configured hour.
|
|
47
|
+
- **Restart survival for the DR ground track** (work doc #16, sea-trial
|
|
48
|
+
prep): the running-fix advancement buffer (6 h ring of 1 Hz DR
|
|
49
|
+
samples, SPEC §9.1) is now persisted to SQLite (`dr_track_samples`,
|
|
50
|
+
flushed incrementally on the 60 s state-flush cadence plus the
|
|
51
|
+
stop-time flush, pruned to the buffer window, `INSERT OR REPLACE`
|
|
52
|
+
keyed on timestamp to match `GroundTrack.append` semantics) and
|
|
53
|
+
re-seeded on plugin start — a mid-passage server restart no longer
|
|
54
|
+
leaves sights taken before the restart un-advanced. Verified
|
|
55
|
+
end-to-end: plugin run → stop → restart on the same db →
|
|
56
|
+
`POST /fix/resolve` advances a pre-restart sight along the persisted
|
|
57
|
+
DR run.
|
|
58
|
+
- **History-backed map restart survival** (work doc #16): a new
|
|
59
|
+
`public/dr-history.js` module speaks the Signal K History API
|
|
60
|
+
(`/signalk/v2/api/history`, no auth) — multi-path `/values` queries
|
|
61
|
+
with aggregation postfixes (`:last` is mandatory for non-numeric
|
|
62
|
+
paths like the divergence record; numbers may use `average`/`sma`/
|
|
63
|
+
`ema`). On load, `<dr-app>` now backfills in one request: the GPS
|
|
64
|
+
track, the DR ghost track (previously live-session only — a page
|
|
65
|
+
reload blanked it), and the divergence sparkline. Tracks merge
|
|
66
|
+
history → live-session points continuously.
|
|
67
|
+
- **Header set & drift readout + manual override** (SPEC §6.2 tier 1):
|
|
68
|
+
the webapp header shows the resolved current vector (`067° · 1.2 kn`)
|
|
69
|
+
themed by source (manual = orange with TTL countdown, weather/pilot
|
|
70
|
+
chart = teal, none = offline grey). A new `≋ Current` toolbar button
|
|
71
|
+
opens `<dr-current-panel>` — enter set (° true), drift (kn) and a TTL
|
|
72
|
+
(default 60 min) to `PUT /current/manual`, or clear the override
|
|
73
|
+
(`DELETE /current/manual`); the resolver honors the manual tier over
|
|
74
|
+
every automatic source while the TTL lasts. `GET /status` now also
|
|
75
|
+
reports the resolved `current` vector and any `manualCurrent`, so
|
|
76
|
+
the header can bootstrap and refresh between deltas (the manual TTL
|
|
77
|
+
caption counts down on the 30 s status poll).
|
|
78
|
+
- **Stopwatch sight-time entry ("N min N sec ago")**: each sight
|
|
79
|
+
form's time field gains an `ago` mode — enter the minutes/seconds
|
|
80
|
+
elapsed since the sight was taken (stopwatch method) and the offset
|
|
81
|
+
converts to clock time **at entry**: every keystroke re-bases
|
|
82
|
+
"now", so the committed instant is anchored to when the navigator
|
|
83
|
+
stopped the watch, not when the form is submitted. The converted
|
|
84
|
+
time lands in the regular sight-time field (still editable; the
|
|
85
|
+
local/UTC toggle re-expresses it). Pure conversion in
|
|
86
|
+
`vm.stopwatchToIso()`.
|
|
87
|
+
- **Running-fix visualization & interactive resolve** (work doc #13):
|
|
88
|
+
- `POST /fix/resolve` candidates now carry per-observation
|
|
89
|
+
`advancements` — the original reference point as taken, the
|
|
90
|
+
DR-transported advanced point, and the displacement used (null when
|
|
91
|
+
not advanced) — so the preview can show the transport instead of
|
|
92
|
+
only the final fix.
|
|
93
|
+
- **Pending observations are first-class**: new `<dr-pending-list>`
|
|
94
|
+
panel alongside the map (moved out of the sight-entry modal) with
|
|
95
|
+
per-row select (map highlight), edit and delete. A single pending
|
|
96
|
+
observation shows a "needs a partner" hint. "Preview selected"
|
|
97
|
+
resolves just the checked subset into a live candidate ring;
|
|
98
|
+
confirm stays the deliberate second step.
|
|
99
|
+
- **Advancement layer on the map**: for each previewed observation,
|
|
100
|
+
the faded original point, the dashed DR-run vector, the advanced
|
|
101
|
+
point and the advanced LOP line. Older observations that could not
|
|
102
|
+
be advanced (no DR track over the interval) render in a warning
|
|
103
|
+
style and the candidate ring flags "includes un-advanced
|
|
104
|
+
observation" — the honest failure made visible.
|
|
105
|
+
- **Map-click detail popover** (`<dr-detail-popover>`): click any
|
|
106
|
+
LOP, CPL or fix on the map for its full record, with Edit (LOP/CPL
|
|
107
|
+
→ seeded sight form; fix → inline notes editor) and Delete actions.
|
|
108
|
+
- **Observation & fix CRUD** (REST + db): `DELETE`/`PUT
|
|
109
|
+
/fix/lop/:id`, `/fix/cpl/:id`, `/fix/:id`. LOPs/CPLs attached to a
|
|
110
|
+
confirmed fix are guarded (409 — delete the fix first); deleting a
|
|
111
|
+
fix un-confirms it: its observations return to pending, the
|
|
112
|
+
correction row and any queued logbook entry are dropped, and the
|
|
113
|
+
DR origin is not rewound. Fix edits allow only audit metadata
|
|
114
|
+
(notes, confirmed_by, estimated error radius) — position and
|
|
115
|
+
source_type are guarded.
|
|
116
|
+
- **Phone-first layout pass** for the fix workflow: dialogs become
|
|
117
|
+
bottom sheets on narrow viewports, tap targets ≥ 44 px, no
|
|
118
|
+
hover-only affordances.
|
|
119
|
+
- **"Fix at coordinates" dialog** (replaces the one-tap "Fix at GPS"
|
|
120
|
+
button): opens prefilled with the live GNSS position, editable before
|
|
121
|
+
confirming. Covers three point-fix workflows with one flow —
|
|
122
|
+
the GPS reality check (accept the prefill as-is), known-position fixes
|
|
123
|
+
(type a berth/dock position, works without GNSS), and offline fixes
|
|
124
|
+
from paper forms (backdated fix time, `backfill` source type). The
|
|
125
|
+
dialog shows GNSS fix-quality stats when the receiver publishes them
|
|
126
|
+
(`navigation.gnss.*`): system (GPS/GLONASS/…), fix method (2D/3D/DGNSS),
|
|
127
|
+
satellites in use/visible, and HDOP with a rough error estimate that
|
|
128
|
+
prefills the new estimated-error field. Coordinates accept decimal,
|
|
129
|
+
DM or DMS free text so they can be transcribed from paper exactly as
|
|
130
|
+
written; editing a prefilled GNSS coordinate switches the source to
|
|
131
|
+
manual automatically.
|
|
132
|
+
- `POST /fix` accepts optional `timestamp`, `notes` and
|
|
133
|
+
`estimated_error_nm` on point fixes (the fix pipeline already
|
|
134
|
+
supported them; the route now forwards them). Backfilled fixes are
|
|
135
|
+
recorded at their observation time — in `fixes`, `dr_corrections` and
|
|
136
|
+
the signalk-logbook write-through — not at entry time. `backfill`
|
|
137
|
+
fixes get a distinct map color.
|
|
138
|
+
- Sea-trial safety hardening — honest DR under sensor failure:
|
|
139
|
+
- **Idle-while-making-way detection**: when STW/heading drop but
|
|
140
|
+
GPS-derived motion shows the vessel still making way (fouled
|
|
141
|
+
paddlewheel, compass dropout), the frozen DR position is flagged
|
|
142
|
+
`moving: true` in `navigation.deadReckoning.state`, the uncertainty
|
|
143
|
+
polygon keeps growing by GPS-derived ground distance (instead of
|
|
144
|
+
freezing with the water track), and a §3.1 sensor-health alert
|
|
145
|
+
(`notifications.navigation.deadReckoning.status`) is raised:
|
|
146
|
+
"DR stopped tracking… position is stale". GPS remains authoritative
|
|
147
|
+
until proven faulty or OVERRIDE — the watchkeeper is informed, not
|
|
148
|
+
left with falsely-confident DR.
|
|
149
|
+
- **Paddlewheel fouling surfaced**: `detectFouling`'s verdict (STW≈0
|
|
150
|
+
while SOG/wind indicate motion) now raises the same §3.1 alert and
|
|
151
|
+
sets `fouled: true` on the state, instead of silently gating
|
|
152
|
+
training.
|
|
153
|
+
- **Transient flag**: the underway state now carries
|
|
154
|
+
`transient: true` during a tack/gybe — the UI explains an expected
|
|
155
|
+
divergence spike instead of reading it as a fault.
|
|
156
|
+
- **"Since last fix" headline**: `navigation.deadReckoning.elapsedSinceFix`
|
|
157
|
+
(s, per-tick) drives the previously-unwired UI figure — the
|
|
158
|
+
watchkeeper's fix-cadence cue (`elapsedText` formatter in the
|
|
159
|
+
view-model).
|
|
160
|
+
- UI status panel renders the new states with distinct styling:
|
|
161
|
+
stale-DR / fouled (red), maneuver-in-progress (amber).
|
|
162
|
+
- Signal K Weather API current (SPEC §6.2 tier 3): a new
|
|
163
|
+
`plugin/current.js` subsystem polls
|
|
164
|
+
`/signalk/v2/api/weather/forecasts/point` at the vessel position
|
|
165
|
+
(default every 30 min, off the 1 Hz hot path) and integrates the
|
|
166
|
+
point-forecast `current` — `set` (rad) / `drift` (m/s), converted and
|
|
167
|
+
u/v-interpolated between bracketing forecast entries — into the DR
|
|
168
|
+
solution as set/drift. Offshore this is typically backed by a GRIB
|
|
169
|
+
another process already downloaded, so it works without the plugin
|
|
170
|
+
itself having connectivity. `resolveCurrent` (moved from
|
|
171
|
+
`training.js`) resolves the full hierarchy: manual override (tier 1,
|
|
172
|
+
not yet wired to an input) → weather API (tier 3) → offline pilot
|
|
173
|
+
charts (tier 4, reserved hook) → zero vector (tier 5). A failed
|
|
174
|
+
fetch keeps the previous cache until its TTL lapses; the resolved
|
|
175
|
+
tier + source is published with `environment.current`. Config:
|
|
176
|
+
`weatherCurrent.enabled` (default on), `.intervalMs`. The endpoint
|
|
177
|
+
requires no authentication (verified against a live server), so no
|
|
178
|
+
token plumbing is needed.
|
|
179
|
+
|
|
180
|
+
### Fixed
|
|
181
|
+
- **GPS track history backfill was silently 404ing**: the webapp
|
|
182
|
+
queried `/signalk/v1/history/values`, but the history API (and the
|
|
183
|
+
installed `signalk-history-sqlite` provider) serves
|
|
184
|
+
`/signalk/v2/api/history/values` — the fallback to the live-session
|
|
185
|
+
track always kicked in. The query now uses the v2 endpoint with the
|
|
186
|
+
`duration` parameter from the History API contract.
|
|
187
|
+
- Sight panel assumed-position seeding threw on every DR/GPS position
|
|
188
|
+
update: the `seedCoord` sub-field selector was missing its closing
|
|
189
|
+
`]` (invalid selector), so the celestial form's assumed position
|
|
190
|
+
never tracked the boat. Fixed in the shared `dr-coord-fields.js`
|
|
191
|
+
module with a regression test.
|
|
192
|
+
- Sight panel DM/DMS submissions could send a stale seeded decimal
|
|
193
|
+
value instead of the user-edited deg/min/sec fields: the form parser
|
|
194
|
+
preferred the (hidden) decimal field whenever it was non-empty.
|
|
195
|
+
Reading is now driven by the panel's `data-pos-format` attribute, so
|
|
196
|
+
only the fields the user actually sees are read.
|
|
197
|
+
- Windows CI: the plugin smoke tests leaked open SQLite handles in the
|
|
198
|
+
shared temp directory (four `makeStarted()` tests never called
|
|
199
|
+
`plugin.stop()`). On Linux/macOS an open file can still be unlinked; on
|
|
200
|
+
Windows the cleanup `rm` failed with `EBUSY: resource busy or locked`.
|
|
201
|
+
Those tests now stop the plugin, and the shared teardown retries the
|
|
202
|
+
removal (`maxRetries`/`retryDelay`, which `fs.rm` applies to EBUSY/EPERM
|
|
203
|
+
on Windows only) to ride out transient locks from AV scanners on CI
|
|
204
|
+
runners.
|
|
205
|
+
- `POST /fix/resolve` and `POST /fix` no longer return `observations not
|
|
206
|
+
resolvable` when called with only `lop_ids`/`cpl_ids` (the common sight
|
|
207
|
+
panel path). The pipeline now hydrates the observation bodies from the
|
|
208
|
+
database via the new `getLineOfPosition`/`getCircularPositionLine` db
|
|
209
|
+
helpers, then runs the geometric resolver on them.
|
|
210
|
+
- Bearing LOP no longer "runs through and past the object to the
|
|
211
|
+
opposite bearing." A bearing LOP is now drawn as a ray from the
|
|
212
|
+
charted object toward the navigator's side (the reciprocal of the
|
|
213
|
+
measured bearing), with a short stub past the object, instead of a
|
|
214
|
+
symmetric infinite line through the object. Celestial LOPs stay
|
|
215
|
+
symmetric infinite lines. `lopLineSpec` now exposes `lopType`, and
|
|
216
|
+
`extendLineSpec` renders bearing vs celestial LOPs differently.
|
|
217
|
+
- Sun-run-sun / running fix (SPEC §9.1): `resolveCandidateFix` now
|
|
218
|
+
advances earlier observations to the timestamp of the latest one along
|
|
219
|
+
the vessel's DR track before resolving, turning two LOPs taken at
|
|
220
|
+
different times into a fix. The displacement comes from a new
|
|
221
|
+
`GroundTrack` DR-history buffer (`plugin/ground-track.js`) fed by the
|
|
222
|
+
DR engine's water-track integration only — **never GPS** (celestial is
|
|
223
|
+
a GPS-independent position check; GPS is used only to calibrate DR
|
|
224
|
+
accuracy, not to advance celestial LOPs). Boats without water-track
|
|
225
|
+
sensors get no advance — the honest failure rather than a wrong fix.
|
|
226
|
+
New `advanceToLatest` + `input.advance` provider in the pipeline.
|
|
227
|
+
- "Fix at GPS" quick action in the DR toolbar: confirms a GPS point fix
|
|
228
|
+
(`source_type: "gps"`) at the current GNSS position — the GPS reality
|
|
229
|
+
check that snaps the DR origin to GPS when the watchkeeper judges GPS
|
|
230
|
+
good. Disabled when no GPS position is known.
|
|
231
|
+
- Noon Sun sight reduction (SPEC §13): `POST /celestial/sight` with
|
|
232
|
+
`noon: true` reduces a local-apparent-noon meridian-altitude sight to
|
|
233
|
+
latitude directly (Lat = Dec ± z) via `reduceNoonSight`, emitted as an
|
|
234
|
+
east-west LOP with zero intercept — a single-sight latitude fix that
|
|
235
|
+
crosses any other LOP/CPL normally through the existing pipeline.
|
|
236
|
+
- Observations logged to the logbook on creation (SPEC §9.5): a bearing
|
|
237
|
+
LOP, vertical-angle CPL, or celestial sight writes a `navigation` entry
|
|
238
|
+
via `composeObservationEntry` — taking the sight is itself a navigational
|
|
239
|
+
event, independent of whether it later resolves into a fix. The entry's
|
|
240
|
+
`position` is the assumed/object/charted position; `text` describes what
|
|
241
|
+
was observed (body name, Zn, intercept for celestial).
|
|
242
|
+
- Logbook write-through no longer loses entries in the approval window:
|
|
243
|
+
while no admin token is granted (access request pending, server
|
|
244
|
+
unreachable, or token expired mid-passage), every fix / tack /
|
|
245
|
+
observation entry is queued in a new `logbook_pending` SQLite table
|
|
246
|
+
(bounded to 200, ordered) and flushed oldest-first once a token lands —
|
|
247
|
+
nothing written during the approval window is dropped. The access flow
|
|
248
|
+
now distinguishes an open server (501/404 → unauthenticated writes) from
|
|
249
|
+
a transport failure (`unreachable` → queue + honest status, retry on
|
|
250
|
+
the next write instead of falsely claiming an open server), and a
|
|
251
|
+
tokenless write re-kicks `initLogbook()` so a lost/expired grant is
|
|
252
|
+
re-requested at write time, not only at startup. A denied access request
|
|
253
|
+
stops writes without re-request spam. Delayed fix deliveries mark their
|
|
254
|
+
`fixes` row logged (the confirm route only marks immediate writes).
|
|
255
|
+
- Initial project scaffold: package metadata, CI workflows, plugin entry
|
|
256
|
+
point with subscription/start/stop structure, SQLite schema layer,
|
|
257
|
+
dead-reckoning vector-integration engine, EMA matrix store, unified fix
|
|
258
|
+
model, and `<dr-map-view>` web component stub.
|
|
259
|
+
- DR web UI (SPEC §14.1): a pure, dependency-free view-model
|
|
260
|
+
(`public/dr-viewmodel.js` — TrackLog ring buffers, LOP/CPL/fix/correction
|
|
261
|
+
render specs, uncertainty + divergence helpers, sparkline reduction, and
|
|
262
|
+
a Signal K `resources/charts` tile-layer parser) backed by unit tests, and
|
|
263
|
+
a vendored-Leaflet `<dr-map-view>` adapter plus `<dr-app>` layout with
|
|
264
|
+
headline figures, dual Ghost/GPS track rendering, uncertainty polygon,
|
|
265
|
+
LOP/CPL/snap-vector overlays, the divergence chip + sparkline, and the
|
|
266
|
+
always-human-initiated Failover Override control. Tile-less by default
|
|
267
|
+
(offline-first); basemaps come from the server's configured charts
|
|
268
|
+
(`/signalk/v1/api/resources/charts`) via a Leaflet layers control, never
|
|
269
|
+
hardcoded OSM. New `GET /fixes`, `GET /observations`, `GET /corrections`
|
|
270
|
+
routes feed the persisted overlays.
|
|
271
|
+
- DR engine idle-state reporting: the plugin publishes
|
|
272
|
+
`navigation.deadReckoning.state` (`{status: "idle"|"underway", reason}`)
|
|
273
|
+
so the UI can explain why the readout is empty when moored/anchored or
|
|
274
|
+
lacking speed/heading, instead of leaving the user to guess. The
|
|
275
|
+
webapp shows a status banner (amber idle, green underway, red link-lost)
|
|
276
|
+
and the GPS boat marker is always drawn when a fix is available.
|
|
277
|
+
- Historical GPS track from the Signal K history API
|
|
278
|
+
(`/signalk/v1/history/values`, same pattern as signalk-logbook's Map):
|
|
279
|
+
fetched once on load to seed the track, then extended by live deltas —
|
|
280
|
+
shows where the boat has actually been, the baseline against which DR
|
|
281
|
+
divergence is measured. Falls back to the live-session track when no
|
|
282
|
+
history provider is configured (route 404s).
|
|
283
|
+
- Sight & LOP input panel (SPEC §14.1 "Manual LOP & Sight Input"):
|
|
284
|
+
a new `<dr-sight-panel>` web component (in a `<dialog>` opened from the
|
|
285
|
+
headline toolbar) with three modes — compass bearing (→ LOP through the
|
|
286
|
+
observer), vertical-angle sight (→ CPL by height/tan(angle) distance),
|
|
287
|
+
and celestial sight (→ LOP via Marcq St. Hilaire, with reduction
|
|
288
|
+
feedback: Hc/Ho/Zn/intercept/LHA). Observations collect in a pending
|
|
289
|
+
list; "Resolve" previews a candidate fix on the map (hollow yellow
|
|
290
|
+
ring); "Confirm" snaps the DR origin via the unified fix pipeline.
|
|
291
|
+
- **Chart pick**: right-click (or long-press) a charted object to get
|
|
292
|
+
a context menu ("Add bearing to here" / "Distance CPL at here") that
|
|
293
|
+
opens the sight dialog with the object position pre-seeded.
|
|
294
|
+
- **Pending list survives reload**: observations are persisted
|
|
295
|
+
server-side; reopening the dialog re-hydrates the pending list from
|
|
296
|
+
any unattached LOPs/CPLs (`used_in_fix_id IS NULL`), so a page
|
|
297
|
+
reload or dialog close doesn't lose work-in-progress. Each item
|
|
298
|
+
shows a readable label (e.g. "#5 · lighthouse brg 045°",
|
|
299
|
+
"#3 · lighthouse 0.46nm"). The candidate itself is a computed preview
|
|
300
|
+
(not persisted) but can be re-derived with "Resolve candidate".
|
|
301
|
+
- **Bearing LOP semantics corrected**: a bearing is taken to a known
|
|
302
|
+
charted object, so the form collects the *object's* position (not the
|
|
303
|
+
observer's assumed position). The view-model shaper rotates the
|
|
304
|
+
azimuth +90° so the engine's perpendicular-line convention yields a
|
|
305
|
+
line running along the bearing through the object (you are somewhere
|
|
306
|
+
on it), matching traditional nav practice.
|
|
307
|
+
- **Configurable position format** (`decimal` / `dm` / `dms`, default
|
|
308
|
+
DMS) set in the plugin config and served to the UI via a public
|
|
309
|
+
`GET /signalk/v2/api/signalk-dead-reckoning/configuration` endpoint
|
|
310
|
+
(mirrors signalk-status-tiles' pattern); a config-hash delta triggers
|
|
311
|
+
a live reload on server-side edits. Coordinate entry uses structured
|
|
312
|
+
deg/min/sec/hemisphere fields (not a single error-prone text field)
|
|
313
|
+
that show/hide based on the configured format. Assumed-position
|
|
314
|
+
defaults track the live DR (or GPS when moored) and re-seed on format
|
|
315
|
+
change.
|
|
316
|
+
- **Server-derived `confirmed_by`**: the watchkeeper is taken from the
|
|
317
|
+
`JAUTHENTICATION` cookie JWT (mirrors signalk-logbook), so there is
|
|
318
|
+
no manual "confirmed by" form field.
|
|
319
|
+
Pure view-model form→REST-body shapers (`bearingLopBody`,
|
|
320
|
+
`verticalAngleCplBody`, `celestialSightBody`, `bearingToTrue`,
|
|
321
|
+
`verticalAngleDistanceNm`) and the position formatter
|
|
322
|
+
(`formatCoord`/`parseCoord`/`coordParts`/`parseParts` + `setFormat`/
|
|
323
|
+
`fmt`/`fmtPos`) are unit-tested. New `GET /celestial/bodies` route
|
|
324
|
+
lists Sun/Moon/bundled stars + almanac validity for the body selector.
|
|
325
|
+
- Stream subscription hardened after signalk-status-tiles' st-stream.js:
|
|
326
|
+
`/signalk/v1/stream?subscribe=none&sendMeta=all` URL, `minPeriod: 1000`
|
|
327
|
+
throttle, auto-reconnect on link loss with immediate re-subscribe,
|
|
328
|
+
hello/ack filtering, and link-state reporting to the UI.
|
|
329
|
+
- Unified fix pipeline (SPEC §4.4, §9.1, §9.3): DB helpers for inserting
|
|
330
|
+
lines of position and circular position lines and attaching them to a
|
|
331
|
+
confirmed fix; a pure local-planar geometric resolver (Line×Line,
|
|
332
|
+
Line×Circle, Circle×Circle, with least-squares residual fallback for
|
|
333
|
+
the cocked-hat case) returning a candidate fix plus a residual spread
|
|
334
|
+
and any alternate Circle×Circle candidate; running-fix advance of an
|
|
335
|
+
observation along the DR track; and a `fix-pipeline.js` orchestrator
|
|
336
|
+
(`resolveCandidateFix` → human confirmation → `confirmFix`) that
|
|
337
|
+
writes the `fixes` row, attaches observations, records a
|
|
338
|
+
`dr_corrections` row on origin-reset, and snaps the DR engine origin
|
|
339
|
+
without flipping navigational authority.
|
|
340
|
+
- REST fix pipeline (SPEC §4.4, §9.1, §9.3): `POST /fix/lop` and
|
|
341
|
+
`POST /fix/cpl` persist lines/circular position lines and return their
|
|
342
|
+
ids; `POST /fix/resolve` previews a candidate fix (with cocked-hat
|
|
343
|
+
residual and any alternate Circle×Circle candidate) WITHOUT
|
|
344
|
+
confirming; `POST /fix` now routes both point fixes and LOP/CPL-resolved
|
|
345
|
+
fixes through the unified pipeline, attaching observations and recording
|
|
346
|
+
corrections. Point-fix request shape is unchanged (back-compat).
|
|
347
|
+
- Celestial sight reduction (SPEC §13): pure `plugin/celestial.js`
|
|
348
|
+
module (Marcq St. Hilaire / intercept method) producing a LOP ready
|
|
349
|
+
for the fix pipeline — Sun/Moon geographic positions via GMST + RA,
|
|
350
|
+
star positions via a bundled `plugin/star-almanac.js` (J2000 SHA/Dec
|
|
351
|
+
for ~23 navigational stars, with an explicit valid epoch and
|
|
352
|
+
`isExpired`/`daysUntilExpiry` for the §12-style startup check);
|
|
353
|
+
altitude corrections (index error, dip, Bennett refraction with a
|
|
354
|
+
5° low-altitude cutoff, Sun/Moon limb semi-diameter, lunar parallax);
|
|
355
|
+
`reduceSight` carries the time-sync staleness indicator (§11) through
|
|
356
|
+
to the result. `POST /celestial/sight` REST endpoint reduces a raw
|
|
357
|
+
sight and persists the resulting celestial LOP, returning the
|
|
358
|
+
reduction details (Hc, Ho, intercept, Zn, LHA) for UI feedback.
|
|
359
|
+
- Uncertainty polygon (SPEC §8): pure `plugin/uncertainty.js` growth
|
|
360
|
+
model producing a confidence-weighted circular error region around
|
|
361
|
+
the DR position — empirical regime from an EWMA of recent
|
|
362
|
+
`dr_corrections` deviation rates (per-condition via sail/sea state,
|
|
363
|
+
converted to a per-distance rate so the radius scales with distance
|
|
364
|
+
run, not clock time), conservative angular-margin fallback for
|
|
365
|
+
low-confidence bins, and a continuous blend between them weighted by
|
|
366
|
+
the current matrix bin's effective hit count. Engine gained
|
|
367
|
+
`logNmSinceOrigin` (distance-since-last-snap) alongside
|
|
368
|
+
`elapsedSinceOriginS`, persisted in `dr_state_store` so a
|
|
369
|
+
mid-excursion restart continues the polygon. Published every tick as
|
|
370
|
+
`navigation.deadReckoning.uncertainty` `{radius_nm, method}` with
|
|
371
|
+
meta; recomputed per tick from the current bin so bin transitions
|
|
372
|
+
(tack, sail change) re-evaluate it. New `db.getDeviationRateStats`
|
|
373
|
+
reads recent per-condition correction rows for the model.
|
|
374
|
+
- Divergence advisory (SPEC §7.3, gradual band): pure
|
|
375
|
+
`plugin/divergence.js` monitor with sustained-interval hysteresis
|
|
376
|
+
(raises when DR-vs-GPS divergence exceeds 1.5× the uncertainty
|
|
377
|
+
polygon radius for 30s, clears on 30s sustained recovery; both
|
|
378
|
+
tunable via `start({divergence: {...}})`) — the "get a fix" nudge
|
|
379
|
+
the polygon was built to threshold. Publishes/clears
|
|
380
|
+
`notifications.navigation.deadReckoning.divergenceAdvisory` at
|
|
381
|
+
`alert` severity (visual method) with the divergence/expected
|
|
382
|
+
numbers in the message, suppressed at anchor/moored, held (not
|
|
383
|
+
progressed) when either position is missing, and cleared on plugin
|
|
384
|
+
stop if live. Also publishes
|
|
385
|
+
`navigation.deadReckoning.divergence` `{distance_nm, bearing_true}`
|
|
386
|
+
each tick — the §14.1 live divergence readout input.
|
|
387
|
+
- Logbook integration (SPEC §9.4, §9.5): `plugin/logbook.js` with
|
|
388
|
+
§9.5 field-mapped fix-entry composition (explicit `datetime`,
|
|
389
|
+
DR-log for `log` per §10.3, per-source_type text templates,
|
|
390
|
+
`origin: agent`, closed-schema observations), auto tack/gybe
|
|
391
|
+
entries, and a REST client sending auth as both Bearer header and
|
|
392
|
+
JAUTHENTICATION cookie (the signalk-dsc pattern; no `app.fetch` —
|
|
393
|
+
verified against signalk-server 2.29.0). Token acquisition via
|
|
394
|
+
the server's Access Requests flow: stable persisted clientId,
|
|
395
|
+
`permissions: "admin"` requested explicitly (plugin routes are
|
|
396
|
+
admin-gated — verified), 30s approval polling, DENIED stops
|
|
397
|
+
polling, 401/403 drops the token and re-requests; a config token
|
|
398
|
+
short-circuits and 501/404 falls back to unauthenticated writes.
|
|
399
|
+
Confirmed fixes write through fire-and-forget and mark
|
|
400
|
+
`logged_to_logbook`/`logbook_entry_ref` on success (`db.markFixLogged`);
|
|
401
|
+
the GPS auto-seed snap does not (not human-confirmed). Maneuver
|
|
402
|
+
classification from the AWA change across the §6.4 transient
|
|
403
|
+
window (pre-maneuver AWA captured at window open; tack = bow
|
|
404
|
+
through the wind within ±90°, gybe = stern through within ±60° of
|
|
405
|
+
downwind), debounced (default 120s) so a beat doesn't flood the
|
|
406
|
+
log; the window now also requires the heading itself to
|
|
407
|
+
re-stabilize (±5°) before closing, so the logged course is the
|
|
408
|
+
settled one. `environment.seaState` (and the logbook's actual
|
|
409
|
+
`environment.water.swell.state`) now feed `sea_state` everywhere —
|
|
410
|
+
bins, dr_corrections, and polygon rates become condition-specific
|
|
411
|
+
for real.
|
package/README.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Signal K Dead Reckoning
|
|
2
|
+
|
|
3
|
+
An offline-first dead reckoning and sensor fusion engine for Signal K that maintains a continuously computed "shadow boat" position from water-track sensors (speed through water, compass heading, and learned leeway and current corrections), so you always have a navigational fallback when GPS becomes unreliable — whether from jamming, spoofing, or plain receiver failure. While GPS is trusted, the engine learns vessel-specific calibration corrections against ground truth and watches for GPS anomalies; when it isn't, the same learned model keeps the dead-reckoned position, its uncertainty polygon, and a water-track log going. Fixes from celestial sights, compass bearings, and vertical angles are entered through a unified pipeline and can snap dead reckoning back on track, with optional write-through to `signalk-logbook`. Historical data can be backfilled to train the calibration model and backtest it against past passages. Licensed under the EUPL-1.2.
|
|
4
|
+
|
|
5
|
+
**Note:** This is just a toy. Make your own navigation calculations and decisions.
|