@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 +67 -39
- package/index.js +4 -4
- package/package.json +1 -1
- package/settings.json +3 -3
- package/style.css +3 -2
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.
|
|
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
|
|
150
|
-
|
|
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
|
-
|
|
153
|
-
|
|
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
|
-
|
|
161
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
535
|
+
if (!obj || obj.val === null || isNaN(obj.val) || instantRaw === undefined) {
|
|
536
|
+
el.innerHTML = "---°";
|
|
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) + "°";
|
|
@@ -693,7 +713,7 @@ function startDisplayLoop() {
|
|
|
693
713
|
// 8. CONFIGURAZIONE E GRAFICI UTILS
|
|
694
714
|
// ==========================================================================
|
|
695
715
|
/**
|
|
696
|
-
* Recupera la configurazione e
|
|
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
|
|
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
|
-
//
|
|
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.
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
package/settings.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"alarms": {
|
|
3
3
|
"depthDanger": 2.5,
|
|
4
|
-
"depthWarning": 5
|
|
4
|
+
"depthWarning": 3.5
|
|
5
5
|
},
|
|
6
6
|
"graphs": {
|
|
7
7
|
"reef1": 15.0,
|
|
8
8
|
"reef2": 20.0,
|
|
9
|
-
"historyMinutes":
|
|
9
|
+
"historyMinutes": 30
|
|
10
10
|
},
|
|
11
11
|
"averaging": {
|
|
12
12
|
"longWindow": 30000,
|
|
13
13
|
"smoothWindow": 2000,
|
|
14
14
|
"minSpeed": 0.5,
|
|
15
|
-
"stabilityThreshold": 0.
|
|
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 */
|