@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.
- package/app.js +51 -30
- package/index.js +38 -30
- 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
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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"] =
|
|
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"] =
|
|
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"] =
|
|
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
|
|
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
|
-
|
|
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 = "
|
|
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
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
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
|
-
//
|
|
1475
|
-
|
|
1476
|
-
if (document.
|
|
1477
|
-
console.log("🔌 [Watchdog]
|
|
1478
|
-
|
|
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
|
|
1481
|
-
console.log("🔌 [Watchdog] Schermo bloccato / Tab in background. Chiusura
|
|
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
|
-
|
|
250
|
-
|
|
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
|
-
|
|
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
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
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
|
-
|
|
813
|
-
|
|
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
|