@sailingrotevista/rotevista-dash 7.0.31 → 7.0.33
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 +61 -57
- package/package.json +1 -1
- 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,21 +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
|
-
|
|
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);
|
|
353
|
-
//
|
|
359
|
+
// Switch immediato tra apparati principali di pari priorità (es. YD e AP) se inattivi da > 1.5s
|
|
354
360
|
const isSamePriorityStale = (score === currentLock.score && (now - currentLock.lastSeen > 1500));
|
|
355
361
|
|
|
356
362
|
if (isSameSource) {
|
|
357
363
|
// Stessa sorgente: aggiorna il timestamp e mantiene il blocco
|
|
358
364
|
currentLock.lastSeen = now;
|
|
359
365
|
currentLock.score = score;
|
|
360
|
-
} else if (
|
|
361
|
-
//
|
|
366
|
+
} else if (hasHigherPriority || isLockExpired || isSamePriorityStale) {
|
|
367
|
+
// Passa a sorgenti secondarie (Victron) solo se Yacht Device è spento da > 5s
|
|
362
368
|
sourceLocks[path] = { label: source, score: score, lastSeen: now };
|
|
363
369
|
console.log(`🔌 [Smart Lock] Path "${path}" switched to: ${source} (Score: ${score})`);
|
|
364
370
|
} else {
|
|
365
|
-
//
|
|
371
|
+
// Scarta sorgenti imprecise di servizio mentre Yacht Device è la sorgente principale attiva
|
|
366
372
|
return;
|
|
367
373
|
}
|
|
368
374
|
}
|
|
@@ -803,62 +809,60 @@ function startDisplayLoop() {
|
|
|
803
809
|
saveDashboardState();
|
|
804
810
|
}
|
|
805
811
|
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
const windThreshold = Math.max(0.60, baseThreshold - 0.13);
|
|
812
|
+
// AGGIORNAMENTO FLUIDO MEDIE UI (Esecuzione a 1Hz costante ad ogni secondo)
|
|
813
|
+
const baseThreshold = CONFIG.averaging.stabilityThreshold || 0.95;
|
|
814
|
+
const breakout = CONFIG.averaging.stabilityBreakout || 15;
|
|
815
|
+
|
|
816
|
+
// CALCOLO DIFFERENZIALE: Tolleranza del vento proporzionale alla sensibilità di base
|
|
817
|
+
const windThreshold = Math.max(0.60, baseThreshold - 0.13);
|
|
813
818
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
}
|
|
830
|
-
safeSetText(ui.tackLabel, currentTackLabelMode);
|
|
819
|
+
let hObj = getCircularAverageFromBuffer(store.longBuf.hdg, CONFIG.averaging.longWindow * 2, false, now, baseThreshold, breakout);
|
|
820
|
+
let cObj = getCircularAverageFromBuffer(store.longBuf.cog, CONFIG.averaging.longWindow, false, now, baseThreshold, breakout);
|
|
821
|
+
|
|
822
|
+
// Applichiamo la soglia differenziale ottimizzata per i tre dati legati al vento
|
|
823
|
+
let awObj = getCircularAverageFromBuffer(store.longBuf.awa, CONFIG.averaging.longWindow, true, now, windThreshold, breakout);
|
|
824
|
+
let twObj = getCircularAverageFromBuffer(store.longBuf.twa, CONFIG.averaging.longWindow, true, now, windThreshold, breakout);
|
|
825
|
+
let twdObj = getCircularAverageFromBuffer(store.longBuf.twd, CONFIG.averaging.longWindow, false, now, windThreshold, breakout);
|
|
826
|
+
|
|
827
|
+
// --- GESTIONE DINAMICA ETICHETTA TACK/GYBE CON ISTERESI DI 10 GRADI ---
|
|
828
|
+
if (ui.tackLabel) {
|
|
829
|
+
const absTwa = Math.abs(radToDeg(store.raw["environment.wind.angleTrueWater"] || 0));
|
|
830
|
+
if (currentTackLabelMode === 'TACK' && absTwa > 95) {
|
|
831
|
+
currentTackLabelMode = 'GYBE';
|
|
832
|
+
} else if (currentTackLabelMode === 'GYBE' && absTwa < 85) {
|
|
833
|
+
currentTackLabelMode = 'TACK';
|
|
831
834
|
}
|
|
835
|
+
safeSetText(ui.tackLabel, currentTackLabelMode);
|
|
836
|
+
}
|
|
832
837
|
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
838
|
+
upUI(ui.hdg, hObj, store.raw["navigation.headingTrue"], true);
|
|
839
|
+
upUI(ui.cog, cObj, store.raw["navigation.courseOverGroundTrue"], true);
|
|
840
|
+
upUI(ui.awaAvg, awObj, store.raw["environment.wind.angleApparent"], false);
|
|
841
|
+
upUI(ui.twaAvg, twObj, store.raw["environment.wind.angleTrueWater"], false);
|
|
842
|
+
upUI(ui.twdAvg, twdObj, store.raw["environment.wind.directionTrue"], true);
|
|
843
|
+
|
|
844
|
+
if (hObj && twdObj) {
|
|
845
|
+
const reflectAngle = (targetRad, axisRad) => {
|
|
846
|
+
const dS = Math.sin(axisRad - targetRad);
|
|
847
|
+
const dC = Math.cos(axisRad - targetRad);
|
|
848
|
+
return Math.atan2(Math.sin(axisRad) * dC + Math.cos(axisRad) * dS, Math.cos(axisRad) * dC - Math.sin(axisRad) * dS);
|
|
849
|
+
};
|
|
850
|
+
const unstableH = !hObj.stable || !twdObj.stable || hObj.dev > CONFIG.averaging.stabilityBreakout;
|
|
851
|
+
if (!isNavigating) ui.tackHdg.innerHTML = "---°";
|
|
852
|
+
else if (unstableH) { ui.tackHdg.innerHTML = "---°"; ui.tackHdg.classList.add('unstable-data'); }
|
|
853
|
+
else {
|
|
854
|
+
const rH = (radToDeg(reflectAngle(hObj.val, twdObj.val)) + 360) % 360;
|
|
855
|
+
ui.tackHdg.innerHTML = `${Math.round(rH).toString().padStart(3, '0')}°`;
|
|
856
|
+
ui.tackHdg.classList.remove('unstable-data');
|
|
857
|
+
}
|
|
858
|
+
if (cObj) {
|
|
859
|
+
const unstableC = !cObj.stable || !twdObj.stable || cObj.dev > CONFIG.averaging.stabilityBreakout;
|
|
860
|
+
if (!isNavigating) ui.tackCog.innerHTML = "---°";
|
|
861
|
+
else if (unstableC) { ui.tackCog.innerHTML = "---°"; ui.tackCog.classList.add('unstable-data'); }
|
|
848
862
|
else {
|
|
849
|
-
const
|
|
850
|
-
ui.
|
|
851
|
-
ui.
|
|
852
|
-
}
|
|
853
|
-
if (cObj) {
|
|
854
|
-
const unstableC = !cObj.stable || !twdObj.stable || cObj.dev > CONFIG.averaging.stabilityBreakout;
|
|
855
|
-
if (!isNavigating) ui.tackCog.innerHTML = "---°";
|
|
856
|
-
else if (unstableC) { ui.tackCog.innerHTML = "---°"; ui.tackCog.classList.add('unstable-data'); }
|
|
857
|
-
else {
|
|
858
|
-
const rC = (radToDeg(reflectAngle(cObj.val, twdObj.val)) + 360) % 360;
|
|
859
|
-
ui.tackCog.innerHTML = `${Math.round(rC).toString().padStart(3, '0')}°`;
|
|
860
|
-
ui.tackCog.classList.remove('unstable-data');
|
|
861
|
-
}
|
|
863
|
+
const rC = (radToDeg(reflectAngle(cObj.val, twdObj.val)) + 360) % 360;
|
|
864
|
+
ui.tackCog.innerHTML = `${Math.round(rC).toString().padStart(3, '0')}°`;
|
|
865
|
+
ui.tackCog.classList.remove('unstable-data');
|
|
862
866
|
}
|
|
863
867
|
}
|
|
864
868
|
}
|
package/package.json
CHANGED
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;
|
|
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:
|
|
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
|
};
|