@sailingrotevista/rotevista-dash 5.0.3 → 5.0.5
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 +144 -32
- package/package.json +1 -1
package/app.js
CHANGED
|
@@ -35,7 +35,7 @@ let CONFIG = {
|
|
|
35
35
|
const RENDER_INTERVAL_MS = 1000;
|
|
36
36
|
const TIMEOUT_MS = 5000;
|
|
37
37
|
const SIM_SAMPLE_INTERVAL = 1000;
|
|
38
|
-
const DASH_VERSION = "3.
|
|
38
|
+
const DASH_VERSION = "3.7"; // Major Update: Time-Based storage and GAP handling
|
|
39
39
|
const sourceLocks = {};
|
|
40
40
|
|
|
41
41
|
// ==========================================================================
|
|
@@ -666,62 +666,173 @@ function startDisplayLoop() {
|
|
|
666
666
|
}
|
|
667
667
|
|
|
668
668
|
// ==========================================================================
|
|
669
|
-
// 8. CONFIGURAZIONE E GRAFICI UTILS
|
|
669
|
+
// 8. CONFIGURAZIONE, AGGIORNAMENTO LIVE E GRAFICI UTILS
|
|
670
670
|
// ==========================================================================
|
|
671
|
+
|
|
672
|
+
let currentConfigString = ""; // Memoria per rilevare cambiamenti nei settings
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Funzione Helper: Applica fisicamente i dati JSON all'oggetto CONFIG globale
|
|
676
|
+
*/
|
|
677
|
+
function applyConfigData(data) {
|
|
678
|
+
Object.assign(CONFIG.alarms, data.alarms || {});
|
|
679
|
+
Object.assign(CONFIG.graphs, data.graphs || {});
|
|
680
|
+
Object.assign(CONFIG.averaging, data.averaging || {});
|
|
681
|
+
|
|
682
|
+
// Migrazione Silenziosa: Rilevato vecchio parametro stabilità (<= 0.85).
|
|
683
|
+
// Aggiornato a 0.95 per ottimizzazione filtri.
|
|
684
|
+
if (CONFIG.averaging.stabilityThreshold <= 0.85) {
|
|
685
|
+
CONFIG.averaging.stabilityThreshold = 0.95;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
if (data.scales) {
|
|
689
|
+
for (let key in data.scales) {
|
|
690
|
+
if (CONFIG.scales[key]) Object.assign(CONFIG.scales[key], data.scales[key]);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* Recupera la configurazione iniziale al caricamento della pagina
|
|
697
|
+
*/
|
|
671
698
|
async function fetchServerConfig() {
|
|
672
699
|
try {
|
|
673
700
|
const response = await fetch('/rotevista-config');
|
|
674
701
|
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
|
675
702
|
const data = await response.json();
|
|
676
703
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
704
|
+
// Salviamo l'impronta digitale della configurazione per i confronti futuri
|
|
705
|
+
currentConfigString = JSON.stringify(data);
|
|
706
|
+
|
|
707
|
+
applyConfigData(data);
|
|
708
|
+
console.log("✅ Configurazione iniziale applicata con successo.");
|
|
709
|
+
} catch (err) {
|
|
710
|
+
console.warn("⚠️ Impossibile raggiungere il server per le configurazioni. Utilizzo default locali. Motivo:", err.message);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/**
|
|
715
|
+
* Watchdog: Controlla in background se le impostazioni sul server sono cambiate
|
|
716
|
+
*/
|
|
717
|
+
async function watchConfigChanges() {
|
|
718
|
+
try {
|
|
719
|
+
const response = await fetch('/rotevista-config');
|
|
720
|
+
if (!response.ok) return;
|
|
721
|
+
const data = await response.json();
|
|
680
722
|
|
|
681
|
-
|
|
682
|
-
CONFIG.averaging.stabilityThreshold = 0.95;
|
|
683
|
-
console.log("♻️ Migrazione Silenziosa: Rilevato vecchio parametro stabilità (<= 0.85). Aggiornato a 0.95 per ottimizzazione filtri.");
|
|
684
|
-
}
|
|
723
|
+
const newConfigString = JSON.stringify(data);
|
|
685
724
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
725
|
+
// Se l'impronta digitale è diversa, l'utente ha salvato nuovi settings!
|
|
726
|
+
if (newConfigString !== currentConfigString) {
|
|
727
|
+
console.log("🔄 Rilevato cambio impostazioni su Signal K! Aggiornamento in tempo reale...");
|
|
728
|
+
|
|
729
|
+
// Applichiamo i nuovi settings al volo
|
|
730
|
+
applyConfigData(data);
|
|
731
|
+
|
|
732
|
+
// Aggiorniamo l'impronta
|
|
733
|
+
currentConfigString = newConfigString;
|
|
734
|
+
|
|
735
|
+
// FEEDBACK VISIVO: Avvisiamo l'utente dell'aggiornamento
|
|
736
|
+
ui.status.innerText = "CONFIG UPDATED!";
|
|
737
|
+
ui.status.style.color = "#00C851"; // Verde Neon
|
|
738
|
+
setTimeout(() => { ui.status.style.color = ""; }, 4000);
|
|
690
739
|
}
|
|
691
740
|
} catch (err) {
|
|
692
|
-
console
|
|
741
|
+
// Ignoriamo gli errori di rete nel watchdog per non intasare la console in caso di disconnessione
|
|
693
742
|
}
|
|
694
743
|
}
|
|
695
744
|
|
|
696
745
|
/**
|
|
697
|
-
*
|
|
746
|
+
* manageHistory v3.7 - Aggregazione semantica "Pro-Grade"
|
|
747
|
+
* Integrazioni:
|
|
748
|
+
* 1. Strict undefined check per lastUpdates.
|
|
749
|
+
* 2. Anti-dropout dinamico tarato sul 50% del Reef 1.
|
|
750
|
+
* 3. Clamping di sicurezza (no negativi, no Infinity).
|
|
698
751
|
*/
|
|
699
|
-
function manageHistory(
|
|
700
|
-
|
|
701
|
-
|
|
752
|
+
function manageHistory(type, value) {
|
|
753
|
+
// --- 1. VALIDAZIONE INPUT RIGOROSA ---
|
|
754
|
+
if (value === undefined || value === null || !isFinite(value)) return;
|
|
755
|
+
|
|
756
|
+
const now = Date.now();
|
|
757
|
+
const historyMinutes = Math.max(1, CONFIG.graphs.historyMinutes || 10);
|
|
758
|
+
const samples = Math.max(2, CONFIG.graphs.samples || 60);
|
|
759
|
+
const bucketIntervalMs = (historyMinutes * 60000) / samples;
|
|
760
|
+
|
|
761
|
+
// --- 2. INIT SICURO (Strict Check) ---
|
|
762
|
+
if (!store.graphTempBuf[type]) store.graphTempBuf[type] = [];
|
|
763
|
+
if (!store.histories[type]) store.histories[type] = [];
|
|
764
|
+
if (store.lastUpdates[type] === undefined) store.lastUpdates[type] = 0;
|
|
765
|
+
|
|
766
|
+
const tempBuf = store.graphTempBuf[type];
|
|
767
|
+
|
|
768
|
+
// --- 3. ANTI-DROPOUT DINAMICO (Auto-scaling) ---
|
|
769
|
+
// Ignora cadute a zero se il valore precedente era superiore al 50% del primo Reef
|
|
770
|
+
if ((type === 'tws' || type === 'aws') && value < 0.05 && tempBuf.length > 0) {
|
|
771
|
+
const lastPoint = tempBuf[tempBuf.length - 1];
|
|
772
|
+
const glitchThreshold = (CONFIG.graphs.reef1 || 15) * 0.5;
|
|
773
|
+
if (lastPoint && lastPoint.val > glitchThreshold) return;
|
|
774
|
+
}
|
|
702
775
|
|
|
703
|
-
|
|
704
|
-
|
|
776
|
+
// --- 4. STORAGE TEMPORANEO ---
|
|
777
|
+
tempBuf.push({ val: value, time: now });
|
|
705
778
|
|
|
706
|
-
|
|
707
|
-
|
|
779
|
+
// Controllo finestra temporale
|
|
780
|
+
const bucketReady = (now - store.lastUpdates[type] > bucketIntervalMs) || store.histories[type].length === 0;
|
|
781
|
+
if (!bucketReady) return;
|
|
708
782
|
|
|
709
|
-
|
|
710
|
-
|
|
783
|
+
// --- 5. AGGREGAZIONE SEMANTICA ---
|
|
784
|
+
let finalValue = value;
|
|
711
785
|
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
786
|
+
if (tempBuf.length > 0) {
|
|
787
|
+
// A. VENTO -> SUSTAINED PEAK (EMA Time-Aware)
|
|
788
|
+
if (type === 'tws' || type === 'aws') {
|
|
789
|
+
const tauMs = 2500;
|
|
790
|
+
let ema = tempBuf[0].val;
|
|
791
|
+
let maxSustained = ema;
|
|
715
792
|
|
|
716
|
-
|
|
717
|
-
|
|
793
|
+
for (let i = 1; i < tempBuf.length; i++) {
|
|
794
|
+
const dt = Math.max(1, tempBuf[i].time - tempBuf[i - 1].time);
|
|
795
|
+
const alpha = 1 - Math.exp(-dt / tauMs);
|
|
796
|
+
ema = (tempBuf[i].val * alpha) + (ema * (1 - alpha));
|
|
797
|
+
if (isFinite(ema) && ema > maxSustained) maxSustained = ema;
|
|
798
|
+
}
|
|
799
|
+
finalValue = maxSustained;
|
|
800
|
+
}
|
|
801
|
+
// B. PROFONDITÀ -> MINIMO
|
|
802
|
+
else if (type === 'depth') {
|
|
803
|
+
const vals = tempBuf.map(p => p.val).filter(v => isFinite(v));
|
|
804
|
+
if (vals.length > 0) finalValue = Math.min(...vals);
|
|
718
805
|
}
|
|
806
|
+
// C. VELOCITÀ -> MEDIA
|
|
807
|
+
else {
|
|
808
|
+
const vals = tempBuf.map(p => p.val).filter(v => isFinite(v));
|
|
809
|
+
if (vals.length > 0) {
|
|
810
|
+
const sum = vals.reduce((a, b) => a + b, 0);
|
|
811
|
+
finalValue = sum / tempBuf.length;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
719
815
|
|
|
720
|
-
|
|
721
|
-
|
|
816
|
+
// --- 6. CLAMPING E VALIDAZIONE FINALE ---
|
|
817
|
+
// Protezione contro valori negativi (fisicamente impossibili per questi dati) e non finiti
|
|
818
|
+
if (!isFinite(finalValue)) return;
|
|
819
|
+
finalValue = Math.max(0, finalValue);
|
|
820
|
+
|
|
821
|
+
// --- 7. STORAGE STORICO (Keys: val, time) ---
|
|
822
|
+
store.histories[type].push({ val: finalValue, time: now });
|
|
823
|
+
|
|
824
|
+
// --- 8. PRUNING DINAMICO ---
|
|
825
|
+
const maxViewportMinutes = historyMinutes * 2;
|
|
826
|
+
const maxHistoryMs = (maxViewportMinutes * 60000) + 60000;
|
|
827
|
+
|
|
828
|
+
while (store.histories[type].length > 0 && (now - store.histories[type][0].time) > maxHistoryMs) {
|
|
829
|
+
store.histories[type].shift();
|
|
722
830
|
}
|
|
723
|
-
}
|
|
724
831
|
|
|
832
|
+
// Reset per il prossimo bucket
|
|
833
|
+
store.graphTempBuf[type] = [];
|
|
834
|
+
store.lastUpdates[type] = now;
|
|
835
|
+
}
|
|
725
836
|
function calculateScale(type, data, mode) {
|
|
726
837
|
const s = CONFIG.scales[type]; let aMin = Math.min(...data), aMax = Math.max(...data);
|
|
727
838
|
if (mode === 'hercules') {
|
|
@@ -1018,6 +1129,7 @@ async function init() {
|
|
|
1018
1129
|
await fetchServerConfig();
|
|
1019
1130
|
startDisplayLoop();
|
|
1020
1131
|
connect();
|
|
1132
|
+
setInterval(watchConfigChanges, 10000);
|
|
1021
1133
|
}
|
|
1022
1134
|
|
|
1023
1135
|
window.addEventListener('load', init);
|