@sailingrotevista/rotevista-dash 6.1.1 → 6.1.3

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 (3) hide show
  1. package/app.js +79 -34
  2. package/index.js +46 -26
  3. package/package.json +1 -1
package/app.js CHANGED
@@ -128,6 +128,7 @@ function safeSetText(el, text) {
128
128
 
129
129
  /**
130
130
  * Inserimento sicuro nel buffer con trigonometria precaricata e pruning automatico
131
+ * Mantiene sempre almeno 60 minuti di memoria locale per il calcolo della bussola meteo.
131
132
  */
132
133
  function safePush(buffer, val, time) {
133
134
  if (val === null || val === undefined || isNaN(val)) return;
@@ -139,7 +140,11 @@ function safePush(buffer, val, time) {
139
140
  cos: Math.cos(val)
140
141
  });
141
142
 
142
- const maxHistoryMs = (CONFIG.graphs.historyMinutes * 60000 * 2) + 60000;
143
+ // Se stiamo spingendo nel buffer TWD, conserviamo sempre almeno 60 minuti in memoria locale
144
+ const isTwdBuffer = (buffer === store.longBuf.twd);
145
+ const limitMinutes = isTwdBuffer ? 60 : CONFIG.graphs.historyMinutes;
146
+ const maxHistoryMs = (limitMinutes * 60000 * 2) + 60000;
147
+
143
148
  while (buffer.length > 0 && (time - buffer[0].time) > maxHistoryMs) {
144
149
  buffer.shift();
145
150
  }
@@ -312,34 +317,61 @@ function updateLeewayDisplay(deg) {
312
317
  ui.leewayVal.textContent = `LEEWAY: ${deg.toFixed(1)}°`;
313
318
  }
314
319
 
315
- // ==========================================================================
316
- // 5. MOTORE DI CALCOLO VENTO E DATA ROUTING
317
- // ==========================================================================
320
+ /**
321
+ * computeTrueWind: Calcola TWS, TWA e TWD strategico.
322
+ * Gestisce il fallback separato (Split-Fallback) in caso di dati parzialmente nativi di bordo.
323
+ */
318
324
  function computeTrueWind() {
319
325
  const aws = store.raw["environment.wind.speedApparent"], awa = store.raw["environment.wind.angleApparent"];
320
326
  const stw = store.raw["navigation.speedThroughWater"] || 0, sog = store.raw["navigation.speedOverGround"] || 0;
321
327
  const hdg = store.raw["navigation.headingTrue"] || 0, cog = store.raw["navigation.courseOverGroundTrue"] || 0;
322
328
  if (aws === undefined || awa === undefined) return;
323
329
 
324
- const tw_water_x = aws * Math.cos(awa) - stw, tw_water_y = aws * Math.sin(awa);
325
- const tws_water = Math.sqrt(tw_water_x * tw_water_x + tw_water_y * tw_water_y);
326
-
327
- const drift = (cog - hdg + Math.PI * 3) % (2 * Math.PI) - Math.PI;
328
- const tw_ground_x = aws * Math.cos(awa) - sog * Math.cos(drift), tw_ground_y = aws * Math.sin(awa) - sog * Math.sin(drift);
329
- const tws_ground = Math.sqrt(tw_ground_x * tw_ground_x + tw_ground_y * tw_ground_y);
330
-
331
330
  // Usiamo il tempo esatto di arrivo del pacchetto del vento per la coerenza dei buffer
332
331
  const now = store.timestamps["environment.wind.speedApparent"] || Date.now();
333
- store.raw["environment.wind.speedTrue"] = tws_water;
334
- if (tws_water > 0.05) {
335
- const twa = Math.atan2(tw_water_y, tw_water_x);
336
- store.raw["environment.wind.angleTrueWater"] = twa;
337
- safePush(store.smoothBuf.twa, twa, now); safePush(store.longBuf.twa, twa, now);
332
+
333
+ // ==========================================================================
334
+ // 1. GESTIONE TWS (NATIVO vs FALLBACK)
335
+ // ==========================================================================
336
+ const hasNativeTws = store.timestamps["environment.wind.speedTrue"] && (Date.now() - store.timestamps["environment.wind.speedTrue"] < 5000);
337
+ let tws_water = 0;
338
+
339
+ if (hasNativeTws) {
340
+ // Se la barca invia il TWS nativo, usiamo direttamente quello
341
+ tws_water = store.raw["environment.wind.speedTrue"] ? msToKts(store.raw["environment.wind.speedTrue"]) : 0;
342
+ } else {
343
+ // Altrimenti eseguiamo il calcolo vettoriale tattico sull'acqua
344
+ tws_water = Math.sqrt(aws * aws + stw * stw - 2 * aws * stw * Math.cos(awa));
345
+ store.raw["environment.wind.speedTrue"] = tws_water;
346
+
347
+ if (tws_water > 0.05) {
348
+ const twa = Math.atan2(aws * Math.sin(awa), aws * Math.cos(awa) - stw);
349
+ store.raw["environment.wind.angleTrueWater"] = twa;
350
+ safePush(store.smoothBuf.twa, twa, now);
351
+ safePush(store.longBuf.twa, twa, now);
352
+ }
338
353
  }
339
- if (tws_ground > 0.05) {
340
- let twd = (hdg + Math.atan2(tw_ground_y, tw_ground_x) + 2 * Math.PI) % (2 * Math.PI);
341
- store.raw["environment.wind.directionTrue"] = twd;
342
- safePush(store.smoothBuf.twd, twd, now); safePush(store.longBuf.twd, twd, now);
354
+
355
+ // ==========================================================================
356
+ // 2. GESTIONE TWD (NATIVO vs FALLBACK)
357
+ // ==========================================================================
358
+ const hasNativeTwd = store.timestamps["environment.wind.directionTrue"] && (Date.now() - store.timestamps["environment.wind.directionTrue"] < 5000);
359
+
360
+ if (hasNativeTwd) {
361
+ // Se il TWD è nativo della centralina, lo lasciamo scorrere passivamente
362
+ } else {
363
+ // Altrimenti calcoliamo lo scarroccio e ricaviamo il TWD geografico sul fondo
364
+ const drift = (cog - hdg + Math.PI * 3) % (2 * Math.PI) - Math.PI;
365
+ const tw_ground_x = aws * Math.cos(awa) - sog * Math.cos(drift);
366
+ const tw_ground_y = aws * Math.sin(awa) - sog * Math.sin(drift);
367
+ const tws_ground = Math.sqrt(tw_ground_x * tw_ground_x + tw_ground_y * tw_ground_y);
368
+
369
+ if (tws_ground > 0.05) {
370
+ let twd = (hdg + Math.atan2(tw_ground_y, tw_ground_x) + 2 * Math.PI) % (2 * Math.PI);
371
+ store.raw["environment.wind.directionTrue"] = twd;
372
+ safePush(store.smoothBuf.twd, twd, now);
373
+ safePush(store.longBuf.twd, twd, now); // Alimenta la bussola meteo strategica!
374
+ }
343
375
  }
344
376
  }
345
377
 
@@ -436,16 +468,20 @@ function processIncomingData(path, val, source, timeMs) {
436
468
 
437
469
  // ==========================================================================
438
470
  // 6. TREND VENTO (Tattico 2s vs 10s | Strategico 1m vs 10m)
471
+ // Sincronizzato sul tempo reale dei sensori per eliminare i conflitti di orologio.
439
472
  // ==========================================================================
440
473
  function updateWindTrend() {
441
- const now = Date.now();
442
-
443
- // --- 6.1 TREND TATTICO (AWA/TWA) ---
444
- const twaNow = getCircularAverageFromBuffer(store.longBuf.twa, 2000, true);
445
- const twaRef = getCircularAverageFromBuffer(store.longBuf.twa, 10000, true);
474
+ // --- RISOLUZIONE CLOCK DRIFT SUI PALLINI METEO ---
475
+ // Usiamo il tempo del sensore (l'ultimo dato del TWD in memoria) invece dell'orologio del tablet
476
+ const latestTwdPoint = store.longBuf.twd.length > 0 ? store.longBuf.twd[store.longBuf.twd.length - 1] : null;
477
+ const now = latestTwdPoint ? latestTwdPoint.time : Date.now();
478
+
479
+ // --- 6.1 TREND TATTICO (AWA/TWA Sintonizzato) ---
480
+ const twaNow = getCircularAverageFromBuffer(store.longBuf.twa, 2000, true, now);
481
+ const twaRef = getCircularAverageFromBuffer(store.longBuf.twa, 10000, true, now);
446
482
  const gaugeDots = { cw: document.getElementById('trend-gauge-cw'), ccw: document.getElementById('trend-gauge-ccw') };
447
483
 
448
- // --- 6.2 ALLARME STRAMBATA CON ISTERESI (MACCHINA A STATI ANTI-BRANDEGGIO) ---
484
+ // --- 6.2 ALLARME STRAMBATA CON ISTERESI (MACCHINA A STATI ANTI-BRANDEGGIO Sintonizzato) ---
449
485
  const instTwaRad = store.raw["environment.wind.angleTrueWater"];
450
486
  let smoothedTwaDeg = null;
451
487
 
@@ -530,11 +566,15 @@ function updateWindTrend() {
530
566
  }
531
567
  }
532
568
 
533
- // --- 6.3 TREND METEO STRATEGICO (TWD) ---
534
- const twdNow = getCircularAverageFromBuffer(store.longBuf.twd, 60000, false);
535
- const multiplier = isNavigating ? 1 : 2;
536
- const strategicWindowMs = CONFIG.graphs.historyMinutes * 60000 * multiplier;
537
- const twdRef = getCircularAverageFromBuffer(store.longBuf.twd, strategicWindowMs, false);
569
+ // --- 6.3 TREND METEO STRATEGICO (TWD PARAMETRIZZATO E SINTONIZZATO) ---
570
+ const twdNow = getCircularAverageFromBuffer(store.longBuf.twd, 60000, false, now); // 1 minuto fisso
571
+
572
+ // Parametrizzazione rigida nel codice: 15 minuti in navigazione, 60 minuti all'ancora
573
+ const fixedTwdMinutes = isNavigating ? 15 : 60;
574
+ const strategicWindowMs = fixedTwdMinutes * 60000;
575
+
576
+ const twdRef = getCircularAverageFromBuffer(store.longBuf.twd, strategicWindowMs, false, now);
577
+
538
578
  const compassDots = { cw: document.getElementById('trend-dot-cw'), ccw: document.getElementById('trend-dot-ccw') };
539
579
 
540
580
  if (twdNow && twdRef) {
@@ -894,11 +934,16 @@ async function fetchServerHistory() {
894
934
  }
895
935
  }
896
936
  // --- SILLABAZIONE STRATEGICA DELLA BUSSOLA METEO (TWD) ---
897
- // Se il server ci invia lo storico del TWD, lo inseriamo direttamente nella memoria a lungo termine
937
+ // Se il server ci invia lo storico del TWD, lo inseriamo calcolando i seni e coseni per i vettori
898
938
  if (data.twd && data.twd.length > 0) {
899
- store.longBuf.twd = data.twd;
939
+ store.longBuf.twd = data.twd.map(p => ({
940
+ val: p.val,
941
+ time: p.time,
942
+ sin: Math.sin(p.val),
943
+ cos: Math.cos(p.val)
944
+ }));
900
945
  console.log(`📈 Memoria strategica TWD sincronizzata dal server (${data.twd.length} punti).`);
901
- }
946
+ }
902
947
  console.log("📈 Storico dei grafici pre-popolato caricato dal server.");
903
948
  }
904
949
  } catch (err) {
package/index.js CHANGED
@@ -21,6 +21,9 @@ module.exports = function (app) {
21
21
  let graphTempBuf = { stw: [], sog: [], depth: [], tws: [], vmg: [], aws: [], twd: [] };
22
22
  let lastUpdates = { stw: 0, sog: 0, depth: 0, tws: 0, vmg: 0, aws: 0, twd: 0 };
23
23
  let raw = {};
24
+ // Memoria temporale per rilevare la presenza di sensori nativi sul Cerbo
25
+ let lastNativeTwsTime = 0;
26
+ let lastNativeTwdTime = 0;
24
27
 
25
28
  /**
26
29
  * plugin.start: Inizializza il plugin.
@@ -98,14 +101,17 @@ module.exports = function (app) {
98
101
  app.debug(msg);
99
102
  };
100
103
 
101
- /**
104
+ /**
102
105
  * processIncomingDelta: Decodifica i dati dei sensori in Knots/Meters ed esegue l'aggregazione
106
+ * Gestisce l'architettura "Nativo Prima, Fallback Dopo" per il vento reale.
103
107
  */
104
108
  function processIncomingDelta(path, val) {
105
109
  if (val === null || val === undefined) return;
106
110
  raw[path] = val;
107
111
 
108
- // Elaborazione e invio alla macchina a stati temporali dello storico
112
+ const now = Date.now();
113
+
114
+ // 1. Cattura dei dati nativi (Se presenti, li scrive direttamente nello storico)
109
115
  if (path === 'navigation.speedThroughWater') {
110
116
  manageHistory('stw', val * 1.94384);
111
117
  }
@@ -118,8 +124,16 @@ module.exports = function (app) {
118
124
  else if (path === 'environment.wind.speedApparent') {
119
125
  manageHistory('aws', val * 1.94384);
120
126
  }
127
+ else if (path === 'environment.wind.speedTrue') {
128
+ lastNativeTwsTime = now; // Rilevato TWS nativo della centralina!
129
+ manageHistory('tws', val * 1.94384);
130
+ }
131
+ else if (path === 'environment.wind.directionTrue') {
132
+ lastNativeTwdTime = now; // Rilevato TWD nativo della centralina!
133
+ manageHistory('twd', val);
134
+ }
121
135
 
122
- // Calcolo combinato del Vento Reale (TWS) e della VMG a livello Server
136
+ // 2. Calcolo combinato di FALLBACK (Si attiva solo se la centralina non invia TWS/TWD nativi)
123
137
  const aws = raw["environment.wind.speedApparent"];
124
138
  const awa = raw["environment.wind.angleApparent"];
125
139
  const stw = raw["navigation.speedThroughWater"] || 0;
@@ -128,25 +142,29 @@ module.exports = function (app) {
128
142
  const cog = raw["navigation.courseOverGroundTrue"] || 0;
129
143
 
130
144
  if (aws !== undefined && awa !== undefined) {
131
- const awsKts = aws * 1.94384;
132
- const stwKts = stw * 1.94384;
133
- const tw_water_x = awsKts * Math.cos(awa) - stwKts;
134
- const tw_water_y = awsKts * Math.sin(awa);
135
- const tws = Math.sqrt(tw_water_x * tw_water_x + tw_water_y * tw_water_y);
145
+ const awsKts = aws * 1.94384;
146
+ const stwKts = stw * 1.94384;
147
+ const tw_water_x = awsKts * Math.cos(awa) - stwKts;
148
+ const tw_water_y = awsKts * Math.sin(awa);
136
149
 
150
+ // Calcoliamo il TWS di fallback solo se non abbiamo visto dati nativi negli ultimi 5 secondi
151
+ if (now - lastNativeTwsTime > 5000) {
152
+ const tws = Math.sqrt(tw_water_x * tw_water_x + tw_water_y * tw_water_y);
137
153
  manageHistory('tws', tws);
154
+ }
138
155
 
139
- const twa = Math.atan2(tw_water_y, tw_water_x);
140
- const vmg = Math.abs(stwKts * Math.cos(twa));
141
- manageHistory('vmg', vmg);
142
-
143
- // --- CALCOLO TWD STRATEGICO SERVER-SIDE ---
144
- if (hdg !== undefined) {
145
- // Calcolo della direzione del vento reale rispetto al nord (TWD) in radianti
146
- const twd = (hdg + twa + 2 * Math.PI) % (2 * Math.PI);
147
- manageHistory('twd', twd);
148
- }
156
+ const twa = Math.atan2(tw_water_y, tw_water_x);
157
+
158
+ // La VMG viene sempre calcolata a livello server poiché raramente è nativa
159
+ const vmg = Math.abs(stwKts * Math.cos(twa));
160
+ manageHistory('vmg', vmg);
161
+
162
+ // Calcoliamo il TWD di fallback solo se non abbiamo visto dati nativi negli ultimi 5 secondi
163
+ if (hdg !== undefined && (now - lastNativeTwdTime > 5000)) {
164
+ const twd = (hdg + twa + 2 * Math.PI) % (2 * Math.PI);
165
+ manageHistory('twd', twd);
149
166
  }
167
+ }
150
168
  }
151
169
 
152
170
  /**
@@ -219,16 +237,18 @@ module.exports = function (app) {
219
237
  if (!isFinite(finalValue)) return;
220
238
  finalValue = Math.max(0, finalValue);
221
239
 
222
- // Salvataggio nel ring buffer dello storico principale
223
- histories[type].push({ val: finalValue, time: now });
240
+ // Salvataggio nel ring buffer dello storico principale
241
+ histories[type].push({ val: finalValue, time: now });
224
242
 
225
- // Pruning automatico basato sulle impostazioni di timeline
226
- const maxViewportMinutes = historyMinutes * 2;
227
- const maxHistoryMs = (maxViewportMinutes * 60000) + 60000;
243
+ // Pruning automatico basato sulle impostazioni di timeline
244
+ // (Forziamo il server a conservare sempre almeno 60 minuti per il TWD!)
245
+ const limitMinutes = (type === 'twd') ? 60 : historyMinutes;
246
+ const maxViewportMinutes = limitMinutes * 2;
247
+ const maxHistoryMs = (maxViewportMinutes * 60000) + 60000;
228
248
 
229
- while (histories[type].length > 0 && (now - histories[type][0].time) > maxHistoryMs) {
230
- histories[type].shift();
231
- }
249
+ while (histories[type].length > 0 && (now - histories[type][0].time) > maxHistoryMs) {
250
+ histories[type].shift();
251
+ }
232
252
 
233
253
  graphTempBuf[type] = [];
234
254
  // Spostiamo il timer esattamente al confine del secchiello assoluto appena concluso
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sailingrotevista/rotevista-dash",
3
- "version": "6.1.1",
3
+ "version": "6.1.3",
4
4
  "description": "Wind Dashboard with navigation and course aids",
5
5
  "main": "index.js",
6
6
  "publishConfig": {