@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.
- package/app.js +79 -34
- package/index.js +46 -26
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
const
|
|
445
|
-
|
|
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
|
-
|
|
536
|
-
|
|
537
|
-
const
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
223
|
-
|
|
240
|
+
// Salvataggio nel ring buffer dello storico principale
|
|
241
|
+
histories[type].push({ val: finalValue, time: now });
|
|
224
242
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
230
|
-
|
|
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
|