@sailingrotevista/rotevista-dash 7.0.22 → 7.0.23

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 (4) hide show
  1. package/app.js +142 -52
  2. package/charts.js +36 -10
  3. package/index.js +16 -8
  4. package/package.json +1 -1
package/app.js CHANGED
@@ -216,16 +216,25 @@ function checkDepthAlarm(m) {
216
216
  function computeTrueWind() {
217
217
  const aws = store.raw["environment.wind.speedApparent"], awa = store.raw["environment.wind.angleApparent"];
218
218
  const stw = store.raw["navigation.speedThroughWater"], sog = store.raw["navigation.speedOverGround"] || 0;
219
- const hdg = store.raw["navigation.headingTrue"] || 0, cog = store.raw["navigation.courseOverGroundTrue"] || 0;
219
+ const hdg = store.raw["navigation.headingTrue"] || 0; // Il COG qui non serve più per il calcolo stabile
220
220
  if (aws === undefined || awa === undefined) return;
221
221
 
222
- // Usiamo il tempo esatto di arrivo del pacchetto del vento per la coerenza dei buffer
223
222
  const now = store.timestamps["environment.wind.speedApparent"] || Date.now();
224
223
 
225
- // Verifica se lo STW fisicamente attivo ha trasmesso dati negli ultimi 15 secondi
226
- const hasStw = store.timestamps["navigation.speedThroughWater"] && (now - store.timestamps["navigation.speedThroughWater"] < 15000);
227
- // Se lo STW non è disponibile, usa automaticamente la velocità del GPS (SOG) come riferimento
228
- const speedRef = hasStw && stw !== undefined ? stw : sog;
224
+ // RILEVAMENTO "LOG BLOCCATO" (Fouled Paddlewheel)
225
+ const hasFreshStw = store.timestamps["navigation.speedThroughWater"] && (now - store.timestamps["navigation.speedThroughWater"] < 15000);
226
+ let speedRef = 0;
227
+
228
+ if (hasFreshStw && stw !== undefined) {
229
+ // Se la barca naviga a GPS (> 1.5 nodi) ma l'elichetta legge quasi zero (< 0.5 nodi), il log è sporco/bloccato.
230
+ if (sog > 0.77 && stw < 0.25) {
231
+ speedRef = sog; // Forza il backup su SOG ignorando lo "0" fittizio del log
232
+ } else {
233
+ speedRef = stw;
234
+ }
235
+ } else {
236
+ speedRef = sog;
237
+ }
229
238
 
230
239
  // ==========================================================================
231
240
  // 1. GESTIONE TWS (NATIVO vs FALLBACK)
@@ -234,47 +243,51 @@ function computeTrueWind() {
234
243
  let tws_water = 0;
235
244
 
236
245
  if (hasNativeTws) {
237
- // Se la barca invia il TWS nativo, usiamo direttamente quello
238
246
  tws_water = store.raw["environment.wind.speedTrue"] ? msToKts(store.raw["environment.wind.speedTrue"]) : 0;
239
247
  } else {
240
- // Altrimenti eseguiamo il calcolo vettoriale tattico utilizzando la velocità di riferimento (STW o SOG)
241
248
  tws_water = Math.sqrt(aws * aws + speedRef * speedRef - 2 * aws * speedRef * Math.cos(awa));
242
249
  store.raw["environment.wind.speedTrue"] = tws_water;
243
250
  }
244
251
 
245
- // BUG RISOLTO: Calcoliamo il TWA sempre, indipendentemente dal TWS nativo!
246
- if (tws_water > 0.05) {
247
- const twa = Math.atan2(aws * Math.sin(awa), aws * Math.cos(awa) - speedRef);
248
- store.raw["environment.wind.angleTrueWater"] = twa;
249
-
250
- // Inserimento atomico e sincronizzato di TWA e AWA nei relativi buffer mobili
251
- // per garantire che le medie vettoriali siano perfettamente allineate in fase
252
+ let twa = 0;
253
+ // Verifica se riceviamo un TWA nativo valido ed efficiente negli ultimi 5 secondi
254
+ const hasNativeTwa = store.timestamps["environment.wind.angleTrueWater"] && (Date.now() - store.timestamps["environment.wind.angleTrueWater"] < 5000);
255
+
256
+ if (hasNativeTwa && store.raw["environment.wind.angleTrueWater"] !== undefined) {
257
+ // "Native First": diamo la precedenza assoluta al valore calcolato dalla centralina
258
+ twa = store.raw["environment.wind.angleTrueWater"];
259
+ } else {
260
+ // "Fallback Second": se manca il dato nativo, eseguiamo il calcolo vettoriale a mano
261
+ if (tws_water > 0.05) {
262
+ twa = Math.atan2(aws * Math.sin(awa), aws * Math.cos(awa) - speedRef);
263
+ store.raw["environment.wind.angleTrueWater"] = twa;
264
+ }
265
+ }
266
+
267
+ // Salviamo e allineiamo i buffer delle medie se abbiamo un angolo valido (nativo o calcolato)
268
+ if (tws_water > 0.05 || hasNativeTwa) {
252
269
  safePush(store.smoothBuf.twa, twa, now);
253
270
  safePush(store.longBuf.twa, twa, now);
254
- safePush(store.smoothBuf.awa, awa, now);
255
- safePush(store.longBuf.awa, awa, now);
271
+
272
+ // Salviamo l'angolo apparente solo se il relativo sensore fisico è effettivamente attivo
273
+ if (awa !== undefined) {
274
+ safePush(store.smoothBuf.awa, awa, now);
275
+ safePush(store.longBuf.awa, awa, now);
276
+ }
256
277
  }
257
278
 
258
279
  // ==========================================================================
259
- // 2. GESTIONE TWD (NATIVO vs FALLBACK)
280
+ // 2. GESTIONE TWD (NATIVO vs FALLBACK MATEMATICO STABILE)
260
281
  // ==========================================================================
261
282
  const hasNativeTwd = store.timestamps["environment.wind.directionTrue"] && (Date.now() - store.timestamps["environment.wind.directionTrue"] < 5000);
262
283
 
263
- if (hasNativeTwd) {
264
- // Se il TWD è nativo della centralina, lo lasciamo scorrere passivamente
265
- } else {
266
- // Altrimenti calcoliamo lo scarroccio e ricaviamo il TWD geografico sul fondo
267
- const drift = (cog - hdg + Math.PI * 3) % (2 * Math.PI) - Math.PI;
268
- const tw_ground_x = aws * Math.cos(awa) - sog * Math.cos(drift);
269
- const tw_ground_y = aws * Math.sin(awa) - sog * Math.sin(drift);
270
- const tws_ground = Math.sqrt(tw_ground_x * tw_ground_x + tw_ground_y * tw_ground_y);
271
-
272
- if (tws_ground > 0.05) {
273
- let twd = (hdg + Math.atan2(tw_ground_y, tw_ground_x) + 2 * Math.PI) % (2 * Math.PI);
274
- store.raw["environment.wind.directionTrue"] = twd;
275
- safePush(store.smoothBuf.twd, twd, now);
276
- safePush(store.longBuf.twd, twd, now); // Alimenta la bussola meteo strategica!
277
- }
284
+ if (!hasNativeTwd && tws_water > 0.05) {
285
+ // Calcolo TWD stabile e immune dal rollio: Prua + TWA
286
+ // Elimina i sobbalzi legati al brandeggio del COG e dello scarroccio fasullo
287
+ let twd = (hdg + twa + 2 * Math.PI) % (2 * Math.PI);
288
+ store.raw["environment.wind.directionTrue"] = twd;
289
+ safePush(store.smoothBuf.twd, twd, now);
290
+ safePush(store.longBuf.twd, twd, now);
278
291
  }
279
292
  }
280
293
 
@@ -387,6 +400,12 @@ function processIncomingData(path, val, source, timeMs) {
387
400
  let speedVal = (val && typeof val === 'object' && val.val !== undefined) ? val.val : val;
388
401
  store.raw["environment.wind.speedTrue"] = speedVal;
389
402
  }
403
+
404
+ // INTERCETTAZIONE TWA NATIVO (Angolo Vento Reale pronto all'uso)
405
+ if (path === "environment.wind.angleTrueWater") {
406
+ let angleVal = (val && typeof val === 'object' && val.val !== undefined) ? val.val : val;
407
+ store.raw["environment.wind.angleTrueWater"] = angleVal;
408
+ }
390
409
 
391
410
  // --- GESTIONE PRUA VERA / MAGNETICA CON AUTODIVIAZIONE ---
392
411
  if (path === "navigation.headingTrue") {
@@ -665,7 +684,16 @@ function startDisplayLoop() {
665
684
 
666
685
  // --- LOGICA SOG / VMG ---
667
686
  if (store.raw["navigation.speedOverGround"] !== undefined) {
668
- const vmgVal = Math.abs(stwKts * Math.cos(store.raw["environment.wind.angleTrueWater"] || 0));
687
+ // RILEVAMENTO "LOG BLOCCATO" PER IL CALCOLO VMG
688
+ const hasFreshStw = store.timestamps["navigation.speedThroughWater"] && (now - store.timestamps["navigation.speedThroughWater"] < 15000);
689
+ let speedRefKts = sogKts; // Fallback predefinito su GPS
690
+
691
+ if (hasFreshStw) {
692
+ // Se viaggiamo a più di 1.5 nodi (GPS) ma il log segna meno di 0.5 nodi, usa il SOG come backup
693
+ speedRefKts = (sogKts > 1.5 && stwKts < 0.5) ? sogKts : stwKts;
694
+ }
695
+
696
+ const vmgVal = Math.abs(speedRefKts * Math.cos(store.raw["environment.wind.angleTrueWater"] || 0));
669
697
  manageHistory('vmg', vmgVal);
670
698
  manageHistory('sog', sogKts);
671
699
 
@@ -679,13 +707,22 @@ function startDisplayLoop() {
679
707
  if (labelSogVmg) labelSogVmg.textContent = 'SOG';
680
708
 
681
709
  if (isNavigating) {
682
- const lastSog = store.histories.sog.length > 0 ? store.histories.sog[store.histories.sog.length - 1].val : sogKts;
683
- const lastStw = store.histories.stw.length > 0 ? store.histories.stw[store.histories.stw.length - 1].val : stwKts;
684
- const drift = lastSog - lastStw;
685
-
686
- if (drift < -0.3) ui.sog.style.setProperty('color', '#ff3b30', 'important'); // Contro
687
- else if (drift > 0.3) ui.sog.style.setProperty('color', '#00C851', 'important'); // Favore
688
- else ui.sog.style.setProperty('color', '#ffbb33', 'important'); // Neutro SOG
710
+ const hasFreshStw = store.timestamps["navigation.speedThroughWater"] && (now - store.timestamps["navigation.speedThroughWater"] < 15000);
711
+ // Rileviamo se il log è plausibilmente sporco o bloccato (SOG in movimento, STW a zero)
712
+ const isFouledLog = (sogKts > 1.5 && stwKts < 0.5);
713
+
714
+ // Se non abbiamo un log affidabile, è impossibile calcolare la corrente. Evitiamo falsi colori tattici.
715
+ if (!hasFreshStw || isFouledLog) {
716
+ ui.sog.style.removeProperty('color');
717
+ } else {
718
+ const lastSog = store.histories.sog.length > 0 ? store.histories.sog[store.histories.sog.length - 1].val : sogKts;
719
+ const lastStw = store.histories.stw.length > 0 ? store.histories.stw[store.histories.stw.length - 1].val : stwKts;
720
+ const drift = lastSog - lastStw;
721
+
722
+ if (drift < -0.3) ui.sog.style.setProperty('color', '#ff3b30', 'important'); // Contro
723
+ else if (drift > 0.3) ui.sog.style.setProperty('color', '#00C851', 'important'); // Favore
724
+ else ui.sog.style.setProperty('color', '#ffbb33', 'important'); // Neutro SOG
725
+ }
689
726
  } else {
690
727
  ui.sog.style.removeProperty('color');
691
728
  }
@@ -902,23 +939,74 @@ async function fetchServerHistory() {
902
939
  const data = await response.json();
903
940
 
904
941
  if (data && typeof data === 'object') {
942
+ // 1. RILEVAMENTO DEL DELTA DI COERENZA TEMPORALE
943
+ // Troviamo il timestamp più recente in assoluto presente nei dati storici del server
944
+ let maxServerTime = 0;
945
+ for (let key in data) {
946
+ if (store.histories[key] !== undefined && Array.isArray(data[key]) && data[key].length > 0) {
947
+ const lastPoint = data[key][data[key].length - 1];
948
+ if (lastPoint && lastPoint.time > maxServerTime) {
949
+ maxServerTime = lastPoint.time;
950
+ }
951
+ }
952
+ }
953
+
954
+ // Calcoliamo lo sfasamento in millisecondi rispetto all'orologio del client (MacBook/Tablet)
955
+ const nowMac = Date.now();
956
+ const timeDelta = (maxServerTime > 0) ? (nowMac - maxServerTime) : 0;
957
+
958
+ if (Math.abs(timeDelta) > 500) {
959
+ console.log(`⏱️ [Clock Sync] Rilevato scostamento di ${(timeDelta/1000).toFixed(1)}s rispetto a einstein. Calibrazione attiva.`);
960
+ }
961
+
962
+ // 2. TRASLAZIONE DEI DATI STORICI
963
+ // Spostiamo nel tempo tutti i punti passati per incollarli millimetricamente al "now" del client
905
964
  for (let key in data) {
906
965
  if (store.histories[key] !== undefined) {
907
- store.histories[key] = data[key];
966
+ if (Array.isArray(data[key])) {
967
+ store.histories[key] = data[key].map(p => ({
968
+ val: p.val,
969
+ time: p.time + timeDelta, // Sposta il punto nel tempo per allinearlo al Mac
970
+ min: p.min !== undefined ? p.min : undefined,
971
+ max: p.max !== undefined ? p.max : undefined
972
+ }));
973
+ } else {
974
+ store.histories[key] = data[key];
975
+ }
908
976
  }
909
977
  }
910
- // Sincronizza i dati specifici del radar storici, previsionali e i buffer minuto per minuto
911
- if (data.windRadarSlots) store.windRadarSlots = data.windRadarSlots;
912
- if (data.futureForecast) store.futureForecast = data.futureForecast;
913
- if (data.twd) store.twdMinuteBuffer = data.twd;
914
- if (data.tws) store.twsMinuteBuffer = data.tws;
978
+
979
+ // Sincronizza i dati specifici del radar storici, previsionali e i buffer minuto per minuto (applicando il Delta)
980
+ if (data.windRadarSlots) {
981
+ store.windRadarSlots = data.windRadarSlots.map(s => ({
982
+ ...s,
983
+ timestamp: s.timestamp + timeDelta
984
+ }));
985
+ }
986
+ if (data.futureForecast) {
987
+ store.futureForecast = {
988
+ ...data.futureForecast,
989
+ timestamp: data.futureForecast.timestamp + timeDelta
990
+ };
991
+ }
992
+ if (data.twd) {
993
+ store.twdMinuteBuffer = data.twd.map(p => ({
994
+ ...p,
995
+ time: p.time + timeDelta
996
+ }));
997
+ }
998
+ if (data.tws) {
999
+ store.twsMinuteBuffer = data.tws.map(p => ({
1000
+ ...p,
1001
+ time: p.time + timeDelta
1002
+ }));
1003
+ }
915
1004
 
916
1005
  // --- SILLABAZIONE STRATEGICA DELLA BUSSOLA METEO (TWD) ---
917
- // Se il server ci invia lo storico del TWD, lo inseriamo calcolando i seni e coseni per i vettori
918
1006
  if (data.twd && data.twd.length > 0) {
919
1007
  store.longBuf.twd = data.twd.map(p => ({
920
1008
  val: p.val,
921
- time: p.time,
1009
+ time: p.time + timeDelta, // Allinea il tempo strategico del radar
922
1010
  sin: Math.sin(p.val),
923
1011
  cos: Math.cos(p.val)
924
1012
  }));
@@ -928,7 +1016,7 @@ async function fetchServerHistory() {
928
1016
  }
929
1017
  } catch (err) {
930
1018
  console.warn("⚠️ Impossibile caricare lo storico dal server. Utilizzo dati vuoti/simulati.");
931
- throw err; // Rilancia l'errore per far attivare il fallback nella funzione init()
1019
+ throw err;
932
1020
  }
933
1021
  }
934
1022
 
@@ -1176,6 +1264,7 @@ function connect() {
1176
1264
  { path: "environment.wind.speedApparent", minPeriod: 333 }, // AWS a 3 Hz
1177
1265
  { path: "environment.wind.angleApparent", minPeriod: 333 }, // AWA a 3 Hz
1178
1266
  { path: "environment.wind.speedTrue", minPeriod: 333 }, // TWS (Nativo) a 3 Hz
1267
+ { path: "environment.wind.angleTrueWater", minPeriod: 333 }, // TWA (Nativo) a 3 Hz (AGGIUNTO)
1179
1268
  { path: "environment.wind.directionTrue", minPeriod: 333 } // TWD (Nativo) a 3 Hz
1180
1269
  ]
1181
1270
  };
@@ -1331,11 +1420,12 @@ async function init() {
1331
1420
  startDisplayLoop();
1332
1421
  connect(); // Si collegherà in tempo reale al WebSocket reale della barca
1333
1422
 
1334
- // 5. POLL LENTO (15 secondi): aggiorna i dati radar in background e, se attivo, li ridisegna
1423
+ // 5. POLL LENTO (15 secondi): aggiorna i dati radar in background SOLO se lo strumento attivo è il radar,
1424
+ // evitando di sovraccaricare la rete ed eliminando il salto visivo di reset dei grafici a 1Hz
1335
1425
  setInterval(async () => {
1336
1426
  try {
1337
- await fetchServerHistory();
1338
1427
  if (activeInstrument === 'radar') {
1428
+ await fetchServerHistory();
1339
1429
  renderRadar();
1340
1430
  }
1341
1431
  } catch (err) {
package/charts.js CHANGED
@@ -19,7 +19,9 @@ function calculateScale(type, data, mode) {
19
19
 
20
20
  if (!store.herculesScales) store.herculesScales = {};
21
21
  if (!store.herculesScales[type]) {
22
- store.herculesScales[type] = { min: 0, max: s ? s.stdMax : 10 };
22
+ // Inizializzazione a NULL dello stato di partenza per consentire un allineamento istantaneo
23
+ // e perfettamente centrato al primo doppio clic
24
+ store.herculesScales[type] = { min: null, max: null };
23
25
  }
24
26
  let currentScale = store.herculesScales[type];
25
27
 
@@ -65,9 +67,10 @@ function calculateScale(type, data, mode) {
65
67
  targetMax = targetMin + 4;
66
68
  }
67
69
 
68
- if (currentVal < currentScale.min || currentVal > currentScale.max) {
69
- currentScale.min = Math.min(currentScale.min, targetMin);
70
- currentScale.max = Math.max(currentScale.max, targetMax);
70
+ // Gestione dell'allineamento iniziale o dello scostamento dei limiti della scala
71
+ if (currentScale.min === null || currentVal < currentScale.min || currentVal > currentScale.max) {
72
+ currentScale.min = currentScale.min === null ? targetMin : Math.min(currentScale.min, targetMin);
73
+ currentScale.max = currentScale.max === null ? targetMax : Math.max(currentScale.max, targetMax);
71
74
  } else {
72
75
  const allStableInTarget = recentVals.every(val => val >= targetMin && val <= targetMax);
73
76
  if (allStableInTarget) {
@@ -90,8 +93,25 @@ function calculateScale(type, data, mode) {
90
93
  const oneThirdCount = Math.max(1, Math.floor(data.length / 3));
91
94
  const recentThirdData = data.slice(-oneThirdCount);
92
95
 
93
- const localMin = Math.min(...recentThirdData);
94
- const localMax = Math.max(...recentThirdData);
96
+ let localMin = Math.min(...recentThirdData);
97
+ let localMax = Math.max(...recentThirdData);
98
+
99
+ // SMART MOTORING DETECTION (Specifica per i grafici di velocità: STW, SOG, VMG)
100
+ const isSpeedType = (type === 'stw' || type === 'sog' || type === 'vmg');
101
+ if (isSpeedType && data.length > oneThirdCount) {
102
+ const olderData = data.slice(0, data.length - oneThirdCount);
103
+ const olderMin = Math.min(...olderData);
104
+ const olderMax = Math.max(...olderData);
105
+ const olderRange = olderMax - olderMin; // Variazione nello storico passato
106
+
107
+ // Se lo storico passato non era piatto (range > 0.2 nodi) oppure eravamo fermi/all'ancora (< 1.5 nodi),
108
+ // significa che il passato è rilevante per il trend. Includiamo il passato nel calcolo della scala Y.
109
+ // Se invece era piatto (motore), lo ignoriamo per zoomare subito sulla vela attuale.
110
+ if (olderRange > 0.2 || olderMin < 0.77) {
111
+ localMin = Math.min(localMin, olderMin);
112
+ localMax = Math.max(localMax, olderMax);
113
+ }
114
+ }
95
115
 
96
116
  let targetMin = Math.max(0, Math.floor(localMin / roundStep) * roundStep);
97
117
  let targetMax = Math.ceil(localMax / roundStep) * roundStep;
@@ -100,9 +120,10 @@ function calculateScale(type, data, mode) {
100
120
  targetMax = targetMin + roundStep;
101
121
  }
102
122
 
103
- if (currentVal < currentScale.min || currentVal > currentScale.max) {
104
- currentScale.min = Math.min(currentScale.min, targetMin);
105
- currentScale.max = Math.max(currentScale.max, targetMax);
123
+ // Gestione dell'allineamento iniziale o dello scostamento dei limiti della scala
124
+ if (currentScale.min === null || currentVal < currentScale.min || currentVal > currentScale.max) {
125
+ currentScale.min = currentScale.min === null ? targetMin : Math.min(currentScale.min, targetMin);
126
+ currentScale.max = currentScale.max === null ? targetMax : Math.max(currentScale.max, targetMax);
106
127
  } else {
107
128
  const allStableInTarget = recentThirdData.every(val => val >= targetMin && val <= targetMax);
108
129
  if (allStableInTarget) {
@@ -256,7 +277,12 @@ function drawGraph(d, id, min, max, isTws, isHercules) {
256
277
  gradientStops += `<stop offset="${offset1}%" stop-color="${props.color}" stop-opacity="${props.opacity}" />`;
257
278
  gradientStops += `<stop offset="${offset2}%" stop-color="${props.color}" stop-opacity="${props.opacity}" />`;
258
279
 
259
- if (isGap) {
280
+ // CALCOLO ELASTICO DELL'INTERRUZIONE: Tollera buchi storici fino a 90 secondi (es. i 30s di einstein)
281
+ // per permettere di disegnare linee solide e continue anche se mostrate in una timeline corta e veloce
282
+ const maxAllowedGap = Math.max(expectedInterval * 2.5, 90000);
283
+ const isHistoryGap = deltaTime > maxAllowedGap;
284
+
285
+ if (isHistoryGap) {
260
286
  if (started) {
261
287
  areaPath += `L ${x1} ${h} `;
262
288
  started = false;
package/index.js CHANGED
@@ -277,32 +277,40 @@ module.exports = function (app) {
277
277
  const stw = raw["navigation.speedThroughWater"];
278
278
  const sog = raw["navigation.speedOverGround"] || 0;
279
279
  const hdg = raw["navigation.headingTrue"];
280
- const cog = raw["navigation.courseOverGroundTrue"] || 0;
281
280
 
282
281
  if (aws !== undefined && awa !== undefined) {
283
282
  const awsKts = aws * 1.94384;
284
283
 
285
- // Verifica se lo STW fisicamente attivo ha trasmesso dati negli ultimi 15 secondi
284
+ // RILEVAMENTO "LOG BLOCCATO" SUL SERVER
286
285
  const hasStw = lastPathProcessTimes["navigation.speedThroughWater"] && (now - lastPathProcessTimes["navigation.speedThroughWater"] < 15000);
287
- // Se lo STW non è disponibile, usa automaticamente la velocità del GPS (SOG) come riferimento
288
- const speedKtsRef = hasStw && stw !== undefined ? (stw * 1.94384) : (sog * 1.94384);
286
+ let speedRef = 0;
287
+
288
+ if (hasStw && stw !== undefined) {
289
+ if (sog > 0.77 && stw < 0.25) {
290
+ speedRef = sog; // Log sporco: forza il SOG
291
+ } else {
292
+ speedRef = stw;
293
+ }
294
+ } else {
295
+ speedRef = sog;
296
+ }
297
+
298
+ const speedKtsRef = speedRef * 1.94384;
289
299
 
290
300
  const tw_water_x = awsKts * Math.cos(awa) - speedKtsRef;
291
301
  const tw_water_y = awsKts * Math.sin(awa);
292
302
 
293
- // Calcoliamo il TWS di fallback solo se non abbiamo visto dati nativi negli ultimi 5 secondi
303
+ // Calcolo TWS
294
304
  if (now - lastNativeTwsTime > 5000) {
295
305
  const tws = Math.sqrt(tw_water_x * tw_water_x + tw_water_y * tw_water_y);
296
306
  manageHistory('tws', tws);
297
307
  }
298
308
 
299
309
  const twa = Math.atan2(tw_water_y, tw_water_x);
300
-
301
- // La VMG viene calcolata usando la velocità di riferimento (STW reale o SOG di fallback)
302
310
  const vmg = Math.abs(speedKtsRef * Math.cos(twa));
303
311
  manageHistory('vmg', vmg);
304
312
 
305
- // Calcoliamo il TWD di fallback solo se non abbiamo visto dati nativi negli ultimi 5 secondi
313
+ // Calcolo TWD stabile (Prua + TWA)
306
314
  if (hdg !== undefined && (now - lastNativeTwdTime > 5000)) {
307
315
  const twd = (hdg + twa + 2 * Math.PI) % (2 * Math.PI);
308
316
  manageHistory('twd', twd);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sailingrotevista/rotevista-dash",
3
- "version": "7.0.22",
3
+ "version": "7.0.23",
4
4
  "description": "Wind Dashboard with navigation and course aids",
5
5
  "main": "index.js",
6
6
  "publishConfig": {