@sailingrotevista/rotevista-dash 4.0.20 → 4.0.21

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 CHANGED
@@ -17,7 +17,7 @@ let CONFIG = {
17
17
  smoothWindow: 2000,
18
18
  longWindow: 30000,
19
19
  stabilityTolerance: 2000,
20
- stabilityThreshold: 0.9,
20
+ stabilityThreshold: 0.95,
21
21
  minSpeed: 0,
22
22
  stabilityBreakout: 15
23
23
  },
@@ -120,6 +120,10 @@ function getShortestRotation(curr, target) {
120
120
  * Inserimento sicuro nel buffer con trigonometria precaricata e pruning automatico
121
121
  */
122
122
  function safePush(buffer, val, time) {
123
+ // --- PROTEZIONE ANTI-NaN (FONDAMENTALE ALL'AVVIO) ---
124
+ // Se il valore è nullo, non definito o non è un numero, ignoriamo l'inserimento
125
+ if (val === null || val === undefined || isNaN(val)) return;
126
+
123
127
  buffer.push({
124
128
  val: val,
125
129
  time: time,
@@ -139,63 +143,78 @@ function safePush(buffer, val, time) {
139
143
  if (buffer.length > 36000) buffer.shift();
140
144
  }
141
145
 
146
+ /**
147
+ * Media Circolare Vettoriale - Versione "Soft Outlier Rejection"
148
+ * Riduce l'impatto degli sbalzi limitando il loro angolo massimo di discostamento.
149
+ */
142
150
  function getCircularAverageFromBuffer(bufferArray, windowMs, signed = false, now) {
143
151
  now = now || Date.now();
144
-
145
152
  const len = bufferArray.length;
146
153
  if (len === 0) return null;
147
154
 
148
- let sSin = 0;
149
- let sCos = 0;
150
- let count = 0;
155
+ let sSin = 0, sCos = 0, count = 0;
156
+ let newestTime = 0, oldestTime = 0;
157
+
158
+ // 1. MEDIA PILOTA: Guardiamo l'ultima frazione di dati (es. max 15 campioni) per sapere dove punta "ora"
159
+ let pilotSin = 0, pilotCos = 0;
160
+ const pilotSamples = Math.min(len, 15);
161
+ for (let i = len - 1; i >= len - pilotSamples; i--) {
162
+ pilotSin += bufferArray[i].sin;
163
+ pilotCos += bufferArray[i].cos;
164
+ }
165
+ const pilotRad = Math.atan2(pilotSin, pilotCos);
151
166
 
152
- let newestTime = 0;
153
- let oldestTime = 0;
167
+ // Il limite elastico in radianti (basato sul tuo stabilityBreakout in gradi)
168
+ const limitRad = (CONFIG.averaging.stabilityBreakout || 15) * (Math.PI / 180);
154
169
 
170
+ // 2. CALCOLO AMMORTIZZATO
155
171
  for (let i = len - 1; i >= 0; i--) {
156
172
  const item = bufferArray[i];
157
-
158
173
  if ((now - item.time) > windowMs) break;
159
174
 
160
- sSin += item.sin;
161
- sCos += item.cos;
175
+ // Troviamo la differenza angolare (da -Pi a +Pi) tra il dato e la Media Pilota
176
+ let diffRad = Math.atan2(
177
+ Math.sin(item.val - pilotRad),
178
+ Math.cos(item.val - pilotRad)
179
+ );
180
+
181
+ let finalSin, finalCos;
182
+
183
+ // Se lo scarto è maggiore del limite, "Pattiniamo" (Ammortizzazione)
184
+ if (Math.abs(diffRad) > limitRad) {
185
+ // Tronchiamo la differenza al limite massimo consentito (mantenendo il segno)
186
+ const clampedDiff = Math.sign(diffRad) * limitRad;
187
+ // Ricalcoliamo l'angolo ammortizzato
188
+ const clampedRad = pilotRad + clampedDiff;
189
+
190
+ finalSin = Math.sin(clampedRad);
191
+ finalCos = Math.cos(clampedRad);
192
+ } else {
193
+ // Il dato è buono, usiamo i valori precalcolati
194
+ finalSin = item.sin;
195
+ finalCos = item.cos;
196
+ }
197
+
198
+ sSin += finalSin;
199
+ sCos += finalCos;
162
200
 
163
201
  if (count === 0) newestTime = item.time;
164
202
  oldestTime = item.time;
165
-
166
203
  count++;
167
204
  }
168
205
 
169
206
  if (count === 0) return null;
170
207
 
171
208
  const R = Math.hypot(sSin, sCos) / count;
172
-
173
209
  const avgRad = Math.atan2(sSin, sCos);
174
-
175
- const finalVal = signed
176
- ? avgRad
177
- : (avgRad + Math.PI * 2) % (Math.PI * 2);
178
-
179
- const historyDuration =
180
- (count > 2)
181
- ? (newestTime - oldestTime)
182
- : 0;
183
-
184
- const isStable =
185
- historyDuration > 10000 &&
186
- R > CONFIG.averaging.stabilityThreshold;
187
-
210
+ const finalVal = signed ? avgRad : (avgRad + Math.PI * 2) % (Math.PI * 2);
211
+ const historyDuration = (count > 2) ? (newestTime - oldestTime) : 0;
188
212
  const safeR = Math.max(R, 1e-9);
189
213
 
190
214
  return {
191
215
  val: finalVal,
192
- stable: isStable,
193
- dev: (R < 1)
194
- ? Math.round(
195
- Math.sqrt(-2 * Math.log(safeR)) *
196
- (180 / Math.PI)
197
- )
198
- : 0,
216
+ stable: historyDuration > 10000 && R > CONFIG.averaging.stabilityThreshold,
217
+ dev: (R < 1) ? Math.round(Math.sqrt(-2 * Math.log(safeR)) * (180 / Math.PI)) : 0,
199
218
  samples: count
200
219
  };
201
220
  }
@@ -513,8 +532,9 @@ function refreshGraph(t) {
513
532
  * upUI: Aggiornamento valori digitali con logica anti-ritardo (Istantaneo vs Media)
514
533
  */
515
534
  const upUI = (el, obj, instantRaw, isCompass = false) => {
516
- if (!obj || obj.val === null || instantRaw === undefined) {
517
- el.innerHTML = "---&deg;"; el.classList.remove('unstable-data');
535
+ if (!obj || obj.val === null || isNaN(obj.val) || instantRaw === undefined) {
536
+ el.innerHTML = "---&deg;";
537
+ el.classList.remove('unstable-data');
518
538
  } else {
519
539
  let valDeg = Math.round(radToDeg(obj.val));
520
540
  let mainVal = (isCompass ? ((valDeg + 360) % 360).toString().padStart(3, '0') : valDeg) + "&deg;";
@@ -693,7 +713,7 @@ function startDisplayLoop() {
693
713
  // 8. CONFIGURAZIONE E GRAFICI UTILS
694
714
  // ==========================================================================
695
715
  /**
696
- * Recupera la configurazione e forza la sovrascrittura di ogni parametro.
716
+ * Recupera la configurazione dal server e applica migrazioni automatiche per le vecchie versioni
697
717
  */
698
718
  async function fetchServerConfig() {
699
719
  try {
@@ -704,19 +724,27 @@ async function fetchServerConfig() {
704
724
  // Stampa di debug per verificare cosa riceve il client
705
725
  console.log("🔍 Configurazione ricevuta dal Server:", data);
706
726
 
707
- // Merge intelligente dei dati ricevuti nel CONFIG esistente
727
+ // Merge intelligente dei dati ricevuti
708
728
  Object.assign(CONFIG.alarms, data.alarms || {});
709
729
  Object.assign(CONFIG.graphs, data.graphs || {});
710
730
  Object.assign(CONFIG.averaging, data.averaging || {});
711
731
 
712
- // Per le scale, siccome sono nidificate, facciamo un loop
732
+ // --- LOGICA DI MIGRAZIONE SILENZIOSA ---
733
+ // Se il valore ricevuto è il vecchio default (0.85) o inferiore, lo portiamo al nuovo standard 0.95.
734
+ // Questo è necessario perché con i nuovi filtri "Soft" lo 0.85 non farebbe quasi mai lampeggiare gli allarmi.
735
+ if (CONFIG.averaging.stabilityThreshold <= 0.85) {
736
+ CONFIG.averaging.stabilityThreshold = 0.95;
737
+ console.log("♻️ Migrazione Silenziosa: Rilevato vecchio parametro stabilità (<= 0.85). Aggiornato a 0.95 per ottimizzazione filtri.");
738
+ }
739
+
740
+ // Per le scale, siccome sono nidificate, facciamo un loop di merge profondo
713
741
  if (data.scales) {
714
742
  for (let key in data.scales) {
715
743
  if (CONFIG.scales[key]) Object.assign(CONFIG.scales[key], data.scales[key]);
716
744
  }
717
745
  }
718
746
 
719
- console.log("✅ Configurazione applicata. Alarmi attivi:", CONFIG.alarms);
747
+ console.log("✅ Configurazione applicata. Stabilità attiva:", CONFIG.averaging.stabilityThreshold);
720
748
  } catch (err) {
721
749
  console.warn("⚠️ Utilizzo default locali. Motivo:", err.message);
722
750
  }
package/index.js CHANGED
@@ -127,10 +127,10 @@ module.exports = function (app) {
127
127
  default: 0.5
128
128
  },
129
129
  stabilityThreshold: {
130
- type: 'number',
131
- title: 'Steering Precision (Sensitivity)',
132
- description: "Controls how strictly the system judges your course coherence. 0.95 requires pro precision; 0.85 is more realistic for cruising in waves.",
133
- default: 0.85
130
+ type: 'number',
131
+ title: 'Steering Precision (Sensitivity)',
132
+ description: "How strictly the system judges data coherence (0.0 to 1.0). Due to internal smoothing, 0.97-0.98 requires racing precision; 0.93-0.95 is ideal for cruising in waves. Below this, the display rarely alerts for instability.",
133
+ default: 0.95
134
134
  },
135
135
  stabilityBreakout: {
136
136
  type: 'number',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sailingrotevista/rotevista-dash",
3
- "version": "4.0.20",
3
+ "version": "4.0.21",
4
4
  "description": "Public Wind Dashboard with navigation and course aids",
5
5
  "main": "index.js",
6
6
  "publishConfig": {
package/settings.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "alarms": {
3
3
  "depthDanger": 2.5,
4
- "depthWarning": 5.0
4
+ "depthWarning": 3.5
5
5
  },
6
6
  "graphs": {
7
7
  "reef1": 15.0,
8
8
  "reef2": 20.0,
9
- "historyMinutes": 5
9
+ "historyMinutes": 30
10
10
  },
11
11
  "averaging": {
12
12
  "longWindow": 30000,
13
13
  "smoothWindow": 2000,
14
14
  "minSpeed": 0.5,
15
- "stabilityThreshold": 0.85,
15
+ "stabilityThreshold": 0.95,
16
16
  "stabilityBreakout": 15
17
17
  },
18
18
  "scales": {
package/style.css CHANGED
@@ -175,7 +175,7 @@ body.night-mode .alarm-danger {
175
175
 
176
176
  .focus-active .is-focused .sparkline path,
177
177
  .focus-active .is-focused .sparkline line {
178
- stroke-width: 1.5px !important;
178
+ stroke-width: 1.5px !important; /* Un briciolo più spessa per la stabilità visiva su schermi grandi */
179
179
  }
180
180
 
181
181
  .focus-active.focus-side-left {
@@ -441,10 +441,11 @@ body.night-mode {
441
441
 
442
442
  /* --- 10.3 GRAFICI (STILE DINAMICO NIGHT MODE) --- */
443
443
  .night-mode .graph-wrapper {
444
- background: rgba(10, 0, 0, 0.8) !important;
444
+ background: rgba(10, 0, 0, 0.8) !important; /* Sfondo scurissimo per alto contrasto */
445
445
  border: 1px solid #330000;
446
446
  }
447
447
 
448
+ /* Griglia temporale (minuti e livelli): quasi impercettibile */
448
449
  .night-mode .sparkline line[stroke="rgba(0,0,0,0.12)"],
449
450
  .night-mode .sparkline line[stroke="rgba(0,0,0,0.08)"] {
450
451
  stroke: rgba(255, 0, 0, 0.1) !important; /* Griglia temporale rossa soffusa */