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.
Files changed (3) hide show
  1. package/README.md +27 -19
  2. package/package.json +1 -1
  3. 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
- ![](doc/BandG_Laylines_Target_TWA_to_Active.png)
12
-
13
- - SailSteer screen -> Long press tile to add 'Performance -> Target TWA -> decollapse, choose SignalK'
14
-
15
- ![](doc/BandG_Target_TWA_to_SignalK.png)
16
-
17
- Now the Target TWA is coming from SignalK and the laylines will be drawn based on it's value.
18
-
19
- ![](doc/BandG_Sailsteer_with_laylines.png)
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
- - Visualisation of the polar diagram
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
+ ![](https://raw.githubusercontent.com/htool/signalk-polar-performance-plugin/main/doc/BandG_Laylines_Target_TWA_to_Active.png)
64
+
65
+ - SailSteer screen -> Long press tile to add 'Performance -> Target TWA -> decollapse, choose SignalK'
66
+
67
+ ![](https://raw.githubusercontent.com/htool/signalk-polar-performance-plugin/main/doc/BandG_Target_TWA_to_SignalK.png)
68
+
69
+ Now the Target TWA is coming from SignalK and the laylines will be drawn based on it's value.
70
+
71
+ ![](https://raw.githubusercontent.com/htool/signalk-polar-performance-plugin/main/doc/BandG_Sailsteer_with_laylines.png)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "signalk-polar-performance-plugin",
3
- "version": "0.0.9",
3
+ "version": "0.0.12",
4
4
  "description": "A plugin that calculates performance information based on a (CSV) polar diagram.",
5
5
  "main": "plugin/index.js",
6
6
  "scripts": {
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 Pi = Math.PI
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
- // Calculate beat/run angle
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
- // app.debug('lowerTWAlower: %d TWA: %d lowerTWAupper: %d', lowerTWAlower.twa, TWA, lowerTWAupper.twa)
253
- // Calculate gap ratio
254
- let gap = lowerTWAupper.twa - lowerTWAlower.twa
255
- let twaGapRatio = (1 / gap) * (TWA - lowerTWAlower.twa)
256
- // app.debug('twaGapRatio: %d', twaGapRatio)
257
- // Calculate lower tws boat speed
258
- let lowerTBS = lowerTWAlower.tbs + ((lowerTWAupper.tbs - lowerTWAlower.tbs) * twaGapRatio)
259
- // Calculate upper tws boat speed
260
- let upperTBS = upperTWAlower.tbs + ((upperTWAupper.tbs - upperTWAlower.tbs) * twaGapRatio)
261
-
262
- // Calculate polar performance
263
- performance.polarSpeed = lowerTBS + ((upperTBS - lowerTBS) * twsGapRatio)
264
- // app.debug('lowerTBS: %d TBS: %d upperTBS: %d', lowerTBS, performance.polarSpeed, upperTBS)
265
-
266
- // Calculate polar performance ratio
267
- performance.polarSpeedRatio = (1 / performance.polarSpeed) * BSP
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
- // app.debug('%d not >= %d && <= %d', TWS, lower, upper)
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 = 1; index < row.length; index++) {
326
- if (row[index] != 0) {
327
- polar[index-1][angleName] = degToRad(angle)
328
- polar[index-1][VMGName] = roundDec(Number((row[index]) * Math.abs(Math.cos(angle))))
329
- if (typeof polar[index-1]['twa'] == 'undefined') {
330
- polar[index-1]['twa'] = []
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-1]['twa'] = polar[index-1]['twa'].concat(Obj)
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)
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 = 1; index < row.length; index++) {
345
- if (typeof polar[index-1].twa == 'undefined') {
346
- polar[index-1].twa = []
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-1]['twa'] = polar[index-1]['twa'].concat(Obj)
351
- if (tbs > polar[index-1]['Max speed']) {
352
- polar[index-1]['Max speed'] = tbs
353
- polar[index-1]['Max speed angle'] = angle
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');