signalk-sailboat-simulator 0.2.0 → 0.3.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 CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.0] - 2026-09-09
4
+
5
+ - Calculate simulated boat speed directly from the active `signalk-polar-management` polar resource via `polar-math`.
6
+ - Replace the `signalk-polar-performance-plugin` recommendation with `signalk-polar-management`.
7
+ - Stop requiring `performance.polarSpeed` as an input path.
8
+ - Keep the active polar cached for up to 60 seconds so edits made under the same resource id are picked up automatically.
9
+
3
10
  ## [0.2.0] - 2026-08-17
4
11
 
5
12
  - Follow `steering.autopilot.output.turnRate` from `signalk-autopilot-emulator-v2` directly instead of deriving and rate-limiting turns from autopilot target headings.
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Signal K Sailboat Simulator
2
2
 
3
- Signal K plugin that simulates a sailing boat by integrating a virtual position from data already present in Signal K: autopilot turn-rate output, weather and polar performance.
3
+ Signal K plugin that simulates a sailing boat by integrating a virtual position from data already present in Signal K: autopilot turn-rate output, weather and an active polar resource.
4
4
 
5
- The design goal is to keep this plugin small. It publishes only the simulated vessel state and expects specialised plugins to provide autopilot intent, weather and polar calculations.
5
+ The design goal is to keep this plugin small. It publishes only the simulated vessel state and expects specialised plugins to provide autopilot intent, weather and polar management.
6
6
 
7
7
  ## Quick Start
8
8
 
@@ -11,11 +11,11 @@ The easiest setup is to use the Signal K App Store:
11
11
  1. Install these recommended plugins:
12
12
  - `@signalk/open-meteo-provider`
13
13
  - `signalk-autopilot-emulator-v2`
14
- - `signalk-polar-performance-plugin`
14
+ - `signalk-polar-management`
15
15
  - `signalk-derived-data`
16
16
  - `signalk-distance-to-shore`
17
17
  2. Configure `@signalk/open-meteo-provider` and make it the default Signal K Weather API provider. You can also leave the Signal K default as-is and set `wind.providerId` to `open-meteo` in this plugin.
18
- 3. Configure `signalk-polar-performance-plugin` with a polar file for the simulated boat.
18
+ 3. Open `signalk-polar-management`, import a polar file for the simulated boat, and set it as the active polar.
19
19
  4. Configure `signalk-derived-data` to publish `navigation.magneticVariation`, so the simulator can publish `navigation.headingMagnetic` for plugins that need magnetic heading.
20
20
  5. Install and enable `signalk-sailboat-simulator`.
21
21
  6. Keep the default simulator options for a first run.
@@ -36,7 +36,7 @@ Minimum capabilities for a useful simulation:
36
36
 
37
37
  - Autopilot output: `signalk-autopilot-emulator-v2` provides `steering.autopilot.output.turnRate`, which the simulator integrates as the boat heading change.
38
38
  - Weather: `@signalk/open-meteo-provider` can provide Weather API observations or forecasts at the simulated position.
39
- - Polar performance: `signalk-polar-performance-plugin` uses the published true wind values to calculate `performance.polarSpeed`, which the simulator uses as boat speed.
39
+ - Polar management: `signalk-polar-management` stores the simulated boat polars and publishes the active polar resource. The simulator uses `polar-math` internally to calculate boat speed from that active polar.
40
40
  - Derived data: enable the `signalk-derived-data` option that publishes `navigation.magneticVariation`; when this path is available, the simulator also publishes `navigation.headingMagnetic` for plugins that need magnetic heading.
41
41
  - Shore distance: `signalk-distance-to-shore` can provide `navigation.distanceToShore` and `navigation.shore.bearingTrue` for grounding protection.
42
42
 
@@ -61,13 +61,13 @@ Wind:
61
61
  - `environment.wind.speedApparent`
62
62
  - `environment.wind.angleApparent`
63
63
 
64
- Performance input expected by the simulator:
64
+ Polar input expected by the simulator:
65
65
 
66
- - `performance.polarSpeed`
66
+ - `polars.activePolar`
67
67
 
68
68
  All values are published with `$source: signalk-sailboat-simulator`.
69
69
 
70
- The plugin status will show `waitingForPerformance` until `performance.polarSpeed` is available. If `steering.autopilot.output.turnRate` is missing, the simulated boat keeps its current heading. If grounding protection is enabled and `navigation.distanceToShore` is below the configured minimum, the status will show `groundingProtection` and the simulated boat will stop.
70
+ The plugin status will show `waitingForPolar` until an active polar is selected, `waitingForWind` until weather wind is available, or `waitingForPolarSpeed` when the active polar cannot produce a speed for the current wind angle. If `steering.autopilot.output.turnRate` is missing, the simulated boat keeps its current heading. If grounding protection is enabled and `navigation.distanceToShore` is below the configured minimum, the status will show `groundingProtection` and the simulated boat will stop.
71
71
 
72
72
  ## Input contract
73
73
 
@@ -75,11 +75,12 @@ The simulator reads these fixed Signal K input paths:
75
75
 
76
76
  - `steering.autopilot.output.turnRate`: desired heading change rate in rad/s
77
77
  - `navigation.magneticVariation`: optional magnetic variation in radians, used only to publish `navigation.headingMagnetic`
78
- - `performance.polarSpeed`: boat speed from the active polar in m/s
78
+ - `polars.activePolar`: active polar resource pointer published by `signalk-polar-management`
79
+ - `polars.performanceFactor`: optional speed multiplier published by `signalk-polar-management`
79
80
  - `navigation.distanceToShore`: optional distance to the nearest coast in m, used for grounding protection
80
81
  - `navigation.shore.bearingTrue`: optional bearing from the vessel to the nearest coast in radians, used to allow recovery headings away from shore
81
82
 
82
- When true or apparent wind publishing is enabled, wind is read from Signal K Weather API data at the simulated position. The simulator first tries observations, then falls back to the closest point forecast when no usable observation is available.
83
+ Wind is always read from Signal K Weather API data at the simulated position because it is required for the internal polar speed calculation. The simulator first tries observations, then falls back to the closest point forecast when no usable observation is available. The true and apparent wind options only control whether those values are also published as Signal K paths.
83
84
 
84
85
  By default the simulator uses the Signal K default weather provider. Set `wind.providerId` to a registered provider id, for example `open-meteo`, to use that provider explicitly.
85
86
 
@@ -93,9 +94,9 @@ The simulator publishes virtual wind for the rest of the Signal K stack. The pub
93
94
  - `environment.wind.speedApparent`
94
95
  - `environment.wind.angleApparent`
95
96
 
96
- This gives `signalk-polar-performance-plugin` the `environment.wind.speedTrue` and `environment.wind.angleTrueWater` inputs it needs to calculate `performance.polarSpeed`.
97
+ The simulator uses the same true wind values with the active polar resource to calculate boat speed. The active polar must use the canonical `polar-format` structure provided by `signalk-polar-management`.
97
98
 
98
- The first version uses `performance.polarSpeed` as the boat speed, integrates heading from `steering.autopilot.output.turnRate`, and integrates position along that simulated heading. Current, leeway, route following and manoeuvre rules are intentionally left as separate steps.
99
+ The simulator uses the calculated polar speed as boat speed, integrates heading from `steering.autopilot.output.turnRate`, and integrates position along that simulated heading. Current, leeway, route following and manoeuvre rules are intentionally left as separate steps.
99
100
 
100
101
  ## Configuration
101
102
 
@@ -107,16 +108,16 @@ The plugin intentionally keeps its configuration surface small:
107
108
  Options: latitude, longitude and heading in degrees. These values are only used when persistence is disabled or no previous runtime state has been saved.
108
109
 
109
110
  - `wind`
110
- What it does: reads wind at the simulated position from the Signal K Weather API and publishes the selected wind values.
111
- Goal: feed `signalk-polar-performance-plugin` with wind data so it can calculate `performance.polarSpeed`, which the simulator then uses as boat speed.
112
- Requires: a Weather API provider. The recommended provider is `@signalk/open-meteo-provider`; the recommended polar plugin is `signalk-polar-performance-plugin`.
111
+ What it does: always reads wind at the simulated position from the Signal K Weather API for the speed calculation, and optionally publishes the selected wind values.
112
+ Goal: provide the true wind data used by the simulator to calculate polar speed, and publish that same virtual wind for other Signal K tools.
113
+ Requires: a Weather API provider. The recommended provider is `@signalk/open-meteo-provider`.
113
114
  Options: publish true wind, publish apparent wind, optional provider id, and polling interval.
114
115
  Published true wind paths: `environment.wind.speedTrue`, `environment.wind.directionTrue`, `environment.wind.angleTrueWater`.
115
116
  Published apparent wind paths: `environment.wind.speedApparent`, `environment.wind.angleApparent`.
116
117
 
117
118
  - `publishing`
118
119
  What it does: publishes the simulated navigation state.
119
- Requires: `steering.autopilot.output.turnRate` to steer and `performance.polarSpeed` to move. Recommended providers are `signalk-autopilot-emulator-v2`, `signalk-polar-performance-plugin`, and `signalk-derived-data` when magnetic heading output is needed.
120
+ Requires: `steering.autopilot.output.turnRate` to steer, an active polar from `signalk-polar-management`, and weather wind to calculate boat speed. Recommended providers are `signalk-autopilot-emulator-v2`, `signalk-polar-management`, and `signalk-derived-data` when magnetic heading output is needed.
120
121
  Options: publish navigation state.
121
122
  Published paths: `navigation.position`, `navigation.headingTrue`, `navigation.headingMagnetic` when `navigation.magneticVariation` is available, `navigation.courseOverGroundTrue`, `navigation.speedOverGround`, `navigation.speedThroughWater`.
122
123
 
@@ -169,7 +170,8 @@ Then restart Signal K.
169
170
  - `@signalk/open-meteo-provider`: https://www.npmjs.com/package/@signalk/open-meteo-provider
170
171
  - `signalk-autopilot-emulator-v2`: https://www.npmjs.com/package/signalk-autopilot-emulator-v2
171
172
  - `signalk-autopilot-emulator-v2` source: https://github.com/macjl/signalk-autopilot-emulator-v2
172
- - `signalk-polar-performance-plugin`: https://www.npmjs.com/package/signalk-polar-performance-plugin
173
+ - `signalk-polar-management`: https://www.npmjs.com/package/signalk-polar-management
174
+ - `polar-math`: https://www.npmjs.com/package/polar-math
173
175
  - `signalk-derived-data`: https://www.npmjs.com/package/signalk-derived-data
174
176
  - `signalk-distance-to-shore`: https://www.npmjs.com/package/signalk-distance-to-shore
175
177
 
package/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
 
3
+ const { Polar } = require('polar-math')
3
4
  const { createInitialState, stepSimulation } = require('./lib/simulation-engine')
4
5
  const { buildSchema } = require('./lib/plugin-schema')
5
6
  const { degToRad, radToDeg, wrap360Rad } = require('./lib/angles')
@@ -8,11 +9,13 @@ const { applyPersistedState, createStateStore, stateFromSimulation } = require('
8
9
 
9
10
  const PLUGIN_ID = 'sailboat-simulator'
10
11
  const PUBLISH_SOURCE = 'signalk-sailboat-simulator'
12
+ const POLAR_CACHE_MAX_AGE_MS = 60_000
11
13
 
12
14
  const INPUT_PATHS = {
13
15
  turnRatePath: 'steering.autopilot.output.turnRate',
14
16
  magneticVariationPath: 'navigation.magneticVariation',
15
- performanceSpeedPath: 'performance.polarSpeed',
17
+ activePolarPath: 'polars.activePolar',
18
+ performanceFactorPath: 'polars.performanceFactor',
16
19
  distanceToShorePath: 'navigation.distanceToShore',
17
20
  shoreBearingTruePath: 'navigation.shore.bearingTrue'
18
21
  }
@@ -60,11 +63,12 @@ module.exports = function createPlugin (app) {
60
63
  let lastStateSaveAt = 0
61
64
  let weatherSnapshot = null
62
65
  let stateStore = null
66
+ let polarCache = null
63
67
 
64
68
  const plugin = {
65
69
  id: PLUGIN_ID,
66
70
  name: 'Sailboat Simulator',
67
- description: 'Simulates a sailing boat position from Signal K autopilot turn-rate output, weather and polar performance data.',
71
+ description: 'Simulates a sailing boat position from Signal K autopilot turn-rate output, weather and an active polar resource.',
68
72
  schema: buildSchema,
69
73
  start,
70
74
  stop,
@@ -78,6 +82,7 @@ module.exports = function createPlugin (app) {
78
82
  lastWeatherFetchAt = 0
79
83
  lastStateSaveAt = 0
80
84
  weatherSnapshot = null
85
+ polarCache = null
81
86
  stateStore = createStateStore(app, PLUGIN_ID)
82
87
  state = createInitialState(options)
83
88
  if (options.persistence.enabled) {
@@ -103,7 +108,7 @@ module.exports = function createPlugin (app) {
103
108
  const now = Date.now()
104
109
  try {
105
110
  await refreshWeather(now)
106
- const inputs = readInputs()
111
+ const inputs = await readInputs()
107
112
  state = stepSimulation(state, inputs, options, now)
108
113
  publishState(inputs)
109
114
  persistState(now)
@@ -126,12 +131,13 @@ module.exports = function createPlugin (app) {
126
131
  if (stateStore.save(persisted)) lastStateSaveAt = now
127
132
  }
128
133
 
129
- function readInputs () {
134
+ async function readInputs () {
130
135
  const weatherWind = freshWeatherWind()
131
- return {
136
+ const inputs = {
132
137
  turnRate: readNumber(INPUT_PATHS.turnRatePath),
133
138
  magneticVariation: readNumber(INPUT_PATHS.magneticVariationPath),
134
- polarSpeed: readNumber(INPUT_PATHS.performanceSpeedPath),
139
+ activePolar: readValue(INPUT_PATHS.activePolarPath),
140
+ performanceFactor: readNumber(INPUT_PATHS.performanceFactorPath),
135
141
  windSpeedTrue: weatherWind && weatherWind.speedTrue != null
136
142
  ? weatherWind.speedTrue
137
143
  : null,
@@ -143,14 +149,89 @@ module.exports = function createPlugin (app) {
143
149
  weatherObservedAt: weatherWind ? weatherWind.observedAt : null,
144
150
  weatherDescription: weatherWind ? weatherWind.description : ''
145
151
  }
152
+ const polarSpeed = await calculatePolarSpeed(inputs)
153
+ inputs.polarSpeed = polarSpeed.speed
154
+ inputs.polar = polarSpeed.status
155
+ return inputs
146
156
  }
147
157
 
148
- function readNumber (path) {
158
+ function readValue (path) {
149
159
  if (!path || typeof app.getSelfPath !== 'function') return null
150
- const value = app.getSelfPath(`${path}.value`)
160
+ return app.getSelfPath(`${path}.value`)
161
+ }
162
+
163
+ function readNumber (path) {
164
+ const value = readValue(path)
151
165
  return Number.isFinite(value) ? value : null
152
166
  }
153
167
 
168
+ async function calculatePolarSpeed (inputs) {
169
+ const activePolar = activePolarFromValue(inputs.activePolar)
170
+ const performanceFactor = Number.isFinite(inputs.performanceFactor)
171
+ ? inputs.performanceFactor
172
+ : 1
173
+ const status = {
174
+ status: 'missingActivePolar',
175
+ id: activePolar.id,
176
+ href: activePolar.href,
177
+ performanceFactor,
178
+ speedState: null,
179
+ speed: null,
180
+ windAngleTrueWaterDeg: null
181
+ }
182
+
183
+ if (!activePolar.id) {
184
+ return { speed: null, status }
185
+ }
186
+
187
+ if (!Number.isFinite(inputs.windSpeedTrue) || !Number.isFinite(inputs.windDirectionTrue)) {
188
+ status.status = 'waitingForWind'
189
+ return { speed: null, status }
190
+ }
191
+
192
+ const windAngleTrueWater = trueWindFromDirection(inputs.windDirectionTrue, state.headingTrue)
193
+ status.windAngleTrueWaterDeg = Number.isFinite(windAngleTrueWater)
194
+ ? radToDeg(windAngleTrueWater)
195
+ : null
196
+
197
+ try {
198
+ const polar = await loadActivePolar(activePolar.id)
199
+ const result = polar.speedAt({
200
+ tws: inputs.windSpeedTrue,
201
+ twa: windAngleTrueWater,
202
+ performanceFactor
203
+ })
204
+ status.speedState = result.state
205
+ if (Number.isFinite(result.value)) {
206
+ status.status = 'ready'
207
+ status.speed = result.value
208
+ return { speed: result.value, status }
209
+ }
210
+ status.status = 'noSpeedForConditions'
211
+ return { speed: null, status }
212
+ } catch (error) {
213
+ status.status = 'error'
214
+ status.error = error.message
215
+ return { speed: null, status }
216
+ }
217
+ }
218
+
219
+ async function loadActivePolar (id) {
220
+ const now = Date.now()
221
+ if (polarCache && polarCache.id === id && now - polarCache.loadedAt < POLAR_CACHE_MAX_AGE_MS) {
222
+ return polarCache.polar
223
+ }
224
+
225
+ if (!app.resourcesApi || typeof app.resourcesApi.getResource !== 'function') {
226
+ throw new Error('Signal K resources API is not available')
227
+ }
228
+
229
+ const table = await app.resourcesApi.getResource('polars', id)
230
+ const polar = Polar.fromTable(table)
231
+ polarCache = { id, polar, loadedAt: now }
232
+ return polar
233
+ }
234
+
154
235
  function publishState (inputs) {
155
236
  if (!state || !app.handleMessage) return
156
237
 
@@ -224,12 +305,7 @@ module.exports = function createPlugin (app) {
224
305
  return options.wind.apparentWind !== false
225
306
  }
226
307
 
227
- function publishesAnyWind () {
228
- return publishTrueWind() || publishApparentWind()
229
- }
230
-
231
308
  async function refreshWeather (now) {
232
- if (!publishesAnyWind()) return
233
309
  const intervalSeconds = freshWeatherWind()
234
310
  ? options.wind.pollIntervalSeconds
235
311
  : options.wind.retryIntervalSeconds
@@ -355,9 +431,7 @@ module.exports = function createPlugin (app) {
355
431
  function updateRuntime (inputs) {
356
432
  const headingMagnetic = magneticHeadingFromTrue(state.headingTrue, inputs.magneticVariation)
357
433
  runtime = {
358
- status: state.groundingProtectionActive
359
- ? 'groundingProtection'
360
- : Number.isFinite(state.speedThroughWater) && state.speedThroughWater > 0 ? 'sailing' : 'waitingForPerformance',
434
+ status: runtimeStatus(inputs),
361
435
  position: state.position,
362
436
  headingTrueDeg: radToDeg(state.headingTrue),
363
437
  headingMagneticDeg: Number.isFinite(headingMagnetic) ? radToDeg(headingMagnetic) : null,
@@ -377,11 +451,14 @@ module.exports = function createPlugin (app) {
377
451
  providerId: weatherSnapshot.providerId,
378
452
  description: weatherSnapshot.description
379
453
  }
380
- : { status: publishesAnyWind() ? 'missing' : 'disabled' },
454
+ : { status: 'missing' },
455
+ polar: inputs.polar || { status: 'missingActivePolar' },
381
456
  inputs: {
382
457
  turnRate: valueStatus(inputs.turnRate),
383
458
  magneticVariation: valueStatus(inputs.magneticVariation),
384
- performanceSpeed: valueStatus(inputs.polarSpeed),
459
+ activePolar: activePolarFromValue(inputs.activePolar).id ? 'present' : 'missing',
460
+ performanceFactor: valueStatus(inputs.performanceFactor),
461
+ polarSpeed: valueStatus(inputs.polarSpeed),
385
462
  distanceToShore: valueStatus(inputs.distanceToShore),
386
463
  shoreBearingTrue: valueStatus(inputs.shoreBearingTrue),
387
464
  windSpeedTrue: valueStatus(inputs.windSpeedTrue),
@@ -391,6 +468,16 @@ module.exports = function createPlugin (app) {
391
468
  }
392
469
  }
393
470
 
471
+ function runtimeStatus (inputs) {
472
+ if (state.groundingProtectionActive) return 'groundingProtection'
473
+ if (Number.isFinite(state.speedThroughWater) && state.speedThroughWater > 0) return 'sailing'
474
+ if (!inputs.polar || inputs.polar.status === 'missingActivePolar') return 'waitingForPolar'
475
+ if (inputs.polar.status === 'waitingForWind') return 'waitingForWind'
476
+ if (inputs.polar.status === 'error') return 'polarError'
477
+ if (inputs.polar.status === 'noSpeedForConditions') return 'waitingForPolarSpeed'
478
+ return 'waitingForPolarSpeed'
479
+ }
480
+
394
481
  function setStatus () {
395
482
  if (!app.setPluginStatus) return
396
483
  if (runtime.status === 'sailing') {
@@ -458,6 +545,36 @@ function valueStatus (value) {
458
545
  return Number.isFinite(value) ? 'present' : 'missing'
459
546
  }
460
547
 
548
+ function activePolarFromValue (value) {
549
+ if (typeof value === 'string' && value.trim()) {
550
+ const hrefId = polarIdFromHref(value)
551
+ return { id: hrefId || value.trim(), href: hrefId ? value : null }
552
+ }
553
+
554
+ if (!value || typeof value !== 'object') {
555
+ return { id: null, href: null }
556
+ }
557
+
558
+ const href = typeof value.href === 'string' ? value.href : null
559
+ return {
560
+ id: typeof value.id === 'string' && value.id.trim()
561
+ ? value.id.trim()
562
+ : polarIdFromHref(href),
563
+ href
564
+ }
565
+ }
566
+
567
+ function polarIdFromHref (href) {
568
+ if (typeof href !== 'string') return null
569
+ const match = href.match(/\/resources\/polars\/([^/?#]+)/)
570
+ if (!match) return null
571
+ try {
572
+ return decodeURIComponent(match[1])
573
+ } catch (_) {
574
+ return match[1]
575
+ }
576
+ }
577
+
461
578
  function magneticHeadingFromTrue (headingTrue, magneticVariation) {
462
579
  if (!Number.isFinite(headingTrue) || !Number.isFinite(magneticVariation)) return null
463
580
  return wrap360Rad(headingTrue - magneticVariation)
@@ -4,7 +4,7 @@ function buildSchema () {
4
4
  return {
5
5
  type: 'object',
6
6
  title: 'Sailboat Simulator',
7
- description: 'Simulates a virtual sailboat from autopilot turn-rate output, polar performance, weather and optional shore-distance data already present in Signal K.',
7
+ description: 'Simulates a virtual sailboat from autopilot turn-rate output, an active polar resource, weather and optional shore-distance data already present in Signal K.',
8
8
  properties: {
9
9
  initialState: {
10
10
  type: 'object',
@@ -37,12 +37,12 @@ function buildSchema () {
37
37
  wind: {
38
38
  type: 'object',
39
39
  title: 'Wind',
40
- description: 'What it does: reads wind at the simulated boat position from the Signal K Weather API and publishes the selected wind values. The main goal is to feed polar performance calculations so another plugin can produce performance.polarSpeed, which the simulator then uses as boat speed. Requires: a Weather API provider, preferably @signalk/open-meteo-provider, and a polar performance plugin such as signalk-polar-performance-plugin. Options: publish true wind, publish apparent wind, optionally force a provider id instead of the Signal K default, and choose the polling interval.',
40
+ description: 'What it does: reads wind at the simulated boat position from the Signal K Weather API and publishes the selected wind values. The simulator also uses this wind with the active polar resource to calculate boat speed. Requires: a Weather API provider, preferably @signalk/open-meteo-provider. Options: publish true wind, publish apparent wind, optionally force a provider id instead of the Signal K default, and choose the polling interval.',
41
41
  properties: {
42
42
  trueWind: {
43
43
  type: 'boolean',
44
44
  title: 'Publish true wind',
45
- description: 'Publishes environment.wind.speedTrue, environment.wind.directionTrue and environment.wind.angleTrueWater. This is usually needed by polar performance plugins.',
45
+ description: 'Publishes environment.wind.speedTrue, environment.wind.directionTrue and environment.wind.angleTrueWater. These are also the true wind values used for the internal polar speed calculation.',
46
46
  default: true
47
47
  },
48
48
  apparentWind: {
@@ -60,7 +60,7 @@ function buildSchema () {
60
60
  pollIntervalSeconds: {
61
61
  type: 'number',
62
62
  title: 'Weather polling interval in seconds',
63
- description: 'How often the simulator refreshes wind from the Weather API while wind publishing is enabled.',
63
+ description: 'How often the simulator refreshes wind from the Weather API for its speed calculation and optional wind publication.',
64
64
  default: 60,
65
65
  minimum: 30
66
66
  }
@@ -69,7 +69,7 @@ function buildSchema () {
69
69
  publishing: {
70
70
  type: 'object',
71
71
  title: 'Navigation output',
72
- description: 'What it does: publishes the simulated boat navigation state. Requires steering.autopilot.output.turnRate to steer and performance.polarSpeed to move. If navigation.magneticVariation is available, navigation.headingMagnetic is also published. Recommended providers: signalk-autopilot-emulator-v2, signalk-polar-performance-plugin and signalk-derived-data with navigation.magneticVariation publishing enabled.',
72
+ description: 'What it does: publishes the simulated boat navigation state. Requires steering.autopilot.output.turnRate to steer, an active polar from signalk-polar-management, and weather wind to calculate boat speed. If navigation.magneticVariation is available, navigation.headingMagnetic is also published. Recommended providers: signalk-autopilot-emulator-v2, signalk-polar-management and signalk-derived-data with navigation.magneticVariation publishing enabled.',
73
73
  properties: {
74
74
  navigation: {
75
75
  type: 'boolean',
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "signalk-sailboat-simulator",
3
- "version": "0.2.0",
4
- "description": "Signal K plugin that simulates a sailing boat from autopilot turn-rate output, weather and polar performance data.",
3
+ "version": "0.3.0",
4
+ "description": "Signal K plugin that simulates a sailing boat from autopilot turn-rate output, weather and an active polar resource.",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "test": "node --test"
@@ -33,16 +33,17 @@
33
33
  "url": "https://github.com/macjl/signalk-sailboat-simulator/issues"
34
34
  },
35
35
  "engines": {
36
- "node": ">=18"
36
+ "node": ">=20"
37
+ },
38
+ "dependencies": {
39
+ "polar-math": "^1.0.0"
37
40
  },
38
- "dependencies": {},
39
- "devDependencies": {},
40
41
  "signalk-plugin-enabled-by-default": false,
41
42
  "signalk": {
42
43
  "displayName": "Sailboat Simulator",
43
44
  "recommends": [
44
45
  "signalk-autopilot-emulator-v2",
45
- "signalk-polar-performance-plugin",
46
+ "signalk-polar-management",
46
47
  "signalk-derived-data",
47
48
  "@signalk/open-meteo-provider",
48
49
  "signalk-distance-to-shore"