@sailingrotevista/rotevista-dash 3.0.5 → 3.0.6

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/app.js +91 -19
  2. package/index.js +8 -2
  3. package/package.json +1 -1
package/app.js CHANGED
@@ -8,8 +8,8 @@ let CONFIG = {
8
8
  smoothWindow: 2000,
9
9
  longWindow: 30000,
10
10
  stabilityTolerance: 2000,
11
- stabilityThreshold: 0.85,
12
- minSpeed: 0.5
11
+ stabilityThreshold: 0.93,
12
+ minSpeed: 2
13
13
  },
14
14
  graphs: { reef1: 15.0, reef2: 20.0, historyMinutes: 5, samples: 60 },
15
15
  scales: {
@@ -94,10 +94,22 @@ function getCircularAverageFromBuffer(bufferArray, windowMs, signed = false) {
94
94
  if (validData.length === 0) return null;
95
95
  let sSin = 0, sCos = 0;
96
96
  validData.forEach(item => { sSin += Math.sin(item.val); sCos += Math.cos(item.val); });
97
+
97
98
  let R = Math.sqrt(sSin * sSin + sCos * sCos) / validData.length;
98
99
  let isStable = (validData.length > 2) && (validData[validData.length - 1].time - validData[0].time >= windowMs - CONFIG.averages.stabilityTolerance) && (R > CONFIG.averages.stabilityThreshold);
99
100
  let avgRad = Math.atan2(sSin, sCos);
100
- return { val: signed ? avgRad : (avgRad + 2 * Math.PI) % (2 * Math.PI), stable: isStable };
101
+
102
+ // Calcolo deviazione standard (scarto) in gradi
103
+ let deviation = 0;
104
+ if (R < 1 && R > 0) {
105
+ deviation = Math.round(Math.sqrt(-2 * Math.log(R)) * (180 / Math.PI));
106
+ }
107
+
108
+ return {
109
+ val: signed ? avgRad : (avgRad + 2 * Math.PI) % (2 * Math.PI),
110
+ stable: isStable,
111
+ dev: deviation
112
+ };
101
113
  }
102
114
 
103
115
  function playBingBing() {
@@ -311,29 +323,89 @@ function startDisplayLoop() {
311
323
  if (tick % 3 === 0) {
312
324
  // Utilizziamo CONFIG.averages.longWindow (che ora è 30000ms)
313
325
  // In questo modo, se cambi il tempo su Signal K, la dashboard si aggiorna da sola.
314
- let hObj = getCircularAverageFromBuffer(store.longBuf.hdg, CONFIG.averages.longWindow, false),
326
+ let hObj = getCircularAverageFromBuffer(store.longBuf.hdg, CONFIG.averages.longWindow * 2, false)
315
327
  cObj = getCircularAverageFromBuffer(store.longBuf.cog, CONFIG.averages.longWindow, false),
316
328
  awObj = getCircularAverageFromBuffer(store.longBuf.awa, CONFIG.averages.longWindow, true),
317
329
  twObj = getCircularAverageFromBuffer(store.longBuf.twa, CONFIG.averages.longWindow, true),
318
330
  twdObj = getCircularAverageFromBuffer(store.longBuf.twd, CONFIG.averages.longWindow, false);
319
331
 
320
- const upUI = (el, obj, isCompass = false) => {
321
- if (!obj || obj.val === null) { el.innerHTML = "---&deg;"; el.classList.remove('unstable-data'); }
322
- else {
323
- let valDeg = Math.round(radToDeg(obj.val)); el.innerHTML = (isCompass ? ((valDeg + 360) % 360).toString().padStart(3, '0') : valDeg) + "&deg;";
324
- if (obj.stable || !isNavigating) el.classList.remove('unstable-data'); else el.classList.add('unstable-data');
325
- }
326
- };
327
- upUI(ui.hdg, hObj, true); upUI(ui.cog, cObj, true); upUI(ui.awaAvg, awObj, false); upUI(ui.twaAvg, twObj, false); upUI(ui.twdAvg, twdObj, true);
332
+ const upUI = (el, obj, instantRaw, isCompass = false) => {
333
+ if (!obj || obj.val === null || instantRaw === undefined) {
334
+ el.innerHTML = "---&deg;";
335
+ el.classList.remove('unstable-data');
336
+ } else {
337
+ let valDeg = Math.round(radToDeg(obj.val));
338
+ let mainVal = (isCompass ? ((valDeg + 360) % 360).toString().padStart(3, '0') : valDeg) + "&deg;";
339
+
340
+ // Mostriamo lo scarto medio (±)
341
+ let devDisplay = (obj.dev > 1 && obj.dev < 90) ?
342
+ `<span style="font-size: 0.35em; opacity: 0.5; margin-left: 4px; vertical-align: middle;">&plusmn;${obj.dev}</span>` : "";
343
+
344
+ el.innerHTML = mainVal + devDisplay;
345
+
346
+ // --- LOGICA ALLARME ISTANTANEA (ANTI-RITARDO) ---
347
+ // Calcoliamo la differenza tra istantaneo e media (con gestione giro bussola)
348
+ let instantDeg = radToDeg(instantRaw);
349
+ let diff = Math.abs((instantDeg - radToDeg(obj.val) + 540) % 360 - 180);
350
+
351
+ // Lampeggia se:
352
+ // 1. La statistica R è bassa (obj.stable è false)
353
+ // 2. Lo scarto medio è alto (> 15°)
354
+ // 3. C'è un salto improvviso tra istantaneo e media (> 15°)
355
+ if (isNavigating && (!obj.stable || obj.dev > 15 || diff > 15)) {
356
+ el.classList.add('unstable-data');
357
+ } else {
358
+ el.classList.remove('unstable-data');
359
+ }
360
+ }
361
+ };
362
+ // Passiamo: (elemento UI, oggetto media, valore istantaneo dal sensore, è una bussola?)
363
+ upUI(ui.hdg, hObj, store.raw["navigation.headingTrue"], true);
364
+ upUI(ui.cog, cObj, store.raw["navigation.courseOverGroundTrue"], true);
365
+ upUI(ui.awaAvg, awObj, store.raw["environment.wind.angleApparent"], false);
366
+ upUI(ui.twaAvg, twObj, store.raw["environment.wind.angleTrueWater"], false);
367
+ upUI(ui.twdAvg, twdObj, store.raw["environment.wind.directionTrue"], true);
328
368
 
329
- if (hObj && twObj) {
330
- const tackHdgDeg = radToDeg((hObj.val + twObj.val * 2 + Math.PI * 2) % (Math.PI * 2));
331
- ui.tackHdg.innerHTML = `${Math.round((tackHdgDeg + 360) % 360).toString().padStart(3, '0')}&deg;`;
332
- if (cObj) {
333
- const tackCogDeg = radToDeg((cObj.val + twObj.val * 2 + Math.PI * 2) % (Math.PI * 2));
334
- ui.tackCog.innerHTML = `${Math.round((tackCogDeg + 360) % 360).toString().padStart(3, '0')}&deg;`;
369
+ // --- CALCOLO E VALIDAZIONE TACK ---
370
+ if (hObj && twObj) {
371
+ // Calcoliamo i valori teorici
372
+ const tackHdgDeg = radToDeg((hObj.val + twObj.val * 2 + Math.PI * 2) % (Math.PI * 2));
373
+ const tackCogDeg = cObj ? radToDeg((cObj.val + twObj.val * 2 + Math.PI * 2) % (Math.PI * 2)) : null;
374
+
375
+ // Condizione di instabilità tecnica (durante la manovra)
376
+ const isTackUnstable = !hObj.stable || !twObj.stable || hObj.dev > 15 || twObj.dev > 15;
377
+
378
+ // --- GESTIONE INTERFACCIA TACK HDG ---
379
+ if (!isNavigating) {
380
+ // Caso 1: Barca ferma -> Trattini fissi
381
+ ui.tackHdg.innerHTML = "---&deg;";
382
+ ui.tackHdg.classList.remove('unstable-data');
383
+ } else if (isTackUnstable) {
384
+ // Caso 2: Manovra in corso -> Trattini lampeggianti
385
+ ui.tackHdg.innerHTML = "---&deg;";
386
+ ui.tackHdg.classList.add('unstable-data');
387
+ } else {
388
+ // Caso 3: Navigazione stabile -> Mostra valore
389
+ ui.tackHdg.innerHTML = `${Math.round((tackHdgDeg + 360) % 360).toString().padStart(3, '0')}&deg;`;
390
+ ui.tackHdg.classList.remove('unstable-data');
391
+ }
392
+
393
+ // --- GESTIONE INTERFACCIA TACK COG ---
394
+ if (cObj) {
395
+ const isCogTackUnstable = !cObj.stable || !twObj.stable || cObj.dev > 15 || twObj.dev > 15;
396
+
397
+ if (!isNavigating) {
398
+ ui.tackCog.innerHTML = "---&deg;";
399
+ ui.tackCog.classList.remove('unstable-data');
400
+ } else if (isCogTackUnstable) {
401
+ ui.tackCog.innerHTML = "---&deg;";
402
+ ui.tackCog.classList.add('unstable-data');
403
+ } else {
404
+ ui.tackCog.innerHTML = `${Math.round((tackCogDeg + 360) % 360).toString().padStart(3, '0')}&deg;`;
405
+ ui.tackCog.classList.remove('unstable-data');
406
+ }
407
+ }
335
408
  }
336
- }
337
409
  const smHdg = getCircularAverageFromBuffer(store.smoothBuf.hdg, 2000, false), smTwd = getCircularAverageFromBuffer(store.smoothBuf.twd, 2000, false);
338
410
  if (smHdg && smTwd) {
339
411
  curWindCompassRot = getShortestRotation(curWindCompassRot, radToDeg(smTwd.val)); ui.twdArrow.setAttribute('transform', `rotate(${curWindCompassRot}, 20, 20)`);
package/index.js CHANGED
@@ -66,17 +66,23 @@ module.exports = function (app) {
66
66
  description: "Time buffer for 'MEAN' values. Larger windows produce smoother numbers but increase the 'Unstable' (orange) alerts during maneuvers or in gusty conditions, as data coherence decreases over time.",
67
67
  default: 30000
68
68
  },
69
- smoothWindow: {
69
+ smoothWindow: {
70
70
  type: 'number',
71
71
  title: 'Pointer Smoothing Window (ms)',
72
72
  description: "Buffer for gauge needles and pointers. Removes sensor jitter while maintaining real-time responsiveness.",
73
73
  default: 2000
74
74
  },
75
- minSpeed: {
75
+ minSpeed: {
76
76
  type: 'number',
77
77
  title: 'Min Speed for Stability (knots)',
78
78
  description: "SOG threshold below which stability alerts (blinking orange) are suppressed to avoid GPS noise while docked.",
79
79
  default: 0.5
80
+ },
81
+ stabilityThreshold: {
82
+ type: 'number',
83
+ title: 'Stability Sensitivity (R value: 0.7 - 0.98)',
84
+ description: "Determines when the number blinks orange. 0.95 = very sensitive (blinks with small movements), 0.85 = standard (balanced for sea), 0.75 = sturdy (blinks only in very rough conditions).",
85
+ default: 0.93
80
86
  }
81
87
  }
82
88
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sailingrotevista/rotevista-dash",
3
- "version": "3.0.5",
3
+ "version": "3.0.6",
4
4
  "description": "Public Wind Dashboard with navigation and course aids",
5
5
  "main": "index.js",
6
6
  "publishConfig": {