signalk-polar-performance-plugin 0.0.5 → 0.0.8
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 +11 -2
- package/package.json +1 -1
- package/plugin/index.js +52 -28
package/README.md
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
# Polar performance plugin
|
|
2
2
|
Generate performance information based on a polar diagram.
|
|
3
3
|
|
|
4
|
+
|
|
5
|
+
## MFD configuration
|
|
6
|
+
|
|
7
|
+
### B&G
|
|
8
|
+
To make use of the performance data and get e.g. lay lines you need to set:
|
|
9
|
+
- Settings -> Chart -> Laylines -> Targets... -> True wind angle to 'Actual'
|
|
10
|
+
- SailSteer screen -> Long press tile to add 'Performance -> Target TWA -> decollapse, choose SignalK'
|
|
11
|
+
Now the Target TWA is coming from SignalK and the laylines will be drawn based on it's value.
|
|
12
|
+
|
|
4
13
|
## Data correctness
|
|
5
14
|
It's assumed data is already corrected when it's read by this plugin. This can be sometimes be done by the sensor, sometimes upon entry into SignalK using the [calibration plugin](https://www.npmjs.com/package/@signalk/calibration).
|
|
6
15
|
|
|
@@ -19,7 +28,7 @@ The polar diagram can be configured through CSV notation as used on [ORC sailboa
|
|
|
19
28
|
In the plugin configuration you can toggle the following options:
|
|
20
29
|
- Enable calculation of beat/upwind and run/gybe/downwind angle
|
|
21
30
|
- Enable calculation of beat/upwind and run/gybe/downwind VMG
|
|
22
|
-
- Enable calculation of
|
|
31
|
+
- Enable calculation of Optimum Wind Angle (difference between TWA and beat/run angle (depends on beat/run angle)
|
|
23
32
|
|
|
24
33
|
## Calculated performance data
|
|
25
34
|
### Currently supported
|
|
@@ -32,10 +41,10 @@ In the plugin configuration you can toggle the following options:
|
|
|
32
41
|
- Polar Boat Speed (performance.polarSpeed)
|
|
33
42
|
- Polar Speed Ratio (performance.polarSpeedRatio)
|
|
34
43
|
- Plugin option to use SOG as boat speed
|
|
44
|
+
- Wind angle for maximum speed at this wind speed
|
|
35
45
|
|
|
36
46
|
### To-do list
|
|
37
47
|
- Fill up the ends of the polar diagram
|
|
38
|
-
- Wind angle for maximum speed at this wind speed
|
|
39
48
|
- Improved interpolation
|
|
40
49
|
- Make moment to do calculation smarter/configurable
|
|
41
50
|
- Create polar from live data
|
package/package.json
CHANGED
package/plugin/index.js
CHANGED
|
@@ -24,14 +24,18 @@ module.exports = function (app) {
|
|
|
24
24
|
type: 'boolean',
|
|
25
25
|
title: 'Enable calculation of beat/upwind and run/gybe/downwind VMG'
|
|
26
26
|
},
|
|
27
|
-
|
|
27
|
+
optimumWindAngle: {
|
|
28
28
|
type: 'boolean',
|
|
29
|
-
title: 'Enable calculation of
|
|
29
|
+
title: 'Enable calculation of Optimum Wind Angle (difference between TWA and beat/run angle (depends on beat/run angle)'
|
|
30
30
|
},
|
|
31
31
|
useSOG: {
|
|
32
32
|
type: 'boolean',
|
|
33
33
|
title: 'Use speed over ground (SOG) as boat speed.'
|
|
34
34
|
},
|
|
35
|
+
maxSpeed: {
|
|
36
|
+
type: 'boolean',
|
|
37
|
+
title: 'Enable writing of maximum speed angle and boat speed for a given TWS'
|
|
38
|
+
},
|
|
35
39
|
csvTable: {
|
|
36
40
|
type: "string",
|
|
37
41
|
title: "Enter csv with polar in http://jieter.github.io/orc-data/site/ style."
|
|
@@ -116,7 +120,7 @@ module.exports = function (app) {
|
|
|
116
120
|
} else if (delta.path == 'environment.wind.speedTrue') {
|
|
117
121
|
TWS = delta.value
|
|
118
122
|
// app.debug('environment.wind.speedTrue (TWS): %d', TWS)
|
|
119
|
-
app.debug('TWS: %d TWA: %d BSP: %d', TWS, TWA, BSP)
|
|
123
|
+
app.debug('TWS: %d TWA: %d BSP: %d', msToKts(TWS), radToDeg(TWA)*port, msToKts(BSP))
|
|
120
124
|
sendUpdates(getPerformanceData(TWS, TWA, BSP))
|
|
121
125
|
} else if (delta.path == 'environment.wind.angleTrueWater') {
|
|
122
126
|
if (delta.value < 0) {
|
|
@@ -125,7 +129,6 @@ module.exports = function (app) {
|
|
|
125
129
|
port = 1
|
|
126
130
|
}
|
|
127
131
|
TWA = Math.abs(delta.value)
|
|
128
|
-
// app.debug('environment.wind.angleTrueWater (TWA): %d', TWA)
|
|
129
132
|
}
|
|
130
133
|
})
|
|
131
134
|
}
|
|
@@ -139,6 +142,8 @@ module.exports = function (app) {
|
|
|
139
142
|
}
|
|
140
143
|
if (typeof perfObj.beatVMG != 'undefined') {
|
|
141
144
|
values.push({path: 'performance.beatAngleVelocityMadeGood', value: roundDec(perfObj.beatVMG)})
|
|
145
|
+
values.push({path: 'performance.targetVelocityMadeGood', value: roundDec(perfObj.beatVMG)})
|
|
146
|
+
metas.push({path: 'performance.targetVelocityMadeGood', value: {"units": "rad"}})
|
|
142
147
|
}
|
|
143
148
|
if (typeof perfObj.runAngle != 'undefined') {
|
|
144
149
|
values.push({path: 'performance.gybeAngle', value: roundDec(perfObj.runAngle)})
|
|
@@ -146,10 +151,12 @@ module.exports = function (app) {
|
|
|
146
151
|
}
|
|
147
152
|
if (typeof perfObj.runVMG != 'undefined') {
|
|
148
153
|
values.push({path: 'performance.gybeAngleVelocityMadeGood', value: roundDec(perfObj.runVMG)})
|
|
154
|
+
values.push({path: 'performance.targetVelocityMadeGood', value: roundDec(perfObj.runVMG)})
|
|
155
|
+
metas.push({path: 'performance.targetVelocityMadeGood', value: {"units": "rad"}})
|
|
149
156
|
}
|
|
150
|
-
if (typeof perfObj.
|
|
151
|
-
values.push({path: 'performance.
|
|
152
|
-
metas.push({path: 'performance.
|
|
157
|
+
if (typeof perfObj.optimumWindAngle != 'undefined') {
|
|
158
|
+
values.push({path: 'performance.optimumWindAngle', value: roundDec(perfObj.optimumWindAngle)})
|
|
159
|
+
metas.push({path: 'performance.optimumWindAngle', value: {"units": "rad"}})
|
|
153
160
|
}
|
|
154
161
|
if (typeof perfObj.targetSpeed != 'undefined') {
|
|
155
162
|
values.push({path: 'performance.targetSpeed', value: roundDec(perfObj.targetSpeed)})
|
|
@@ -158,6 +165,12 @@ module.exports = function (app) {
|
|
|
158
165
|
values.push({path: 'performance.polarSpeed', value: roundDec(perfObj.polarSpeed)})
|
|
159
166
|
values.push({path: 'performance.polarSpeedRatio', value: roundDec(perfObj.polarSpeedRatio)})
|
|
160
167
|
}
|
|
168
|
+
if (typeof perfObj.maxSpeed != 'undefined') {
|
|
169
|
+
values.push({path: 'performance.maxSpeed', value: roundDec(perfObj.maxSpeed)})
|
|
170
|
+
metas.push({path: 'performance.maxSpeed', value: {"units": "m/s"}})
|
|
171
|
+
values.push({path: 'performance.maxSpeedAngle', value: roundDec(perfObj.maxSpeedAngle)})
|
|
172
|
+
metas.push({path: 'performance.maxSpeedAngle', value: {"units": "rad"}})
|
|
173
|
+
}
|
|
161
174
|
|
|
162
175
|
app.debug('sendUpdates: %s', JSON.stringify(values))
|
|
163
176
|
app.handleMessage(plugin.id, {
|
|
@@ -191,9 +204,9 @@ module.exports = function (app) {
|
|
|
191
204
|
let beatUpper = polar[indexTWS+1]['Beat angle']
|
|
192
205
|
performance.beatAngle = beatLower + ((beatUpper - beatLower) * twsGapRatio)
|
|
193
206
|
}
|
|
194
|
-
// Calculate
|
|
195
|
-
if (options.
|
|
196
|
-
|
|
207
|
+
// Calculate optimum wind angle (B&G thing)
|
|
208
|
+
if (options.optimumWindAngle == true) {
|
|
209
|
+
performance.optimumWindAngle = (TWA - performance.beatAngle) * port
|
|
197
210
|
}
|
|
198
211
|
// Calculate beat VMG
|
|
199
212
|
if (options.beatVMG == true) {
|
|
@@ -210,9 +223,9 @@ module.exports = function (app) {
|
|
|
210
223
|
//app.debug('runLower: %s runUpper: %s', runLower, runUpper)
|
|
211
224
|
performance.runAngle = runLower + ((runUpper - runLower) * twsGapRatio)
|
|
212
225
|
}
|
|
213
|
-
if (options.
|
|
214
|
-
// Calculate
|
|
215
|
-
performance.
|
|
226
|
+
if (options.optimumWindAngle == true) {
|
|
227
|
+
// Calculate optimum wind angle
|
|
228
|
+
performance.optimumWindAngle = (performance.runAngle - TWA) * port * -1
|
|
216
229
|
}
|
|
217
230
|
if (options.beatVMG == true) {
|
|
218
231
|
// Calculate run VMG
|
|
@@ -246,17 +259,25 @@ module.exports = function (app) {
|
|
|
246
259
|
// Calculate upper tws boat speed
|
|
247
260
|
let upperTBS = upperTWAlower.tbs + ((upperTWAupper.tbs - upperTWAlower.tbs) * twaGapRatio)
|
|
248
261
|
|
|
249
|
-
// Calculate
|
|
262
|
+
// Calculate polar performance
|
|
250
263
|
performance.polarSpeed = lowerTBS + ((upperTBS - lowerTBS) * twsGapRatio)
|
|
251
264
|
// app.debug('lowerTBS: %d TBS: %d upperTBS: %d', lowerTBS, performance.polarSpeed, upperTBS)
|
|
252
265
|
|
|
253
|
-
// Calculate polar performance
|
|
266
|
+
// Calculate polar performance ratio
|
|
254
267
|
performance.polarSpeedRatio = (1 / performance.polarSpeed) * BSP
|
|
255
|
-
|
|
268
|
+
break
|
|
256
269
|
}
|
|
257
270
|
}
|
|
258
|
-
|
|
259
|
-
|
|
271
|
+
if (options.maxSpeed == true) {
|
|
272
|
+
let lowerMax = polar[indexTWS]['Max speed']
|
|
273
|
+
let upperMax = polar[indexTWS+1]['Max speed']
|
|
274
|
+
let maxSpeed = lowerMax + ((upperMax - lowerMax) * twsGapRatio)
|
|
275
|
+
performance.maxSpeed = maxSpeed
|
|
276
|
+
let lowerMaxAngle = polar[indexTWS]['Max speed angle']
|
|
277
|
+
let upperMaxAngle = polar[indexTWS+1]['Max speed angle']
|
|
278
|
+
let maxSpeedAngle = lowerMaxAngle + ((upperMaxAngle - lowerMaxAngle) * twsGapRatio)
|
|
279
|
+
performance.maxSpeedAngle = maxSpeedAngle
|
|
280
|
+
}
|
|
260
281
|
} else {
|
|
261
282
|
// app.debug('%d not >= %d && <= %d', TWS, lower, upper)
|
|
262
283
|
}
|
|
@@ -305,17 +326,16 @@ module.exports = function (app) {
|
|
|
305
326
|
if (row[index] != 0) {
|
|
306
327
|
polar[index-1][angleName] = degToRad(angle)
|
|
307
328
|
polar[index-1][VMGName] = roundDec(Number((row[index]) * Math.abs(Math.cos(angle))))
|
|
308
|
-
app.debug('polar[index-1]: %s', polar[index-1])
|
|
309
329
|
if (typeof polar[index-1]['twa'] == 'undefined') {
|
|
310
330
|
polar[index-1]['twa'] = []
|
|
311
331
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
let Obj = {"twa": degToRad(angle), "tbs": ktsToMs(Number(row[index]))}
|
|
315
|
-
app.debug('Obj: %s', JSON.stringify(Obj))
|
|
332
|
+
let tbs = ktsToMs(Number(row[index]))
|
|
333
|
+
let Obj = {"twa": degToRad(angle), "tbs": tbs}
|
|
316
334
|
polar[index-1]['twa'] = polar[index-1]['twa'].concat(Obj)
|
|
317
|
-
|
|
318
|
-
|
|
335
|
+
if (typeof polar[index-1]['Max speed'] == 'undefined' || tbs > polar[index-1]['Max speed']) {
|
|
336
|
+
polar[index-1]['Max speed'] = tbs
|
|
337
|
+
polar[index-1]['Max speed angle'] = degToRad(angle)
|
|
338
|
+
}
|
|
319
339
|
}
|
|
320
340
|
}
|
|
321
341
|
} else {
|
|
@@ -325,9 +345,13 @@ module.exports = function (app) {
|
|
|
325
345
|
if (typeof polar[index-1].twa == 'undefined') {
|
|
326
346
|
polar[index-1].twa = []
|
|
327
347
|
}
|
|
328
|
-
let
|
|
348
|
+
let tbs = ktsToMs(Number(row[index]))
|
|
349
|
+
let Obj = {"twa": angle, "tbs": tbs}
|
|
329
350
|
polar[index-1]['twa'] = polar[index-1]['twa'].concat(Obj)
|
|
330
|
-
|
|
351
|
+
if (tbs > polar[index-1]['Max speed']) {
|
|
352
|
+
polar[index-1]['Max speed'] = tbs
|
|
353
|
+
polar[index-1]['Max speed angle'] = angle
|
|
354
|
+
}
|
|
331
355
|
}
|
|
332
356
|
}
|
|
333
357
|
})
|
|
@@ -377,7 +401,7 @@ function ktsToMs(knots) {
|
|
|
377
401
|
return knots / 1.94384
|
|
378
402
|
}
|
|
379
403
|
|
|
380
|
-
function
|
|
404
|
+
function msToKts(ms) {
|
|
381
405
|
return ms * 1.94384
|
|
382
406
|
}
|
|
383
407
|
|