@sailingrotevista/rotevista-dash 7.0.29 → 7.0.31

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/app.js CHANGED
@@ -348,15 +348,17 @@ function processIncomingData(path, val, source, timeMs) {
348
348
  } else {
349
349
  const currentLock = sourceLocks[path];
350
350
  const isSameSource = (currentLock.label === source);
351
- const isLockExpired = (now - currentLock.lastSeen > 12000); // Scadenza a 12 secondi per tollerare il GPS dello Yacht Devices
351
+ const isLockExpired = (now - currentLock.lastSeen > 12000); // Scadenza a 12 secondi
352
352
  const hasHigherPriority = (score > currentLock.score);
353
+ // Consente lo switch tra dispositivi di pari priorità (es. AP e YD) se la sorgente precedente è inattiva da > 1.5s
354
+ const isSamePriorityStale = (score === currentLock.score && (now - currentLock.lastSeen > 1500));
353
355
 
354
356
  if (isSameSource) {
355
357
  // Stessa sorgente: aggiorna il timestamp e mantiene il blocco
356
358
  currentLock.lastSeen = now;
357
359
  currentLock.score = score;
358
- } else if (isLockExpired || hasHigherPriority) {
359
- // Ruba il blocco se la sorgente precedente è scaduta o se questa ha priorità superiore
360
+ } else if (isLockExpired || hasHigherPriority || isSamePriorityStale) {
361
+ // Ruba il blocco se la sorgente precedente è scaduta, ha priorità superiore o è inattiva da 1.5s a pari priorità
360
362
  sourceLocks[path] = { label: source, score: score, lastSeen: now };
361
363
  console.log(`🔌 [Smart Lock] Path "${path}" switched to: ${source} (Score: ${score})`);
362
364
  } else {
@@ -410,19 +412,21 @@ function processIncomingData(path, val, source, timeMs) {
410
412
 
411
413
  // --- GESTIONE PRUA VERA / MAGNETICA CON AUTODIVIAZIONE ---
412
414
  if (path === "navigation.headingTrue") {
415
+ store.timestamps["navigation.headingTrueNative"] = now; // Marca la presenza di un sensore nativo di prua vera
416
+ store.timestamps["navigation.headingTrue"] = now;
413
417
  safePush(store.smoothBuf.hdg, val, now);
414
418
  safePush(store.longBuf.hdg, val, now);
415
419
  }
416
420
  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) {
421
+ // Se non c'è una prua vera NATIVA negli ultimi 5 secondi, converte e registra ad ogni secondo la prua magnetica
422
+ const hasNativeTrueHdg = store.timestamps["navigation.headingTrueNative"] && (now - store.timestamps["navigation.headingTrueNative"] < 5000);
423
+ if (!hasNativeTrueHdg) {
420
424
  const variation = store.raw["navigation.magneticVariation"] || 0; // Legge la declinazione magnetica del GPS
421
425
  const calculatedTrueHdg = (val + variation + 2 * Math.PI) % (2 * Math.PI);
422
426
 
423
- // Registriamo il valore calcolato come Prua Vera temporanea e aggiorniamo il timestamp per evitare i timeout del watchdog
427
+ // Registra il valore calcolato mantenendo attivo il watchdog senza bloccare i successivi pacchetti magnetici
424
428
  store.raw["navigation.headingTrue"] = calculatedTrueHdg;
425
- store.timestamps["navigation.headingTrue"] = now; // (AGGIUNTO - RISOLVE IL LOCKOUT)
429
+ store.timestamps["navigation.headingTrue"] = now;
426
430
  safePush(store.smoothBuf.hdg, calculatedTrueHdg, now);
427
431
  safePush(store.longBuf.hdg, calculatedTrueHdg, now);
428
432
  }
package/gauge.js CHANGED
@@ -55,10 +55,10 @@ function updateCentralGauge(store, ui, now, isNavigating, sogKts, stwKts, rawAws
55
55
  }
56
56
 
57
57
  // B. Rotazione dei puntatori analogici di AWA (Apparent) e TWA (True)
58
- const smAwa = getCircularAverageFromBuffer(store.smoothBuf.awa, 2000, true);
59
- const smTwa = getCircularAverageFromBuffer(store.smoothBuf.twa, 2000, true);
60
- if (smAwa) ui.awa.setAttribute('transform', `rotate(${curAwaRot = getShortestRotation(curAwaRot, radToDeg(smAwa.val))}, 200, 200)`);
61
- if (smTwa) ui.twa.setAttribute('transform', `rotate(${curTwaRot = getShortestRotation(curTwaRot, radToDeg(smTwa.val))}, 200, 200)`);
58
+ const smAwa = getCircularAverageFromBuffer(store.smoothBuf.awa, 2000, true);
59
+ const smTwa = getCircularAverageFromBuffer(store.smoothBuf.twa, 2000, true);
60
+ if (smAwa) ui.awa.setAttribute('transform', `rotate(${curAwaRot = getShortestRotation(curAwaRot, radToDeg(smAwa.val))}, 200, 200)`);
61
+ if (smTwa) ui.twa.setAttribute('transform', `rotate(${curTwaRot = getShortestRotation(curTwaRot, radToDeg(smTwa.val))}, 200, 200)`);
62
62
 
63
63
  // C. Calcolo dello Scarroccio (Leeway) e orientamento del vettore Track
64
64
  if (store.raw["navigation.courseOverGroundTrue"] !== undefined && store.raw["navigation.headingTrue"] !== undefined) {
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.29",
3
+ "version": "7.0.31",
4
4
  "description": "Wind Dashboard with navigation and course aids",
5
5
  "main": "index.js",
6
6
  "publishConfig": {
@@ -13,6 +13,7 @@
13
13
  "author": "Sailing Rotevista",
14
14
  "license": "MIT",
15
15
  "signalk": {
16
- "displayName": "Rotevista Wind Dashboard"
16
+ "displayName": "Rotevista Wind Dashboard",
17
+ "appIcon": "icons/dash.png"
17
18
  }
18
19
  }
package/style.css CHANGED
@@ -612,3 +612,29 @@ body.night-mode {
612
612
  }
613
613
 
614
614
  */
615
+
616
+ /* ==========================================================================
617
+ 14. POSIZIONAMENTO SOVRAPPOSTO LAYERED COMPASS (0% CARICO CPU)
618
+ ========================================================================== */
619
+
620
+ /* Contenitore padre relativo */
621
+ .wind-gauge-container {
622
+ position: relative;
623
+ width: 100%;
624
+ height: 100%;
625
+ max-height: 100%;
626
+ display: flex;
627
+ align-items: center;
628
+ justify-content: center;
629
+ }
630
+
631
+ /* Sovrapposizione assoluta perfetta del livello lancette sopra lo sfondo */
632
+ #wind-gauge-pointers {
633
+ position: absolute;
634
+ top: 0;
635
+ left: 0;
636
+ width: 100%;
637
+ height: 100%;
638
+ object-fit: contain;
639
+ pointer-events: none; /* Permette al tocco del mouse di passare sotto al centro interattivo */
640
+ }
package/utils.js CHANGED
@@ -33,10 +33,13 @@ function safeSetText(el, text) {
33
33
 
34
34
  // --- 2. MOTORE MATEMATICO: MEDIA CIRCOLARE VETTORIALE ---
35
35
  function getCircularAverageFromBuffer(bufferArray, windowMs, signed = false, now, stabilityThreshold = 0.95, stabilityBreakout = 15) {
36
- now = now || Date.now();
37
36
  const len = bufferArray.length;
38
37
  if (len === 0) return null;
39
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.
42
+ const referenceTime = bufferArray[len - 1].time;
40
43
  let sSin = 0, sCos = 0, count = 0;
41
44
  let newestTime = 0, oldestTime = 0;
42
45
 
@@ -51,7 +54,7 @@ function getCircularAverageFromBuffer(bufferArray, windowMs, signed = false, now
51
54
 
52
55
  for (let i = len - 1; i >= 0; i--) {
53
56
  const item = bufferArray[i];
54
- if ((now - item.time) > windowMs) break;
57
+ if ((referenceTime - item.time) > windowMs) break; // Usa referenceTime invece di 'now'
55
58
 
56
59
  let diffRad = Math.atan2(Math.sin(item.val - pilotRad), Math.cos(item.val - pilotRad));
57
60
  let finalSin, finalCos;