signalk-polar-performance-plugin 0.0.9 → 0.0.12
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 +27 -19
- package/package.json +1 -1
- package/plugin/index.js +107 -47
package/README.md
CHANGED
|
@@ -1,23 +1,6 @@
|
|
|
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
|
-
|
|
11
|
-

|
|
12
|
-
|
|
13
|
-
- SailSteer screen -> Long press tile to add 'Performance -> Target TWA -> decollapse, choose SignalK'
|
|
14
|
-
|
|
15
|
-

|
|
16
|
-
|
|
17
|
-
Now the Target TWA is coming from SignalK and the laylines will be drawn based on it's value.
|
|
18
|
-
|
|
19
|
-

|
|
20
|
-
|
|
21
4
|
## Data correctness
|
|
22
5
|
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).
|
|
23
6
|
|
|
@@ -50,11 +33,13 @@ In the plugin configuration you can toggle the following options:
|
|
|
50
33
|
- Polar Speed Ratio (performance.polarSpeedRatio)
|
|
51
34
|
- Plugin option to use SOG as boat speed
|
|
52
35
|
- Wind angle for maximum speed at this wind speed
|
|
36
|
+
- Fill up the ends of the polar diagram
|
|
53
37
|
|
|
54
38
|
### To-do list
|
|
55
|
-
- Fill up the ends of the polar diagram
|
|
56
39
|
- Improved interpolation
|
|
57
40
|
- Make moment to do calculation smarter/configurable
|
|
41
|
+
- API to see JSON of polar
|
|
42
|
+
- Visualisation of the polar diagram
|
|
58
43
|
- Create polar from live data
|
|
59
44
|
-- Save polar info to file
|
|
60
45
|
-- Save new record speed for angle in polar
|
|
@@ -63,4 +48,27 @@ In the plugin configuration you can toggle the following options:
|
|
|
63
48
|
-- Configure the resolution of the polar diagram
|
|
64
49
|
- Support multiple polar diagrams
|
|
65
50
|
- Capture heel in polar diagram
|
|
66
|
-
|
|
51
|
+
|
|
52
|
+
## MFD configuration
|
|
53
|
+
|
|
54
|
+
### B&G
|
|
55
|
+
To get the values calculated by this plugin from SignalK to your B&G MFD/Triton2, you need to install the [B&G performance plugin](https://www.npmjs.com/package/signalk-bandg-performance-plugin) and select at least the following values:
|
|
56
|
+
- Polar Speed (Polar Speed - POL SPD)
|
|
57
|
+
- Polar Speed Ratio (Polar Performance - POL PERF))
|
|
58
|
+
- Target TWA (TARG TWA)
|
|
59
|
+
|
|
60
|
+
To see lay lines you need to set:
|
|
61
|
+
- Settings -> Chart -> Laylines -> Targets... -> True wind angle to 'Actual'
|
|
62
|
+
|
|
63
|
+

|
|
64
|
+
|
|
65
|
+
- SailSteer screen -> Long press tile to add 'Performance -> Target TWA -> decollapse, choose SignalK'
|
|
66
|
+
|
|
67
|
+

|
|
68
|
+
|
|
69
|
+
Now the Target TWA is coming from SignalK and the laylines will be drawn based on it's value.
|
|
70
|
+
|
|
71
|
+

|
|
72
|
+
|
|
73
|
+
### Raymarine
|
|
74
|
+
If you have a Raymarine MFD and can tell more about this, please add to the README or tell me.
|
package/package.json
CHANGED
package/plugin/index.js
CHANGED
|
@@ -61,8 +61,7 @@ module.exports = function (app) {
|
|
|
61
61
|
|
|
62
62
|
// Global variables
|
|
63
63
|
var BSP, STW, TWA, TWS, port // Angles in rad, speed in m/s
|
|
64
|
-
var
|
|
65
|
-
var halfPi = Pi / 2
|
|
64
|
+
var halfPi = Math.PI / 2
|
|
66
65
|
|
|
67
66
|
// Subscribe to paths
|
|
68
67
|
let localSubscription = {
|
|
@@ -195,7 +194,7 @@ module.exports = function (app) {
|
|
|
195
194
|
let gap = upper - lower
|
|
196
195
|
let twsGapRatio = (1 / gap) * (TWS - lower)
|
|
197
196
|
//app.debug('twsGapRatio: %d', twsGapRatio)
|
|
198
|
-
|
|
197
|
+
// Calculate beat/run angle
|
|
199
198
|
if (TWA < halfPi) {
|
|
200
199
|
// app.debug('Upwind')
|
|
201
200
|
if (options.beatAngle == true) {
|
|
@@ -237,37 +236,52 @@ module.exports = function (app) {
|
|
|
237
236
|
}
|
|
238
237
|
// Calculate polar target boat speed
|
|
239
238
|
|
|
240
|
-
// Define the 4 near data points
|
|
239
|
+
// Interpolate Define the 4 near data points
|
|
241
240
|
let lowerTWA = polar[indexTWS].twa
|
|
242
241
|
let upperTWA = polar[indexTWS+1].twa
|
|
243
242
|
// app.debug('TWAlower: %s', JSON.stringify(TWAlower))
|
|
243
|
+
// First find lowerTWA
|
|
244
244
|
for (let indexTWA = 0 ; indexTWA < lowerTWA.length-1; indexTWA++) {
|
|
245
245
|
let lowerTWAlower = lowerTWA[indexTWA]
|
|
246
246
|
let lowerTWAupper = lowerTWA[indexTWA+1]
|
|
247
|
-
let upperTWAlower = upperTWA[indexTWA]
|
|
248
|
-
let upperTWAupper = upperTWA[indexTWA+1]
|
|
249
|
-
|
|
250
247
|
// app.debug('lowerTWAlower: %s lowerTWAupper: %s', lowerTWAlower, lowerTWAupper)
|
|
251
248
|
if (TWA >= lowerTWAlower.twa && TWA <= lowerTWAupper.twa) {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
249
|
+
// Now find upperTWA
|
|
250
|
+
for (let indexTWA = 0 ; indexTWA < upperTWA.length-1; indexTWA++) {
|
|
251
|
+
let upperTWAlower = upperTWA[indexTWA]
|
|
252
|
+
let upperTWAupper = upperTWA[indexTWA+1]
|
|
253
|
+
if (TWA >= upperTWAlower.twa && TWA <= upperTWAupper.twa) {
|
|
254
|
+
// Found the 4 points
|
|
255
|
+
// app.debug('lowerTWAlower: %d TWA: %d lowerTWAupper: %d', lowerTWAlower.twa, TWA, lowerTWAupper.twa)
|
|
256
|
+
// Calculate gap ratio
|
|
257
|
+
let gap = lowerTWAupper.twa - lowerTWAlower.twa
|
|
258
|
+
let twaGapRatio = (1 / gap) * (TWA - lowerTWAlower.twa)
|
|
259
|
+
// app.debug('twaGapRatio: %d', twaGapRatio)
|
|
260
|
+
// Calculate lower tws boat speed
|
|
261
|
+
let lowerTBS = lowerTWAlower.tbs + ((lowerTWAupper.tbs - lowerTWAlower.tbs) * twaGapRatio)
|
|
262
|
+
// Calculate upper tws boat speed
|
|
263
|
+
let upperTBS = upperTWAlower.tbs + ((upperTWAupper.tbs - upperTWAlower.tbs) * twaGapRatio)
|
|
264
|
+
// Calculate polar boat speed
|
|
265
|
+
performance.polarSpeed = lowerTBS + ((upperTBS - lowerTBS) * twsGapRatio)
|
|
266
|
+
// app.debug('lowerTBS: %d TBS: %d upperTBS: %d', lowerTBS, performance.polarSpeed, upperTBS)
|
|
267
|
+
break
|
|
268
|
+
}
|
|
269
|
+
}
|
|
268
270
|
break
|
|
269
271
|
}
|
|
270
272
|
}
|
|
273
|
+
if (typeof performance.polarSpeed == 'undefined') {
|
|
274
|
+
if (TWA < performance.beatAngle) {
|
|
275
|
+
// In case TWA < lowest polar angle
|
|
276
|
+
app.debug('Low angle %d not present in table', radToDeg(TWA))
|
|
277
|
+
// performance.polarSpeed =
|
|
278
|
+
} else if (TWA > performance.runAngle) {
|
|
279
|
+
// In case TWA > highest polar angle
|
|
280
|
+
app.debug('High angle %d not present in table', radToDeg(TWA))
|
|
281
|
+
// performance.polarSpeed =
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
271
285
|
if (options.maxSpeed == true) {
|
|
272
286
|
let lowerMax = polar[indexTWS]['Max speed']
|
|
273
287
|
let upperMax = polar[indexTWS+1]['Max speed']
|
|
@@ -278,10 +292,20 @@ module.exports = function (app) {
|
|
|
278
292
|
let maxSpeedAngle = lowerMaxAngle + ((upperMaxAngle - lowerMaxAngle) * twsGapRatio)
|
|
279
293
|
performance.maxSpeedAngle = maxSpeedAngle
|
|
280
294
|
}
|
|
281
|
-
} else {
|
|
282
|
-
|
|
295
|
+
} else if (indexTWS == 0 && TWS < lower) {
|
|
296
|
+
app.debug('No data for low wind speed (%d kts)', msToKts(TWS))
|
|
297
|
+
} else if (indexTWS == polar.length-1 && TWS > upper) {
|
|
298
|
+
app.debug('No data for high wind speed (%d kts)', msToKts(TWS))
|
|
283
299
|
}
|
|
284
300
|
}
|
|
301
|
+
// Calculate polar performance ratio
|
|
302
|
+
if (typeof performance.polarSpeed != 'undefined') {
|
|
303
|
+
performance.polarSpeedRatio = (1 / performance.polarSpeed) * BSP
|
|
304
|
+
} else {
|
|
305
|
+
// No value would create stale values
|
|
306
|
+
performance.polarSpeed = 0
|
|
307
|
+
performance.polarSpeedRatio = 0
|
|
308
|
+
}
|
|
285
309
|
return performance
|
|
286
310
|
}
|
|
287
311
|
|
|
@@ -322,35 +346,35 @@ module.exports = function (app) {
|
|
|
322
346
|
angleName = 'Run angle'
|
|
323
347
|
VMGName = 'Run VMG'
|
|
324
348
|
}
|
|
325
|
-
for (let index =
|
|
326
|
-
if (row[index] != 0) {
|
|
327
|
-
polar[index
|
|
328
|
-
polar[index
|
|
329
|
-
if (typeof polar[index
|
|
330
|
-
polar[index
|
|
349
|
+
for (let index = 0; index < row.length-1; index++) {
|
|
350
|
+
if (row[index+1] != 0) {
|
|
351
|
+
polar[index][angleName] = degToRad(angle)
|
|
352
|
+
polar[index][VMGName] = roundDec(Number((row[index+1]) * Math.abs(Math.cos(angle))))
|
|
353
|
+
if (typeof polar[index]['twa'] == 'undefined') {
|
|
354
|
+
polar[index]['twa'] = []
|
|
331
355
|
}
|
|
332
|
-
let tbs = ktsToMs(Number(row[index]))
|
|
356
|
+
let tbs = ktsToMs(Number(row[index+1]))
|
|
333
357
|
let Obj = {"twa": degToRad(angle), "tbs": tbs}
|
|
334
|
-
polar[index
|
|
335
|
-
if (typeof polar[index
|
|
336
|
-
polar[index
|
|
337
|
-
polar[index
|
|
358
|
+
polar[index]['twa'] = polar[index]['twa'].concat(Obj)
|
|
359
|
+
if (typeof polar[index]['Max speed'] == 'undefined' || tbs > polar[index]['Max speed']) {
|
|
360
|
+
polar[index]['Max speed'] = tbs
|
|
361
|
+
polar[index]['Max speed angle'] = degToRad(angle)
|
|
338
362
|
}
|
|
339
363
|
}
|
|
340
364
|
}
|
|
341
365
|
} else {
|
|
342
366
|
// Normal line
|
|
343
367
|
let angle = degToRad(Number(row[0])) // CSV deg to internal rad
|
|
344
|
-
for (let index =
|
|
345
|
-
if (typeof polar[index
|
|
346
|
-
polar[index
|
|
368
|
+
for (let index = 0; index < row.length-1; index++) {
|
|
369
|
+
if (typeof polar[index].twa == 'undefined') {
|
|
370
|
+
polar[index].twa = []
|
|
347
371
|
}
|
|
348
|
-
let tbs = ktsToMs(Number(row[index]))
|
|
372
|
+
let tbs = ktsToMs(Number(row[index+1]))
|
|
349
373
|
let Obj = {"twa": angle, "tbs": tbs}
|
|
350
|
-
polar[index
|
|
351
|
-
if (tbs > polar[index
|
|
352
|
-
polar[index
|
|
353
|
-
polar[index
|
|
374
|
+
polar[index]['twa'] = polar[index]['twa'].concat(Obj)
|
|
375
|
+
if (tbs > polar[index]['Max speed']) {
|
|
376
|
+
polar[index]['Max speed'] = tbs
|
|
377
|
+
polar[index]['Max speed angle'] = angle
|
|
354
378
|
}
|
|
355
379
|
}
|
|
356
380
|
}
|
|
@@ -364,14 +388,50 @@ module.exports = function (app) {
|
|
|
364
388
|
tws.twa = twaArray
|
|
365
389
|
})
|
|
366
390
|
|
|
391
|
+
// And now fill in some missing ends to avoid doing expensive calculations in the main loop
|
|
392
|
+
for (let index = 0; index < polar.length-1; index++) {
|
|
393
|
+
// Sorted on angle, so first is lowest
|
|
394
|
+
let twaArray = polar[index].twa
|
|
395
|
+
let lowTBS = twaArray[0].tbs
|
|
396
|
+
let lowTWA = twaArray[0].twa
|
|
397
|
+
|
|
398
|
+
// app.debug('Padding polar from 0 to first given angle (%d deg, %d kts) to twaArray %s' , radToDeg(lowTWA), msToKts(lowTBS), JSON.stringify(twaArray))
|
|
399
|
+
|
|
400
|
+
// Now put some extra values at the beginning
|
|
401
|
+
var topArray = []
|
|
402
|
+
for (let angle = 0; angle < lowTWA; angle = angle + degToRad(5)) {
|
|
403
|
+
let tbs = (angle / lowTWA) * Math.pow(Math.cos((-1*lowTWA + angle)*3),3) * lowTBS
|
|
404
|
+
let Obj = {'twa': angle, 'tbs': tbs}
|
|
405
|
+
// app.debug('Adding Obj: %s', JSON.stringify(Obj))
|
|
406
|
+
topArray.push(Obj)
|
|
407
|
+
// app.debug('twaArray: %s', JSON.stringify(twaArray))
|
|
408
|
+
}
|
|
409
|
+
twaArray = topArray.concat(twaArray)
|
|
410
|
+
// app.debug('twaArray: %s', JSON.stringify(polar[index].twa))
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
for (let index = 0; index < polar.length-1; index++) {
|
|
414
|
+
let twaArray = polar[index].twa
|
|
415
|
+
let highTBS = twaArray[twaArray.length-1].tbs
|
|
416
|
+
let highTWA = twaArray[twaArray.length-1].twa
|
|
417
|
+
|
|
418
|
+
// app.debug('Padding polar from highTWA to last given angle (%d, %d kts) to twaArray %s' , highTWA, msToKts(highTBS), JSON.stringify(twaArray))
|
|
419
|
+
|
|
420
|
+
// Now put some extra values at the beginning
|
|
421
|
+
var tailArray = []
|
|
422
|
+
for (let angle = Math.PI; angle > highTWA; angle = angle - degToRad(5)) {
|
|
423
|
+
let tbs = Math.pow(highTWA/angle, 2) * highTBS
|
|
424
|
+
let Obj = {'twa': angle, 'tbs': tbs}
|
|
425
|
+
tailArray.unshift(Obj)
|
|
426
|
+
}
|
|
427
|
+
twaArray = twaArray.concat(tailArray)
|
|
428
|
+
}
|
|
429
|
+
|
|
367
430
|
// app.debug(JSON.stringify(polar))
|
|
368
431
|
return (polar)
|
|
369
432
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
433
|
}
|
|
373
434
|
|
|
374
|
-
|
|
375
435
|
plugin.stop = function () {
|
|
376
436
|
// Here we put logic we need when the plugin stops
|
|
377
437
|
app.debug('Plugin stopped');
|