@sailingrotevista/rotevista-dash 7.0.30 → 7.0.32

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 (4) hide show
  1. package/app.js +24 -15
  2. package/index.js +7 -2
  3. package/package.json +1 -1
  4. package/utils.js +7 -6
package/app.js CHANGED
@@ -323,6 +323,11 @@ function getSourcePriorityScore(sourceName) {
323
323
  }
324
324
 
325
325
  function processIncomingData(path, val, source, timeMs) {
326
+ // 0. Filtro di validità assoluta: ignora all'istante pacchetti vuoti, null o non numerici
327
+ if (val === null || val === undefined || (typeof val === 'number' && isNaN(val))) {
328
+ return;
329
+ }
330
+
326
331
  // 1. Filtro anti-spike vento (> 100 nodi / 51.44 m/s)
327
332
  if ((path === "environment.wind.speedApparent" || path === "environment.wind.speedTrue") && val > 51.44) {
328
333
  return;
@@ -348,19 +353,22 @@ function processIncomingData(path, val, source, timeMs) {
348
353
  } else {
349
354
  const currentLock = sourceLocks[path];
350
355
  const isSameSource = (currentLock.label === source);
351
- const isLockExpired = (now - currentLock.lastSeen > 12000); // Scadenza a 12 secondi per tollerare il GPS dello Yacht Devices
356
+ // Tolleranza 5s: ignora il GPS Victron durante le micro-pause di Yacht Device per evitare imprecisioni
357
+ const isLockExpired = (now - currentLock.lastSeen > 5000);
352
358
  const hasHigherPriority = (score > currentLock.score);
359
+ // Switch immediato tra apparati principali di pari priorità (es. YD e AP) se inattivi da > 1.5s
360
+ const isSamePriorityStale = (score === currentLock.score && (now - currentLock.lastSeen > 1500));
353
361
 
354
362
  if (isSameSource) {
355
363
  // Stessa sorgente: aggiorna il timestamp e mantiene il blocco
356
364
  currentLock.lastSeen = now;
357
365
  currentLock.score = score;
358
- } else if (isLockExpired || hasHigherPriority) {
359
- // Ruba il blocco se la sorgente precedente è scaduta o se questa ha priorità superiore
366
+ } else if (hasHigherPriority || isLockExpired || isSamePriorityStale) {
367
+ // Passa a sorgenti secondarie (Victron) solo se Yacht Device è spento da > 5s
360
368
  sourceLocks[path] = { label: source, score: score, lastSeen: now };
361
369
  console.log(`🔌 [Smart Lock] Path "${path}" switched to: ${source} (Score: ${score})`);
362
370
  } else {
363
- // Rifiuta i dati da sorgenti a priorità inferiore se quella principale è attiva
371
+ // Scarta sorgenti imprecise di servizio mentre Yacht Device è la sorgente principale attiva
364
372
  return;
365
373
  }
366
374
  }
@@ -410,19 +418,21 @@ function processIncomingData(path, val, source, timeMs) {
410
418
 
411
419
  // --- GESTIONE PRUA VERA / MAGNETICA CON AUTODIVIAZIONE ---
412
420
  if (path === "navigation.headingTrue") {
421
+ store.timestamps["navigation.headingTrueNative"] = now; // Marca la presenza di un sensore nativo di prua vera
422
+ store.timestamps["navigation.headingTrue"] = now;
413
423
  safePush(store.smoothBuf.hdg, val, now);
414
424
  safePush(store.longBuf.hdg, val, now);
415
425
  }
416
426
  else if (path === "navigation.headingMagnetic") {
417
- // Se non abbiamo ricevuto una prua vera negli ultimi 5 secondi, convertiamo quella magnetica!
418
- const hasTrueHdg = store.timestamps["navigation.headingTrue"] && (now - store.timestamps["navigation.headingTrue"] < 5000);
419
- if (!hasTrueHdg) {
427
+ // Se non c'è una prua vera NATIVA negli ultimi 5 secondi, converte e registra ad ogni secondo la prua magnetica
428
+ const hasNativeTrueHdg = store.timestamps["navigation.headingTrueNative"] && (now - store.timestamps["navigation.headingTrueNative"] < 5000);
429
+ if (!hasNativeTrueHdg) {
420
430
  const variation = store.raw["navigation.magneticVariation"] || 0; // Legge la declinazione magnetica del GPS
421
431
  const calculatedTrueHdg = (val + variation + 2 * Math.PI) % (2 * Math.PI);
422
432
 
423
- // Registriamo il valore calcolato come Prua Vera temporanea e aggiorniamo il timestamp per evitare i timeout del watchdog
433
+ // Registra il valore calcolato mantenendo attivo il watchdog senza bloccare i successivi pacchetti magnetici
424
434
  store.raw["navigation.headingTrue"] = calculatedTrueHdg;
425
- store.timestamps["navigation.headingTrue"] = now; // (AGGIUNTO - RISOLVE IL LOCKOUT)
435
+ store.timestamps["navigation.headingTrue"] = now;
426
436
  safePush(store.smoothBuf.hdg, calculatedTrueHdg, now);
427
437
  safePush(store.longBuf.hdg, calculatedTrueHdg, now);
428
438
  }
@@ -795,16 +805,15 @@ function startDisplayLoop() {
795
805
  }
796
806
 
797
807
  // --- SLOW TIER (Salvataggio stato ogni 10 secondi) ---
798
- if (lastAvgUIUpdate++ % 10 === 0) {
799
- saveDashboardState();
800
- }
808
+ if (lastAvgUIUpdate++ % 10 === 0) {
809
+ saveDashboardState();
810
+ }
801
811
 
802
- if (lastAvgUIUpdate % 3 === 0) {
812
+ // AGGIORNAMENTO FLUIDO MEDIE UI (Esecuzione a 1Hz costante ad ogni secondo)
803
813
  const baseThreshold = CONFIG.averaging.stabilityThreshold || 0.95;
804
814
  const breakout = CONFIG.averaging.stabilityBreakout || 15;
805
815
 
806
- // CALCOLO DIFFERENZIALE: Tolleranza del vento proporzionale alla sensibilità di base.
807
- // Rilassiamo la soglia del vento di 0.13 rispetto alla prua (es. se la prua richiede 0.95, il vento richiede 0.82)
816
+ // CALCOLO DIFFERENZIALE: Tolleranza del vento proporzionale alla sensibilità di base
808
817
  const windThreshold = Math.max(0.60, baseThreshold - 0.13);
809
818
 
810
819
  let hObj = getCircularAverageFromBuffer(store.longBuf.hdg, CONFIG.averaging.longWindow * 2, false, now, baseThreshold, breakout);
package/index.js CHANGED
@@ -38,6 +38,7 @@ module.exports = function (app) {
38
38
  // Memoria temporale per rilevare la presenza di sensori nativi sul Cerbo
39
39
  let lastNativeTwsTime = 0;
40
40
  let lastNativeTwdTime = 0;
41
+ let lastNativeHeadingTrueTime = 0; // Traccia la presenza di bussola vera nativa
41
42
 
42
43
  // Monitoraggio dello scorrere dei blocchi da 30 minuti
43
44
  let lastFrozen30mSlot = 0;
@@ -248,10 +249,14 @@ module.exports = function (app) {
248
249
  lastNativeTwsTime = now; // Rilevato TWS nativo della centralina!
249
250
  manageHistory('tws', smoothedVal * 1.94384);
250
251
  }
252
+ else if (path === 'navigation.headingTrue') {
253
+ lastNativeHeadingTrueTime = now; // Rilevata prua vera nativa dalla rete NMEA
254
+ }
251
255
  // --- DECODIFICA PRUA MAGNETICA SERVER-SIDE ---
252
256
  else if (path === 'navigation.headingMagnetic') {
253
- const hasTrueHdg = raw['navigation.headingTrue'] !== undefined;
254
- if (!hasTrueHdg) {
257
+ // Converte in prua vera solo se non c'è una bussola vera nativa attiva negli ultimi 5s
258
+ const hasNativeTrueHdg = (now - lastNativeHeadingTrueTime < 5000);
259
+ if (!hasNativeTrueHdg) {
255
260
  const variation = raw['navigation.magneticVariation'] || 0;
256
261
  raw['navigation.headingTrue'] = (smoothedVal + variation + 2 * Math.PI) % (2 * Math.PI);
257
262
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sailingrotevista/rotevista-dash",
3
- "version": "7.0.30",
3
+ "version": "7.0.32",
4
4
  "description": "Wind Dashboard with navigation and course aids",
5
5
  "main": "index.js",
6
6
  "publishConfig": {
package/utils.js CHANGED
@@ -36,10 +36,9 @@ function getCircularAverageFromBuffer(bufferArray, windowMs, signed = false, now
36
36
  const len = bufferArray.length;
37
37
  if (len === 0) return null;
38
38
 
39
- // RIFERIMENTO TEMPORALE IMMUNE DA DESINCRONIZZAZIONE:
40
- // Usiamo come 'ora attuale' l'ultimo timestamp presente nel buffer stesso,
41
- // garantendo che la finestra di 2s sia sempre calcolata sui dati NMEA reali arrivati.
39
+ // RIFERIMENTO TEMPORALE IMMUNE DA DESINCRONIZZAZIONE (Usa l'ultimo dato del buffer)
42
40
  const referenceTime = bufferArray[len - 1].time;
41
+
43
42
  let sSin = 0, sCos = 0, count = 0;
44
43
  let newestTime = 0, oldestTime = 0;
45
44
 
@@ -54,7 +53,7 @@ function getCircularAverageFromBuffer(bufferArray, windowMs, signed = false, now
54
53
 
55
54
  for (let i = len - 1; i >= 0; i--) {
56
55
  const item = bufferArray[i];
57
- if ((referenceTime - item.time) > windowMs) break; // Usa referenceTime invece di 'now'
56
+ if ((referenceTime - item.time) > windowMs) break;
58
57
 
59
58
  let diffRad = Math.atan2(Math.sin(item.val - pilotRad), Math.cos(item.val - pilotRad));
60
59
  let finalSin, finalCos;
@@ -82,12 +81,14 @@ function getCircularAverageFromBuffer(bufferArray, windowMs, signed = false, now
82
81
  const R = Math.hypot(sSin, sCos) / count;
83
82
  const avgRad = Math.atan2(sSin, sCos);
84
83
  const finalVal = signed ? avgRad : (avgRad + Math.PI * 2) % (Math.PI * 2);
85
- const historyDuration = (count > 2) ? (newestTime - oldestTime) : 0;
86
84
  const safeR = Math.max(R, 1e-9);
87
85
 
86
+ // Stabilità valida se ci sono almeno 2 campioni nel buffer e la varianza radiale supera la soglia
87
+ const isStable = (count >= 2) && (R > stabilityThreshold);
88
+
88
89
  return {
89
90
  val: finalVal,
90
- stable: historyDuration > 10000 && R > stabilityThreshold,
91
+ stable: isStable,
91
92
  dev: (R < 1) ? Math.round(Math.sqrt(-2 * Math.log(safeR)) * (180 / Math.PI)) : 0,
92
93
  samples: count
93
94
  };