@sailingrotevista/rotevista-dash 7.0.36 → 7.0.38

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 +51 -30
  2. package/index.js +38 -30
  3. package/package.json +1 -1
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
 
@@ -342,7 +343,7 @@ function processIncomingData(path, val, source, timeMs) {
342
343
  if (path === "environment.depth.belowTransducer" && (val < -2.0 || val > 500)) {
343
344
  return;
344
345
  }
345
- // Usiamo il tempo reale del pacchetto del server per eliminare lo sfasamento
346
+
346
347
  const now = timeMs || Date.now();
347
348
  const score = getSourcePriorityScore(source);
348
349
 
@@ -373,7 +374,7 @@ function processIncomingData(path, val, source, timeMs) {
373
374
  }
374
375
  }
375
376
 
376
- // Aggiorna lo stato raw e sincronizza il timestamp del watchdog sull'orologio locale (Date.now()) per evitare falsi timeout
377
+ // Aggiorna lo stato raw e sincronizza il timestamp del watchdog sull'orologio locale (Date.now()) per evitare falsi timeout
377
378
  const localNow = Date.now();
378
379
  store.timestamps[path] = localNow;
379
380
  store.raw[path] = val;
@@ -401,25 +402,21 @@ function processIncomingData(path, val, source, timeMs) {
401
402
  lastPathProcessTimes[path] = now;
402
403
 
403
404
  // Da qui in poi, l'inserimento nei buffer fisici avviene rigorosamente a 1Hz:
404
- //if (path === "environment.wind.angleApparent") {
405
- // safePush(store.smoothBuf.awa, val, now);
406
- // safePush(store.longBuf.awa, val, now);
407
- //}
408
405
 
409
- // BUG RISOLTO: Intercetta il TWD nativo e lo spinge nei buffer della bussola radar
406
+ // Intercetta il TWD nativo e lo spinge nei buffer della bussola radar
410
407
  if (path === "environment.wind.directionTrue") {
411
408
  let directionVal = (val && typeof val === 'object' && val.val !== undefined) ? val.val : val;
412
409
  safePush(store.smoothBuf.twd, directionVal, now);
413
410
  safePush(store.longBuf.twd, directionVal, now);
414
411
  }
415
412
 
416
- // BUG RISOLTO: Intercetta il TWS nativo e lo memorizza in tempo reale
413
+ // Intercetta il TWS nativo e lo memorizza in tempo reale
417
414
  if (path === "environment.wind.speedTrue") {
418
415
  let speedVal = (val && typeof val === 'object' && val.val !== undefined) ? val.val : val;
419
416
  store.raw["environment.wind.speedTrue"] = speedVal;
420
417
  }
421
418
 
422
- // INTERCETTAZIONE TWA NATIVO (Angolo Vento Reale pronto all'uso)
419
+ // Intercetta TWA nativo (Angolo Vento Reale)
423
420
  if (path === "environment.wind.angleTrueWater") {
424
421
  let angleVal = (val && typeof val === 'object' && val.val !== undefined) ? val.val : val;
425
422
  store.raw["environment.wind.angleTrueWater"] = angleVal;
@@ -428,7 +425,7 @@ function processIncomingData(path, val, source, timeMs) {
428
425
  // --- GESTIONE PRUA VERA / MAGNETICA CON AUTODIVIAZIONE ---
429
426
  if (path === "navigation.headingTrue") {
430
427
  store.timestamps["navigation.headingTrueNative"] = now; // Marca la presenza di un sensore nativo di prua vera
431
- store.timestamps["navigation.headingTrue"] = now;
428
+ store.timestamps["navigation.headingTrue"] = localNow;
432
429
  safePush(store.smoothBuf.hdg, val, now);
433
430
  safePush(store.longBuf.hdg, val, now);
434
431
  }
@@ -441,7 +438,7 @@ function processIncomingData(path, val, source, timeMs) {
441
438
 
442
439
  // Registra il valore calcolato mantenendo attivo il watchdog senza bloccare i successivi pacchetti magnetici
443
440
  store.raw["navigation.headingTrue"] = calculatedTrueHdg;
444
- store.timestamps["navigation.headingTrue"] = now;
441
+ store.timestamps["navigation.headingTrue"] = localNow;
445
442
  safePush(store.smoothBuf.hdg, calculatedTrueHdg, now);
446
443
  safePush(store.longBuf.hdg, calculatedTrueHdg, now);
447
444
  }
@@ -457,7 +454,7 @@ function processIncomingData(path, val, source, timeMs) {
457
454
  const sog = store.raw["navigation.speedOverGround"] || 0;
458
455
  if (!hasCompass && sog > 0.77) { // 0.77 m/s = 1.5 nodi
459
456
  store.raw["navigation.headingTrue"] = val;
460
- store.timestamps["navigation.headingTrue"] = now; // (AGGIUNTO - RISOLVE IL LOCKOUT COG)
457
+ store.timestamps["navigation.headingTrue"] = localNow;
461
458
  safePush(store.smoothBuf.hdg, val, now);
462
459
  safePush(store.longBuf.hdg, val, now);
463
460
  }
@@ -669,10 +666,17 @@ function startDisplayLoop() {
669
666
  // --- CALCOLO TREND VENTO & ALLARME STRAMBATA ---
670
667
  updateWindTrend();
671
668
 
672
- // --- AGGIORNAMENTO STATUS CON CONTEGGIO MINUTI REALE ---
669
+ // --- AGGIORNAMENTO STATUS CON WATCHDOG ANTI-ZOMBIE ---
673
670
  const isSocketOpen = socket && socket.readyState === WebSocket.OPEN;
671
+ const isDataFlowing = (now - lastMessageReceivedTime) < 6000; // Flusso dati attivo negli ultimi 6s
674
672
 
675
- if (isSocketOpen) {
673
+ // Se il socket risulta aperto ma non riceve pacchetti da > 6s (connessione zombie congelata da Android)
674
+ if (isSocketOpen && !isDataFlowing && !document.hidden && !simulationMode) {
675
+ console.warn("🔌 [Watchdog] Rilevato socket zombie (0 byte ricevuti da 6s). Forzo riconnessione...");
676
+ connect();
677
+ }
678
+
679
+ if (isSocketOpen && isDataFlowing) {
676
680
  ui.status.className = "online"; // Colore Verde
677
681
  const viewportMinutes = CONFIG.graphs.historyMinutes * (isNavigating ? 1 : 2);
678
682
  const requiredMs = viewportMinutes * 60000;
@@ -691,7 +695,7 @@ function startDisplayLoop() {
691
695
  }
692
696
  } else {
693
697
  ui.status.className = "offline"; // Colore Rosso
694
- ui.status.innerText = "OFFLINE"; // Chirurgico: Forza il testo a OFFLINE se il socket è chiuso, evitando scritte verdi in rosso
698
+ ui.status.innerText = isSocketOpen ? "STALL (RECONNECTING...)" : "OFFLINE";
695
699
  }
696
700
 
697
701
  // --- WATCHDOG: CONTROLLO TIMEOUT ---
@@ -1270,6 +1274,17 @@ function startDynamicSimulation() {
1270
1274
 
1271
1275
  function connect() {
1272
1276
  if (simulationMode) return;
1277
+
1278
+ // Pulizia preventiva del vecchio socket orfano (elimina memory leak ed eventi doppi)
1279
+ if (socket) {
1280
+ socket.onopen = null;
1281
+ socket.onmessage = null;
1282
+ socket.onerror = null;
1283
+ socket.onclose = null;
1284
+ try { socket.close(); } catch(e) {}
1285
+ socket = null;
1286
+ }
1287
+
1273
1288
  let addr = window.location.host || CONFIG.server.fallbackIp;
1274
1289
  try {
1275
1290
  // Connessione con subscribe=none per impedire a Signal K l'invio automatico di tutto lo stream di bordo
@@ -1279,6 +1294,7 @@ function connect() {
1279
1294
  ui.status.className = "online";
1280
1295
  ui.status.innerText = "ONLINE";
1281
1296
  reconnectDelay = 1000;
1297
+ lastMessageReceivedTime = Date.now(); // Reset timestamp dati
1282
1298
 
1283
1299
  // Sottoscrizione selettiva bilanciata: applichiamo un filtro a 3 Hz (333ms) per i dati rapidi,
1284
1300
  // garantendo fluidità matematica senza ritardi e ottimizzando profondità (1s) e GPS (10s)
@@ -1320,11 +1336,12 @@ function connect() {
1320
1336
  };
1321
1337
 
1322
1338
  socket.onmessage = (e) => {
1323
- const d = JSON.parse(e.data);
1324
- if (d.updates) {
1325
- d.updates.forEach(u => {
1326
- // Sincronizzazione dell'orologio sul tempo reale del server (NMEA/GPS)
1327
- const timeMs = u.timestamp ? new Date(u.timestamp).getTime() : Date.now();
1339
+ lastMessageReceivedTime = Date.now(); // Aggiorna il battito cardiaco del flusso dati
1340
+ const d = JSON.parse(e.data);
1341
+ if (d.updates) {
1342
+ d.updates.forEach(u => {
1343
+ // Sincronizzazione dell'orologio sul tempo reale del server (NMEA/GPS)
1344
+ const timeMs = u.timestamp ? new Date(u.timestamp).getTime() : Date.now();
1328
1345
  let sourceLabel = "Unknown";
1329
1346
  if (u.$source) {
1330
1347
  sourceLabel = u.$source;
@@ -1471,20 +1488,24 @@ async function init() {
1471
1488
  }
1472
1489
  }
1473
1490
 
1474
- // Watchdog attivo per la gestione intelligente dello standby e il massimo risparmio energetico
1475
- document.addEventListener('visibilitychange', () => {
1476
- if (document.visibilityState === 'visible') {
1477
- console.log("🔌 [Watchdog] Schermo sbloccato / Tab visibile. Riconnessione immediata e allineamento storico...");
1478
- // Al risveglio stabiliamo una nuova connessione pulita che scaricherà lo storico accumulato in background dal server
1491
+ // Gestore unificato di riattivazione su Android / iOS (Visibility, Focus, Pageshow, Online)
1492
+ const handleWakeUp = () => {
1493
+ if (!document.hidden) {
1494
+ console.log("🔌 [Watchdog] Risveglio dispositivo / Tab attiva. Riconnessione immediata...");
1495
+ reconnectDelay = 1000;
1479
1496
  connect();
1480
- } else if (document.visibilityState === 'hidden') {
1481
- console.log("🔌 [Watchdog] Schermo bloccato / Tab in background. Chiusura WebSocket preventiva per salvaguardare la batteria.");
1482
- // Tagliamo attivamente la connessione per congelare all'istante l'attività di rete ed i consumi del browser
1497
+ } else {
1498
+ console.log("🔌 [Watchdog] Schermo bloccato / Tab in background. Chiusura preventiva WebSocket.");
1483
1499
  if (socket) {
1484
- socket.close();
1500
+ try { socket.close(); } catch(e) {}
1485
1501
  }
1486
1502
  }
1487
- });
1503
+ };
1504
+
1505
+ document.addEventListener('visibilitychange', handleWakeUp);
1506
+ window.addEventListener('pageshow', handleWakeUp);
1507
+ window.addEventListener('focus', handleWakeUp);
1508
+ window.addEventListener('online', handleWakeUp);
1488
1509
 
1489
1510
  window.addEventListener('load', init);
1490
1511
  window.addEventListener('pagehide', saveDashboardState);
package/index.js CHANGED
@@ -246,22 +246,21 @@ module.exports = function (app) {
246
246
  // Già gestito e normalizzato dal filtro passa-basso superiore
247
247
  }
248
248
  else if (path === 'environment.wind.speedTrue') {
249
- lastNativeTwsTime = now; // Rilevato TWS nativo della centralina!
250
- manageHistory('tws', smoothedVal * 1.94384);
251
- }
252
- else if (path === 'navigation.headingTrue') {
253
- lastNativeHeadingTrueTime = now; // Rilevata prua vera nativa dalla rete NMEA
254
- }
255
- // --- DECODIFICA PRUA MAGNETICA SERVER-SIDE ---
256
- else if (path === 'navigation.headingMagnetic') {
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) {
260
- const variation = raw['navigation.magneticVariation'] || 0;
261
- raw['navigation.headingTrue'] = (smoothedVal + variation + 2 * Math.PI) % (2 * Math.PI);
249
+ lastNativeTwsTime = now; // Rilevato TWS nativo della centralina!
250
+ manageHistory('tws', smoothedVal * 1.94384);
262
251
  }
263
- }
264
- else if (path === 'environment.wind.directionTrue') {
252
+ else if (path === 'navigation.headingTrue') {
253
+ lastNativeHeadingTrueTime = now; // Rilevata prua vera nativa dalla rete NMEA
254
+ }
255
+ // --- DECODIFICA PRUA MAGNETICA SERVER-SIDE ---
256
+ else if (path === 'navigation.headingMagnetic') {
257
+ const hasNativeTrueHdg = (now - lastNativeHeadingTrueTime < 5000);
258
+ if (!hasNativeTrueHdg) {
259
+ const variation = raw['navigation.magneticVariation'] || 0;
260
+ raw['navigation.headingTrue'] = (smoothedVal + variation + 2 * Math.PI) % (2 * Math.PI);
261
+ }
262
+ }
263
+ else if (path === 'environment.wind.directionTrue') {
265
264
  lastNativeTwdTime = now; // Rilevato TWD nativo della centralina!
266
265
  manageHistory('twd', smoothedVal);
267
266
  }
@@ -763,13 +762,13 @@ module.exports = function (app) {
763
762
 
764
763
  lastForecast30mSlot = current30mSlot; // Aggiorna preventivamente lo slot per evitare chiamate simultanee in caso di rallentamento di rete
765
764
 
766
- https.get(url, (res) => {
767
- if (res.statusCode !== 200) {
768
- app.error(`[Open-Meteo] HTTP Error: ${res.statusCode}`);
769
- res.resume();
770
- lastForecast30mSlot = 0; // Reset in caso di errore per permettere un tentativo al prossimo pacchetto GPS
771
- return;
772
- }
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
+ }
773
772
 
774
773
  let data = '';
775
774
  res.on('data', (chunk) => { data += chunk; });
@@ -809,15 +808,24 @@ module.exports = function (app) {
809
808
  calculateInterpolatedFuture(forecastList);
810
809
 
811
810
  } catch (err) {
812
- app.error(`[Open-Meteo] Error parsing JSON: ${err.message}`);
813
- 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
+ });
814
828
  }
815
- });
816
- }).on('error', (err) => {
817
- app.error(`[Open-Meteo] Network Error: ${err.message}`);
818
- lastForecast30mSlot = 0;
819
- });
820
- }
821
829
 
822
830
  /**
823
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.36",
3
+ "version": "7.0.38",
4
4
  "description": "Wind Dashboard with navigation and course aids",
5
5
  "main": "index.js",
6
6
  "publishConfig": {