@sailingrotevista/rotevista-dash 7.0.15 → 7.0.17
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 +43 -29
- package/index.js +98 -68
- package/package.json +1 -1
package/app.js
CHANGED
|
@@ -23,7 +23,7 @@ let CONFIG = {
|
|
|
23
23
|
minSpeed: 0.5,
|
|
24
24
|
stabilityBreakout: 15
|
|
25
25
|
},
|
|
26
|
-
graphs: { reef1: 10, reef2: 15, historyMinutes:
|
|
26
|
+
graphs: { reef1: 10, reef2: 15, historyMinutes: 5, samples: 60 },
|
|
27
27
|
scales: {
|
|
28
28
|
stw: { stdMax: 4, hercSpan: 2, step: 1 },
|
|
29
29
|
sog: { stdMax: 4, hercSpan: 2, step: 1 },
|
|
@@ -964,11 +964,11 @@ function manageHistory(type, value) {
|
|
|
964
964
|
const bucketReady = (now - store.lastUpdates[type] > bucketIntervalMs) || store.histories[type].length === 0;
|
|
965
965
|
if (!bucketReady) return;
|
|
966
966
|
|
|
967
|
-
// --- 5. AGGREGAZIONE SEMANTICA ---
|
|
967
|
+
// --- 5. AGGREGAZIONE SEMANTICA (Ottimizzata a zero-allocazioni di array intermedi) ---
|
|
968
968
|
let finalValue = value;
|
|
969
969
|
|
|
970
970
|
if (tempBuf.length > 0) {
|
|
971
|
-
// A. VENTO -> SUSTAINED PEAK (EMA Time-Aware)
|
|
971
|
+
// A. VENTO -> SUSTAINED PEAK (EMA Time-Aware) (0 allocazioni)
|
|
972
972
|
if (type === 'tws' || type === 'aws') {
|
|
973
973
|
const tauMs = 2500;
|
|
974
974
|
let ema = tempBuf[0].val;
|
|
@@ -982,17 +982,32 @@ function manageHistory(type, value) {
|
|
|
982
982
|
}
|
|
983
983
|
finalValue = maxSustained;
|
|
984
984
|
}
|
|
985
|
-
// B. PROFONDITÀ -> MINIMO
|
|
985
|
+
// B. PROFONDITÀ -> MINIMO (In-place loop, 0 allocazioni)
|
|
986
986
|
else if (type === 'depth') {
|
|
987
|
-
|
|
988
|
-
|
|
987
|
+
let minVal = Infinity;
|
|
988
|
+
for (let i = 0; i < tempBuf.length; i++) {
|
|
989
|
+
const v = tempBuf[i].val;
|
|
990
|
+
if (isFinite(v) && v < minVal) {
|
|
991
|
+
minVal = v;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
if (minVal !== Infinity) {
|
|
995
|
+
finalValue = minVal;
|
|
996
|
+
}
|
|
989
997
|
}
|
|
990
|
-
// C. VELOCITÀ -> MEDIA
|
|
998
|
+
// C. VELOCITÀ -> MEDIA (In-place loop, 0 allocazioni)
|
|
991
999
|
else {
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1000
|
+
let sum = 0;
|
|
1001
|
+
let count = 0;
|
|
1002
|
+
for (let i = 0; i < tempBuf.length; i++) {
|
|
1003
|
+
const v = tempBuf[i].val;
|
|
1004
|
+
if (isFinite(v)) {
|
|
1005
|
+
sum += v;
|
|
1006
|
+
count++;
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
if (count > 0) {
|
|
1010
|
+
finalValue = sum / count;
|
|
996
1011
|
}
|
|
997
1012
|
}
|
|
998
1013
|
}
|
|
@@ -1201,9 +1216,15 @@ function connect() {
|
|
|
1201
1216
|
socket.onclose = () => {
|
|
1202
1217
|
if (!simulationMode) {
|
|
1203
1218
|
ui.status.className = "offline";
|
|
1204
|
-
ui.status.innerText = "
|
|
1205
|
-
|
|
1206
|
-
|
|
1219
|
+
ui.status.innerText = "OFFLINE";
|
|
1220
|
+
|
|
1221
|
+
// Tentiamo la riconnessione automatica solo se lo schermo è attivo e visibile,
|
|
1222
|
+
// evitando cicli di loop di rete infiniti in background mentre il tablet dorme
|
|
1223
|
+
if (document.visibilityState === "visible") {
|
|
1224
|
+
ui.status.innerText = "RECONNECTING...";
|
|
1225
|
+
setTimeout(connect, reconnectDelay);
|
|
1226
|
+
reconnectDelay = Math.min(reconnectDelay * 1.5, 10000);
|
|
1227
|
+
}
|
|
1207
1228
|
}
|
|
1208
1229
|
};
|
|
1209
1230
|
} catch (e) {
|
|
@@ -1319,24 +1340,17 @@ async function init() {
|
|
|
1319
1340
|
}
|
|
1320
1341
|
}
|
|
1321
1342
|
|
|
1322
|
-
// Watchdog per
|
|
1343
|
+
// Watchdog attivo per la gestione intelligente dello standby e il massimo risparmio energetico
|
|
1323
1344
|
document.addEventListener('visibilitychange', () => {
|
|
1324
1345
|
if (document.visibilityState === 'visible') {
|
|
1325
|
-
console.log("🔌 [Watchdog] Tab
|
|
1326
|
-
|
|
1327
|
-
|
|
1346
|
+
console.log("🔌 [Watchdog] Schermo sbloccato / Tab visibile. Riconnessione immediata e allineamento storico...");
|
|
1347
|
+
// Al risveglio stabiliamo una nuova connessione pulita che scaricherà lo storico accumulato in background dal server
|
|
1348
|
+
connect();
|
|
1349
|
+
} else if (document.visibilityState === 'hidden') {
|
|
1350
|
+
console.log("🔌 [Watchdog] Schermo bloccato / Tab in background. Chiusura WebSocket preventiva per salvaguardare la batteria.");
|
|
1351
|
+
// Tagliamo attivamente la connessione per congelare all'istante l'attività di rete ed i consumi del browser
|
|
1328
1352
|
if (socket) {
|
|
1329
|
-
|
|
1330
|
-
// Se era già chiuso o in errore, proviamo a riconnettere subito
|
|
1331
|
-
connect();
|
|
1332
|
-
} else {
|
|
1333
|
-
// Se resulta "OPEN" ma potrebbe essere una connessione fantasma,
|
|
1334
|
-
// la chiudiamo forzatamente per scatenare la riconnessione pulita e il download della cronologia
|
|
1335
|
-
console.log("🔌 [Watchdog] Riavvio precauzionale del WebSocket per evitare connessioni fantasma.");
|
|
1336
|
-
socket.close();
|
|
1337
|
-
}
|
|
1338
|
-
} else {
|
|
1339
|
-
connect();
|
|
1353
|
+
socket.close();
|
|
1340
1354
|
}
|
|
1341
1355
|
}
|
|
1342
1356
|
});
|
package/index.js
CHANGED
|
@@ -351,81 +351,111 @@ module.exports = function (app) {
|
|
|
351
351
|
let isTwdType = (type === 'twd');
|
|
352
352
|
|
|
353
353
|
if (tempBuf.length > 0) {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
const calmThresholdMs = CALM_THRESHOLD_KTS / 1.94384;
|
|
364
|
-
const pressureThreshold = Math.max(maxTwsInMinute * PRESSURE_FILTER_RATIO, calmThresholdMs);
|
|
365
|
-
|
|
366
|
-
// 3. Esclude le direzioni registrate quando il vento era debole (sotto la soglia di pressione)
|
|
367
|
-
const activePoints = tempBuf.filter(p => (p.tws || 0) >= pressureThreshold);
|
|
368
|
-
const pointsToUse = activePoints.length > 0 ? activePoints : tempBuf; // Fallback se tutto è calma piatta
|
|
369
|
-
|
|
370
|
-
// 4. Media Vettoriale Pesata (TWS * sin, TWS * cos)
|
|
371
|
-
let sumSin = 0;
|
|
372
|
-
let sumCos = 0;
|
|
373
|
-
let totalWeight = 0;
|
|
374
|
-
pointsToUse.forEach(p => {
|
|
375
|
-
const weight = Math.max(p.tws || 0.1, 0.05); // Evita pesi a zero
|
|
376
|
-
sumSin += weight * Math.sin(p.val);
|
|
377
|
-
sumCos += weight * Math.cos(p.val);
|
|
378
|
-
totalWeight += weight;
|
|
379
|
-
});
|
|
380
|
-
const avgAngle = Math.atan2(sumSin, sumCos);
|
|
381
|
-
const finalAvg = (avgAngle + Math.PI * 2) % (Math.PI * 2);
|
|
382
|
-
|
|
383
|
-
// 5. Calcolo di Min e Max angolare del minuto (rispetto alla media per gestire l'oltrepasso di 0/360°)
|
|
384
|
-
let diffs = pointsToUse.map(p => {
|
|
385
|
-
let diff = p.val - finalAvg;
|
|
386
|
-
return Math.atan2(Math.sin(diff), Math.cos(diff)); // Srotolamento tra -PI e +PI
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
const minDiff = Math.min(...diffs);
|
|
390
|
-
const maxDiff = Math.max(...diffs);
|
|
391
|
-
|
|
392
|
-
const finalMin = (finalAvg + minDiff + Math.PI * 2) % (Math.PI * 2);
|
|
393
|
-
const finalMax = (finalAvg + maxDiff + Math.PI * 2) % (Math.PI * 2);
|
|
394
|
-
|
|
395
|
-
finalValue = {
|
|
396
|
-
val: finalAvg,
|
|
397
|
-
min: finalMin,
|
|
398
|
-
max: finalMax
|
|
399
|
-
};
|
|
354
|
+
// ==========================================================================
|
|
355
|
+
// CASO PARTICOLARE: DIREZIONE VENTO (TWD) -> MEDIA PESATA E LIMITI MIN/MAX
|
|
356
|
+
// ==========================================================================
|
|
357
|
+
if (isTwdType) {
|
|
358
|
+
// 1. Identifica il vento massimo del minuto (in m/s) - Loop in-place (0 allocazioni)
|
|
359
|
+
let maxTwsInMinute = 0;
|
|
360
|
+
for (let i = 0; i < tempBuf.length; i++) {
|
|
361
|
+
const tws = tempBuf[i].tws || 0;
|
|
362
|
+
if (tws > maxTwsInMinute) maxTwsInMinute = tws;
|
|
400
363
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
364
|
+
|
|
365
|
+
// 2. Calcola la soglia dinamica di pressione
|
|
366
|
+
const calmThresholdMs = CALM_THRESHOLD_KTS / 1.94384;
|
|
367
|
+
const pressureThreshold = Math.max(maxTwsInMinute * PRESSURE_FILTER_RATIO, calmThresholdMs);
|
|
368
|
+
|
|
369
|
+
// 3. Determina in un unico passaggio se ci sono punti attivi sopra la soglia (0 allocazioni)
|
|
370
|
+
let hasActivePoints = false;
|
|
371
|
+
for (let i = 0; i < tempBuf.length; i++) {
|
|
372
|
+
if ((tempBuf[i].tws || 0) >= pressureThreshold) {
|
|
373
|
+
hasActivePoints = true;
|
|
374
|
+
break;
|
|
412
375
|
}
|
|
413
|
-
finalValue = maxSustained;
|
|
414
376
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
377
|
+
|
|
378
|
+
// 4. Media Vettoriale Pesata (0 allocazioni di array)
|
|
379
|
+
let sumSin = 0;
|
|
380
|
+
let sumCos = 0;
|
|
381
|
+
let totalWeight = 0;
|
|
382
|
+
for (let i = 0; i < tempBuf.length; i++) {
|
|
383
|
+
const p = tempBuf[i];
|
|
384
|
+
if (hasActivePoints && (p.tws || 0) < pressureThreshold) continue;
|
|
385
|
+
|
|
386
|
+
const weight = Math.max(p.tws || 0.1, 0.05);
|
|
387
|
+
sumSin += weight * Math.sin(p.val);
|
|
388
|
+
sumCos += weight * Math.cos(p.val);
|
|
389
|
+
totalWeight += weight;
|
|
419
390
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
391
|
+
const avgAngle = Math.atan2(sumSin, sumCos);
|
|
392
|
+
const finalAvg = (avgAngle + Math.PI * 2) % (Math.PI * 2);
|
|
393
|
+
|
|
394
|
+
// 5. Calcolo di Min e Max angolare del minuto (0 allocazioni di array)
|
|
395
|
+
let minDiff = 0;
|
|
396
|
+
let maxDiff = 0;
|
|
397
|
+
let firstDiff = true;
|
|
398
|
+
for (let i = 0; i < tempBuf.length; i++) {
|
|
399
|
+
const p = tempBuf[i];
|
|
400
|
+
if (hasActivePoints && (p.tws || 0) < pressureThreshold) continue;
|
|
401
|
+
|
|
402
|
+
const diff = Math.atan2(Math.sin(p.val - finalAvg), Math.cos(p.val - finalAvg));
|
|
403
|
+
if (firstDiff) {
|
|
404
|
+
minDiff = diff;
|
|
405
|
+
maxDiff = diff;
|
|
406
|
+
firstDiff = false;
|
|
407
|
+
} else {
|
|
408
|
+
if (diff < minDiff) minDiff = diff;
|
|
409
|
+
if (diff > maxDiff) maxDiff = diff;
|
|
426
410
|
}
|
|
427
411
|
}
|
|
412
|
+
|
|
413
|
+
const finalMin = (finalAvg + minDiff + Math.PI * 2) % (Math.PI * 2);
|
|
414
|
+
const finalMax = (finalAvg + maxDiff + Math.PI * 2) % (Math.PI * 2);
|
|
415
|
+
|
|
416
|
+
finalValue = {
|
|
417
|
+
val: finalAvg,
|
|
418
|
+
min: finalMin,
|
|
419
|
+
max: finalMax
|
|
420
|
+
};
|
|
428
421
|
}
|
|
422
|
+
// A. VENTO VELOCITÀ -> SUSTAINED PEAK (EMA Time-Aware) (Inalterato per minimizzare le modifiche)
|
|
423
|
+
else if (type === 'tws' || type === 'aws') {
|
|
424
|
+
const tauMs = 2500;
|
|
425
|
+
let ema = tempBuf[0].val;
|
|
426
|
+
let maxSustained = ema;
|
|
427
|
+
|
|
428
|
+
for (let i = 1; i < tempBuf.length; i++) {
|
|
429
|
+
const dt = Math.max(1, tempBuf[i].time - tempBuf[i-1].time);
|
|
430
|
+
const alpha = 1 - Math.exp(-dt / tauMs);
|
|
431
|
+
ema = (tempBuf[i].val * alpha) + (ema * (1 - alpha));
|
|
432
|
+
if (isFinite(ema) && ema > maxSustained) maxSustained = ema;
|
|
433
|
+
}
|
|
434
|
+
finalValue = maxSustained;
|
|
435
|
+
}
|
|
436
|
+
// B. PROFONDITÀ -> MINIMO (Ottimizzato a 0 allocazioni)
|
|
437
|
+
else if (type === 'depth') {
|
|
438
|
+
let minVal = Infinity;
|
|
439
|
+
for (let i = 0; i < tempBuf.length; i++) {
|
|
440
|
+
const v = tempBuf[i].val;
|
|
441
|
+
if (isFinite(v) && v < minVal) minVal = v;
|
|
442
|
+
}
|
|
443
|
+
if (minVal !== Infinity) finalValue = minVal;
|
|
444
|
+
}
|
|
445
|
+
// C. VELOCITÀ BARCA / ALTRO -> MEDIA (Ottimizzato a 0 allocazioni)
|
|
446
|
+
else {
|
|
447
|
+
let sum = 0;
|
|
448
|
+
let count = 0;
|
|
449
|
+
for (let i = 0; i < tempBuf.length; i++) {
|
|
450
|
+
const v = tempBuf[i].val;
|
|
451
|
+
if (isFinite(v)) {
|
|
452
|
+
sum += v;
|
|
453
|
+
count++;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
if (count > 0) finalValue = sum / count;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
429
459
|
|
|
430
460
|
// Validazione e clamping di sicurezza (non si applica all'oggetto TWD)
|
|
431
461
|
if (!isTwdType) {
|