@sailingrotevista/rotevista-dash 7.0.37 → 7.0.39

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
@@ -53,6 +53,7 @@ let rotationTrend = 0, meteoTrend = 0;
53
53
  let lastShortAvgVal = null, lastInstantTwa = null;
54
54
  let lastTrendTime = Date.now(), lastGybeAlarmTime = 0, lastTWCompute = 0;
55
55
  let twDirty = false, isNavigating = false, reconnectDelay = 1000;
56
+ let lastMessageReceivedTime = Date.now(); // Watchdog anti-zombie per monitorare il flusso dati reale
56
57
 
57
58
  let pressTimer, isFocusActive = false;
58
59
 
@@ -76,6 +77,7 @@ const sourceLocks = {};
76
77
  const store = {
77
78
  raw: {},
78
79
  timestamps: {},
80
+ isNavigating: false, // Stato unificato di navigazione barca in movimento
79
81
  depthProtectedActive: false, // Memoria per lo stato della protezione profondità
80
82
  herculesScales: {}, // Memoria per i limiti attivi della modalità Hercules
81
83
  smoothBuf: { hdg: [], cog: [], awa: [], twa: [], twd: [] },
@@ -661,14 +663,22 @@ function startDisplayLoop() {
661
663
  const sogKts = msToKts(store.raw["navigation.speedOverGround"] || 0);
662
664
 
663
665
  isNavigating = stwKts > CONFIG.averaging.minSpeed || sogKts > CONFIG.averaging.minSpeed;
666
+ store.isNavigating = isNavigating; // Sincronizzazione stato nello store condiviso
664
667
 
665
668
  // --- CALCOLO TREND VENTO & ALLARME STRAMBATA ---
666
669
  updateWindTrend();
667
670
 
668
- // --- AGGIORNAMENTO STATUS CON CONTEGGIO MINUTI REALE ---
671
+ // --- AGGIORNAMENTO STATUS CON WATCHDOG ANTI-ZOMBIE ---
669
672
  const isSocketOpen = socket && socket.readyState === WebSocket.OPEN;
673
+ const isDataFlowing = (now - lastMessageReceivedTime) < 6000; // Flusso dati attivo negli ultimi 6s
670
674
 
671
- if (isSocketOpen) {
675
+ // Se il socket risulta aperto ma non riceve pacchetti da > 6s (connessione zombie congelata da Android)
676
+ if (isSocketOpen && !isDataFlowing && !document.hidden && !simulationMode) {
677
+ console.warn("🔌 [Watchdog] Rilevato socket zombie (0 byte ricevuti da 6s). Forzo riconnessione...");
678
+ connect();
679
+ }
680
+
681
+ if (isSocketOpen && isDataFlowing) {
672
682
  ui.status.className = "online"; // Colore Verde
673
683
  const viewportMinutes = CONFIG.graphs.historyMinutes * (isNavigating ? 1 : 2);
674
684
  const requiredMs = viewportMinutes * 60000;
@@ -687,7 +697,7 @@ function startDisplayLoop() {
687
697
  }
688
698
  } else {
689
699
  ui.status.className = "offline"; // Colore Rosso
690
- ui.status.innerText = "OFFLINE"; // Chirurgico: Forza il testo a OFFLINE se il socket è chiuso, evitando scritte verdi in rosso
700
+ ui.status.innerText = isSocketOpen ? "STALL (RECONNECTING...)" : "OFFLINE";
691
701
  }
692
702
 
693
703
  // --- WATCHDOG: CONTROLLO TIMEOUT ---
@@ -1024,18 +1034,7 @@ async function fetchServerHistory() {
1024
1034
  timestamp: data.futureForecast.timestamp + timeDelta
1025
1035
  };
1026
1036
  }
1027
- if (data.twd) {
1028
- store.twdMinuteBuffer = data.twd.map(p => ({
1029
- ...p,
1030
- time: p.time + timeDelta
1031
- }));
1032
- }
1033
- if (data.tws) {
1034
- store.twsMinuteBuffer = data.tws.map(p => ({
1035
- ...p,
1036
- time: p.time + timeDelta
1037
- }));
1038
- }
1037
+ // data.twd e data.tws sono già mappati direttamente in store.histories.twd e store.histories.tws
1039
1038
 
1040
1039
  // Sincronizziamo lo storico dei minuti per il radar, ma lasciamo che il buffer rapido 'store.longBuf.twd'
1041
1040
  // parta pulito all'avvio. Si popolerà istantaneamente a 3Hz con i soli dati in tempo reale,
@@ -1266,6 +1265,17 @@ function startDynamicSimulation() {
1266
1265
 
1267
1266
  function connect() {
1268
1267
  if (simulationMode) return;
1268
+
1269
+ // Pulizia preventiva del vecchio socket orfano (elimina memory leak ed eventi doppi)
1270
+ if (socket) {
1271
+ socket.onopen = null;
1272
+ socket.onmessage = null;
1273
+ socket.onerror = null;
1274
+ socket.onclose = null;
1275
+ try { socket.close(); } catch(e) {}
1276
+ socket = null;
1277
+ }
1278
+
1269
1279
  let addr = window.location.host || CONFIG.server.fallbackIp;
1270
1280
  try {
1271
1281
  // Connessione con subscribe=none per impedire a Signal K l'invio automatico di tutto lo stream di bordo
@@ -1275,6 +1285,7 @@ function connect() {
1275
1285
  ui.status.className = "online";
1276
1286
  ui.status.innerText = "ONLINE";
1277
1287
  reconnectDelay = 1000;
1288
+ lastMessageReceivedTime = Date.now(); // Reset timestamp dati
1278
1289
 
1279
1290
  // Sottoscrizione selettiva bilanciata: applichiamo un filtro a 3 Hz (333ms) per i dati rapidi,
1280
1291
  // garantendo fluidità matematica senza ritardi e ottimizzando profondità (1s) e GPS (10s)
@@ -1316,11 +1327,12 @@ function connect() {
1316
1327
  };
1317
1328
 
1318
1329
  socket.onmessage = (e) => {
1319
- const d = JSON.parse(e.data);
1320
- if (d.updates) {
1321
- d.updates.forEach(u => {
1322
- // Sincronizzazione dell'orologio sul tempo reale del server (NMEA/GPS)
1323
- const timeMs = u.timestamp ? new Date(u.timestamp).getTime() : Date.now();
1330
+ lastMessageReceivedTime = Date.now(); // Aggiorna il battito cardiaco del flusso dati
1331
+ const d = JSON.parse(e.data);
1332
+ if (d.updates) {
1333
+ d.updates.forEach(u => {
1334
+ // Sincronizzazione dell'orologio sul tempo reale del server (NMEA/GPS)
1335
+ const timeMs = u.timestamp ? new Date(u.timestamp).getTime() : Date.now();
1324
1336
  let sourceLabel = "Unknown";
1325
1337
  if (u.$source) {
1326
1338
  sourceLabel = u.$source;
@@ -1467,20 +1479,24 @@ async function init() {
1467
1479
  }
1468
1480
  }
1469
1481
 
1470
- // Watchdog attivo per la gestione intelligente dello standby e il massimo risparmio energetico
1471
- document.addEventListener('visibilitychange', () => {
1472
- if (document.visibilityState === 'visible') {
1473
- console.log("🔌 [Watchdog] Schermo sbloccato / Tab visibile. Riconnessione immediata e allineamento storico...");
1474
- // Al risveglio stabiliamo una nuova connessione pulita che scaricherà lo storico accumulato in background dal server
1482
+ // Gestore unificato di riattivazione su Android / iOS (Visibility, Focus, Pageshow, Online)
1483
+ const handleWakeUp = () => {
1484
+ if (!document.hidden) {
1485
+ console.log("🔌 [Watchdog] Risveglio dispositivo / Tab attiva. Riconnessione immediata...");
1486
+ reconnectDelay = 1000;
1475
1487
  connect();
1476
- } else if (document.visibilityState === 'hidden') {
1477
- console.log("🔌 [Watchdog] Schermo bloccato / Tab in background. Chiusura WebSocket preventiva per salvaguardare la batteria.");
1478
- // Tagliamo attivamente la connessione per congelare all'istante l'attività di rete ed i consumi del browser
1488
+ } else {
1489
+ console.log("🔌 [Watchdog] Schermo bloccato / Tab in background. Chiusura preventiva WebSocket.");
1479
1490
  if (socket) {
1480
- socket.close();
1491
+ try { socket.close(); } catch(e) {}
1481
1492
  }
1482
1493
  }
1483
- });
1494
+ };
1495
+
1496
+ document.addEventListener('visibilitychange', handleWakeUp);
1497
+ window.addEventListener('pageshow', handleWakeUp);
1498
+ window.addEventListener('focus', handleWakeUp);
1499
+ window.addEventListener('online', handleWakeUp);
1484
1500
 
1485
1501
  window.addEventListener('load', init);
1486
1502
  window.addEventListener('pagehide', saveDashboardState);
package/index.js CHANGED
@@ -762,13 +762,13 @@ module.exports = function (app) {
762
762
 
763
763
  lastForecast30mSlot = current30mSlot; // Aggiorna preventivamente lo slot per evitare chiamate simultanee in caso di rallentamento di rete
764
764
 
765
- https.get(url, (res) => {
766
- if (res.statusCode !== 200) {
767
- app.error(`[Open-Meteo] HTTP Error: ${res.statusCode}`);
768
- res.resume();
769
- lastForecast30mSlot = 0; // Reset in caso di errore per permettere un tentativo al prossimo pacchetto GPS
770
- return;
771
- }
765
+ const req = https.get(url, (res) => {
766
+ if (res.statusCode !== 200) {
767
+ app.error(`[Open-Meteo] HTTP Error: ${res.statusCode}`);
768
+ res.resume();
769
+ lastForecast30mSlot = 0; // Reset in caso di errore per permettere un tentativo al prossimo pacchetto GPS
770
+ return;
771
+ }
772
772
 
773
773
  let data = '';
774
774
  res.on('data', (chunk) => { data += chunk; });
@@ -808,15 +808,24 @@ module.exports = function (app) {
808
808
  calculateInterpolatedFuture(forecastList);
809
809
 
810
810
  } catch (err) {
811
- app.error(`[Open-Meteo] Error parsing JSON: ${err.message}`);
812
- lastForecast30mSlot = 0;
811
+ app.error(`[Open-Meteo] Error parsing JSON: ${err.message}`);
812
+ lastForecast30mSlot = 0;
813
+ }
814
+ });
815
+ });
816
+
817
+ // Timeout di sicurezza a 8 secondi contro cadute di linea 4G/Satellite
818
+ req.setTimeout(8000, () => {
819
+ app.error('[Open-Meteo] Request Timeout (8s)');
820
+ req.abort();
821
+ lastForecast30mSlot = 0;
822
+ });
823
+
824
+ req.on('error', (err) => {
825
+ app.error(`[Open-Meteo] Network Error: ${err.message}`);
826
+ lastForecast30mSlot = 0;
827
+ });
813
828
  }
814
- });
815
- }).on('error', (err) => {
816
- app.error(`[Open-Meteo] Network Error: ${err.message}`);
817
- lastForecast30mSlot = 0;
818
- });
819
- }
820
829
 
821
830
  /**
822
831
  * calculateInterpolatedFuture: Esegue l'interpolazione lineare e vettoriale circolare
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sailingrotevista/rotevista-dash",
3
- "version": "7.0.37",
3
+ "version": "7.0.39",
4
4
  "description": "Wind Dashboard with navigation and course aids",
5
5
  "main": "index.js",
6
6
  "publishConfig": {
package/weather-radar.js CHANGED
@@ -59,8 +59,9 @@ function calculateActive30mRing() {
59
59
  const now = Date.now();
60
60
  const start30m = now - 1800000;
61
61
 
62
- const twdRecent = (store.twdMinuteBuffer || []).filter(p => p.time >= start30m);
63
- const twsRecent = (store.twsMinuteBuffer || []).filter(p => p.time >= start30m);
62
+ // Lettura unificata dai buffer storici dello store
63
+ const twdRecent = (store.histories.twd || []).filter(p => p.time >= start30m);
64
+ const twsRecent = (store.histories.tws || []).filter(p => p.time >= start30m);
64
65
 
65
66
  if (twdRecent.length === 0) return null;
66
67
 
@@ -264,7 +265,8 @@ function renderRadar() {
264
265
  // Disegno del LED lampeggiante del meteo-trend
265
266
  if (activeRing && !activeRing.isCalm) {
266
267
  const twdNow = getCircularAverageFromBuffer(store.longBuf.twd, 60000, false);
267
- const strategicWindowMs = (isNavigating ? 15 : 60) * 60000;
268
+ const isNav = (typeof store !== 'undefined' && store.isNavigating);
269
+ const strategicWindowMs = (isNav ? 15 : 60) * 60000;
268
270
  const twdRef = getCircularAverageFromBuffer(store.longBuf.twd, strategicWindowMs, false);
269
271
 
270
272
  if (twdNow && twdRef) {