@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.
Files changed (2) hide show
  1. package/app.js +144 -32
  2. 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.5"; // Major Update: Time-Based storage and GAP handling
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
- Object.assign(CONFIG.alarms, data.alarms || {});
678
- Object.assign(CONFIG.graphs, data.graphs || {});
679
- Object.assign(CONFIG.averaging, data.averaging || {});
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
- if (CONFIG.averaging.stabilityThreshold <= 0.85) {
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
- if (data.scales) {
687
- for (let key in data.scales) {
688
- if (CONFIG.scales[key]) Object.assign(CONFIG.scales[key], data.scales[key]);
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.warn("⚠️ Utilizzo default locali. Motivo:", err.message);
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
- * Rielaborazione Storica Time-Based (Rimuove accumulo e gestisce il Pruning dinamicamente)
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(t, v) {
700
- const n = Date.now();
701
- const interval = (CONFIG.graphs.historyMinutes * 60000) / CONFIG.graphs.samples;
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
- if (!store.graphTempBuf[t]) store.graphTempBuf[t] = [];
704
- store.graphTempBuf[t].push(v);
776
+ // --- 4. STORAGE TEMPORANEO ---
777
+ tempBuf.push({ val: value, time: now });
705
778
 
706
- if (n - store.lastUpdates[t] > interval || store.histories[t].length === 0) {
707
- const avg = store.graphTempBuf[t].reduce((a, b) => a + b, 0) / store.graphTempBuf[t].length;
779
+ // Controllo finestra temporale
780
+ const bucketReady = (now - store.lastUpdates[type] > bucketIntervalMs) || store.histories[type].length === 0;
781
+ if (!bucketReady) return;
708
782
 
709
- // NUOVO STORAGE TIME-BASED
710
- store.histories[t].push({ time: n, val: avg });
783
+ // --- 5. AGGREGAZIONE SEMANTICA ---
784
+ let finalValue = value;
711
785
 
712
- // PRUNING DINAMICO
713
- const maxViewportMinutes = CONFIG.graphs.historyMinutes * 2;
714
- const maxHistoryMs = (maxViewportMinutes * 60000) + 30000;
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
- while (store.histories[t].length > 0 && (n - store.histories[t][0].time) > maxHistoryMs) {
717
- store.histories[t].shift();
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
- store.graphTempBuf[t] = [];
721
- store.lastUpdates[t] = n;
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sailingrotevista/rotevista-dash",
3
- "version": "5.0.3",
3
+ "version": "5.0.5",
4
4
  "description": "Wind Dashboard with navigation and course aids",
5
5
  "main": "index.js",
6
6
  "publishConfig": {