@sailingrotevista/rotevista-dash 2.0.17 → 2.0.18
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 +58 -141
- package/package.json +1 -1
package/app.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
// ==========================================================================
|
|
2
|
-
// 1. CONFIGURAZIONE E DEFAULT
|
|
2
|
+
// 1. CONFIGURAZIONE E DEFAULT (Sincronizzato con il Plugin SignalK)
|
|
3
3
|
// ==========================================================================
|
|
4
4
|
let CONFIG = {
|
|
5
5
|
alarms: { depthDanger: 2.5, depthWarning: 5.0 },
|
|
6
6
|
averages: { smoothWindow: 2000, longWindow: 60000, stabilityTolerance: 2000, stabilityThreshold: 0.90, minSpeed: 0.5 },
|
|
7
7
|
graphs: { reef1: 15.0, reef2: 20.0, historyMinutes: 5, samples: 60 },
|
|
8
|
-
scales: {
|
|
8
|
+
scales: {
|
|
9
|
+
stw: { stdMax: 12, hercSpan: 4, step: 2 },
|
|
10
|
+
sog: { stdMax: 12, hercSpan: 4, step: 2 },
|
|
11
|
+
tws: { stdMax: 25, hercSpan: 10, step: 5 },
|
|
12
|
+
depth: { stdMax: 20, hercSpan: 10, step: 10 }
|
|
13
|
+
},
|
|
9
14
|
server: { fallbackIp: "192.168.111.240:3000" }
|
|
10
15
|
};
|
|
11
16
|
|
|
@@ -26,9 +31,7 @@ let curBoatCompassRot = 0, curWindCompassRot = 0;
|
|
|
26
31
|
let smoothedLeeway = 0, rotationTrend = 0;
|
|
27
32
|
let lastShortAvgVal = null, lastInstantTwa = null;
|
|
28
33
|
let lastTrendTime = Date.now(), lastGybeAlarmTime = 0, lastTWCompute = 0;
|
|
29
|
-
let twDirty = false;
|
|
30
|
-
let reconnectDelay = 1000;
|
|
31
|
-
let isNavigating = false;
|
|
34
|
+
let twDirty = false, isNavigating = false, reconnectDelay = 1000;
|
|
32
35
|
|
|
33
36
|
let pressTimer, isFocusActive = false;
|
|
34
37
|
|
|
@@ -72,13 +75,16 @@ function ktsToMs(kts) { return kts / 1.94384; }
|
|
|
72
75
|
function getShortestRotation(curr, target) { let diff = (target - curr) % 360; if (diff > 180) diff -= 360; else if (diff < -180) diff += 360; return curr + diff; }
|
|
73
76
|
|
|
74
77
|
/**
|
|
75
|
-
* Gestione sicura dei buffer per prevenire memory leak.
|
|
78
|
+
* Gestione sicura dei buffer per prevenire memory leak (O(1) complexity).
|
|
76
79
|
*/
|
|
77
80
|
function safePush(buffer, val, time, maxLen = 200) {
|
|
78
81
|
buffer.push({ val: val, time: time });
|
|
79
82
|
if (buffer.length > maxLen) { buffer.shift(); }
|
|
80
83
|
}
|
|
81
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Calcola medie circolari restituendo Radianti.
|
|
87
|
+
*/
|
|
82
88
|
function getCircularAverageFromBuffer(bufferArray, windowMs, signed = false) {
|
|
83
89
|
const now = Date.now();
|
|
84
90
|
const validData = bufferArray.filter(item => (now - item.time) <= windowMs);
|
|
@@ -91,8 +97,13 @@ function getCircularAverageFromBuffer(bufferArray, windowMs, signed = false) {
|
|
|
91
97
|
return { val: signed ? avgRad : (avgRad + 2 * Math.PI) % (2 * Math.PI), stable: isStable };
|
|
92
98
|
}
|
|
93
99
|
|
|
100
|
+
// ==========================================================================
|
|
101
|
+
// 4. LOGICA FISICA E CALCOLI
|
|
102
|
+
// ==========================================================================
|
|
103
|
+
|
|
94
104
|
/**
|
|
95
|
-
* Calcola il Vento Reale
|
|
105
|
+
* Calcola il Vento Reale partendo dagli Apparenti.
|
|
106
|
+
* Logica Acqua (Vele) vs Terra (Meteo).
|
|
96
107
|
*/
|
|
97
108
|
function computeTrueWind() {
|
|
98
109
|
const aws = store.raw["environment.wind.speedApparent"];
|
|
@@ -103,11 +114,11 @@ function computeTrueWind() {
|
|
|
103
114
|
if (aws === undefined || awa === undefined) return;
|
|
104
115
|
if (awa > Math.PI) awa -= 2 * Math.PI;
|
|
105
116
|
|
|
106
|
-
//
|
|
117
|
+
// Vento Reale su ACQUA (TWA/TWS)
|
|
107
118
|
const tw_water_x = aws * Math.cos(awa) - stw, tw_water_y = aws * Math.sin(awa);
|
|
108
119
|
const tws_water = Math.sqrt(tw_water_x * tw_water_x + tw_water_y * tw_water_y), twa_water = Math.atan2(tw_water_y, tw_water_x);
|
|
109
120
|
|
|
110
|
-
//
|
|
121
|
+
// Vento Reale su TERRA (TWD)
|
|
111
122
|
const drift_angle = (cog - hdg + Math.PI * 3) % (2 * Math.PI) - Math.PI;
|
|
112
123
|
const sog_vec_x = sog * Math.cos(drift_angle), sog_vec_y = sog * Math.sin(drift_angle);
|
|
113
124
|
const tw_ground_x = aws * Math.cos(awa) - sog_vec_x, tw_ground_y = aws * Math.sin(awa) - sog_vec_y;
|
|
@@ -125,6 +136,7 @@ function processIncomingData(path, val) {
|
|
|
125
136
|
if (path === "navigation.position") store.raw["navigation.position"] = val;
|
|
126
137
|
if (path === "environment.wind.angleApparent") { safePush(store.smoothBuf.awa, val, now); safePush(store.longBuf.awa, val, now); }
|
|
127
138
|
|
|
139
|
+
// Dirty flag + Debounce 100ms
|
|
128
140
|
const twPaths = ["environment.wind.speedApparent", "environment.wind.angleApparent", "navigation.speedThroughWater", "navigation.speedOverGround", "navigation.headingTrue", "navigation.courseOverGroundTrue"];
|
|
129
141
|
if (twPaths.includes(path)) twDirty = true;
|
|
130
142
|
if (twDirty && (now - lastTWCompute > 100)) { computeTrueWind(); lastTWCompute = now; twDirty = false; }
|
|
@@ -134,25 +146,15 @@ function processIncomingData(path, val) {
|
|
|
134
146
|
}
|
|
135
147
|
|
|
136
148
|
// ==========================================================================
|
|
137
|
-
//
|
|
149
|
+
// 5. AUDIO E ALLARMI
|
|
138
150
|
// ==========================================================================
|
|
139
151
|
function playBingBing() { if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)(); const n = Date.now(); if (n - lastAlarmTime < 3000) return; lastAlarmTime = n; function b(f, s) { const o = audioCtx.createOscillator(); const g = audioCtx.createGain(); o.connect(g); g.connect(audioCtx.destination); o.frequency.value = f; g.gain.setValueAtTime(0.1, s); g.gain.exponentialRampToValueAtTime(0.01, s + 0.4); o.start(s); o.stop(s + 0.5); } b(880, audioCtx.currentTime); b(880, audioCtx.currentTime + 0.6); }
|
|
140
|
-
|
|
141
|
-
function playGybeAlarm() {
|
|
142
|
-
if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
|
143
|
-
const n = Date.now(); if (n - lastAlarmTime < 2000) return; lastAlarmTime = n;
|
|
144
|
-
function note(f, s, d) { const o = audioCtx.createOscillator(); const g = audioCtx.createGain(); o.connect(g); g.connect(audioCtx.destination); o.type = 'square'; o.frequency.value = f; g.gain.setValueAtTime(0.05, s); g.gain.exponentialRampToValueAtTime(0.001, s + d); o.start(s); o.stop(s + d); }
|
|
145
|
-
for (let i = 0; i < 4; i++) { note(1800, audioCtx.currentTime + (i * 0.15), 0.1); note(1200, audioCtx.currentTime + (i * 0.15) + 0.07, 0.1); }
|
|
146
|
-
}
|
|
147
|
-
|
|
152
|
+
function playGybeAlarm() { if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)(); const n = Date.now(); if (n - lastAlarmTime < 2000) return; lastAlarmTime = n; function note(f, s, d) { const o = audioCtx.createOscillator(); const g = audioCtx.createGain(); o.connect(g); g.connect(audioCtx.destination); o.type = 'square'; o.frequency.value = f; g.gain.setValueAtTime(0.05, s); g.gain.exponentialRampToValueAtTime(0.001, s + d); o.start(s); o.stop(s + d); } for (let i = 0; i < 4; i++) { note(1800, audioCtx.currentTime + (i * 0.15), 0.1); note(1200, audioCtx.currentTime + (i * 0.15) + 0.07, 0.1); } }
|
|
148
153
|
function checkDepthAlarm(m) { ui.depth.classList.remove('alarm-warning', 'alarm-danger'); if (m < CONFIG.alarms.depthDanger) { ui.depth.classList.add('alarm-danger'); playBingBing(); } else if (m < CONFIG.alarms.depthWarning) ui.depth.classList.add('alarm-warning'); }
|
|
149
154
|
|
|
150
155
|
// ==========================================================================
|
|
151
|
-
//
|
|
156
|
+
// 6. TREND VENTO E TATTICA
|
|
152
157
|
// ==========================================================================
|
|
153
|
-
/**
|
|
154
|
-
* Monitoraggio Trend Vento Professionale (Tattica + Meteo + Gybe Safety)
|
|
155
|
-
*/
|
|
156
158
|
function updateWindTrend() {
|
|
157
159
|
const now = Date.now();
|
|
158
160
|
const twaAvgObj = getCircularAverageFromBuffer(store.longBuf.twa, 60000, true);
|
|
@@ -160,14 +162,12 @@ function updateWindTrend() {
|
|
|
160
162
|
const instantTwaRad = store.raw["environment.wind.angleTrueWater"];
|
|
161
163
|
|
|
162
164
|
if (!shortAvg || !twaAvgObj || instantTwaRad === undefined) return;
|
|
163
|
-
const instantTwaDeg = radToDeg(instantTwaRad);
|
|
164
|
-
const shortAvgDeg = radToDeg(shortAvg.val);
|
|
165
|
-
const twaAvgDeg = radToDeg(twaAvgObj.val);
|
|
165
|
+
const instantTwaDeg = radToDeg(instantTwaRad), shortAvgDeg = radToDeg(shortAvg.val), twaAvgDeg = radToDeg(twaAvgObj.val);
|
|
166
166
|
|
|
167
167
|
if (lastShortAvgVal === null) { lastShortAvgVal = shortAvgDeg; lastInstantTwa = instantTwaDeg; return; }
|
|
168
168
|
const dt = (now - lastTrendTime) / 1000; lastTrendTime = now;
|
|
169
169
|
|
|
170
|
-
//
|
|
170
|
+
// Gybe Safety
|
|
171
171
|
const gybeDetected = (Math.abs(instantTwaDeg) > 155 && Math.sign(instantTwaDeg) !== Math.sign(lastInstantTwa));
|
|
172
172
|
lastInstantTwa = instantTwaDeg;
|
|
173
173
|
if (gybeDetected && isNavigating && (now - lastGybeAlarmTime > 5000)) { lastGybeAlarmTime = now; playGybeAlarm(); }
|
|
@@ -180,12 +180,12 @@ function updateWindTrend() {
|
|
|
180
180
|
return;
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
//
|
|
183
|
+
// Trend calculation con Clamping e Alpha adattivo
|
|
184
184
|
let diff = (shortAvgDeg - lastShortAvgVal + 540) % 360 - 180; lastShortAvgVal = shortAvgDeg;
|
|
185
185
|
if (dt > 0) {
|
|
186
|
-
let
|
|
186
|
+
let rate = Math.max(-10, Math.min(10, diff / dt));
|
|
187
187
|
const alpha = Math.min(1, dt / 5);
|
|
188
|
-
rotationTrend = rotationTrend * (1 - alpha) +
|
|
188
|
+
rotationTrend = rotationTrend * (1 - alpha) + rate * alpha;
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
if (Math.abs(rotationTrend) > 1.0) {
|
|
@@ -211,31 +211,34 @@ function updateWindTrend() {
|
|
|
211
211
|
}
|
|
212
212
|
|
|
213
213
|
// ==========================================================================
|
|
214
|
-
//
|
|
214
|
+
// 7. RENDERING ENGINE (TIERED RENDERING)
|
|
215
215
|
// ==========================================================================
|
|
216
216
|
function updateLeewayDisplay(deg) { const c = 125, px = 125/20; let w = Math.min(Math.abs(deg)*px, 125); ui.leewayMask.setAttribute('x', deg >= 0 ? c : c - w); ui.leewayMask.setAttribute('width', w); ui.leewayVal.textContent = `LEEWAY: ${deg.toFixed(1)}°`; }
|
|
217
217
|
|
|
218
|
-
function
|
|
219
|
-
|
|
218
|
+
function refreshGraph(t) {
|
|
219
|
+
const type = (t === 'vmg') ? 'sog' : t;
|
|
220
|
+
const data = store.histories[t]; if (!data || data.length < 2) return;
|
|
221
|
+
const mode = graphModes[type], cfg = calculateScale(type, data, mode);
|
|
222
|
+
const graphEl = document.getElementById(type + '-graph');
|
|
223
|
+
if (graphEl) { const box = graphEl.closest('.data-box'); if (box) box.classList.toggle('box-hercules', mode === 'hercules'); }
|
|
224
|
+
updateScaleLabels(type, cfg.min, cfg.max); drawGraph(data, type + '-graph', cfg.min, cfg.max, t === 'tws', mode === 'hercules');
|
|
225
|
+
}
|
|
220
226
|
|
|
227
|
+
function startDisplayLoop() {
|
|
228
|
+
let tick = 0;
|
|
221
229
|
renderInterval = setInterval(() => {
|
|
222
|
-
const now = Date.now();
|
|
223
|
-
tick++;
|
|
230
|
+
const now = Date.now(); tick++;
|
|
224
231
|
|
|
225
|
-
//
|
|
226
|
-
// LIVE TIER (Ogni 1s) - Reattività, Numeri, Allarmi, Lancette
|
|
227
|
-
// ==========================================================
|
|
232
|
+
// --- LIVE TIER (1s) ---
|
|
228
233
|
const pathsToWatch = { "navigation.speedThroughWater": ui.stw, "navigation.speedOverGround": ui.sog, "navigation.headingTrue": ui.hdg, "navigation.courseOverGroundTrue": ui.cog, "environment.wind.speedApparent": ui.awsSvg, "environment.depth.belowTransducer": ui.depth, "environment.wind.speedTrue": ui.tws };
|
|
229
234
|
for (let p in pathsToWatch) { if (!store.timestamps[p] || (now - store.timestamps[p] > TIMEOUT_MS)) { if (pathsToWatch[p] === ui.awsSvg) ui.awsSvg.textContent = "---"; else pathsToWatch[p].innerText = "---"; delete store.raw[p]; } }
|
|
230
235
|
|
|
231
236
|
const stwKts = msToKts(store.raw["navigation.speedThroughWater"] || 0), sogKts = msToKts(store.raw["navigation.speedOverGround"] || 0);
|
|
232
237
|
isNavigating = stwKts > CONFIG.averages.minSpeed || sogKts > CONFIG.averages.minSpeed;
|
|
233
238
|
|
|
234
|
-
// Numeri veloci
|
|
235
239
|
if (store.raw["navigation.speedThroughWater"] !== undefined) { ui.stw.innerText = stwKts.toFixed(1); manageHistory('stw', stwKts); }
|
|
236
240
|
if (store.raw["navigation.speedOverGround"] !== undefined) {
|
|
237
|
-
const twaRad = store.raw["environment.wind.angleTrueWater"];
|
|
238
|
-
const vmgKts = (twaRad !== undefined) ? Math.abs(stwKts * Math.cos(twaRad)) : 0;
|
|
241
|
+
const twaRad = store.raw["environment.wind.angleTrueWater"], vmgKts = (twaRad !== undefined) ? Math.abs(stwKts * Math.cos(twaRad)) : 0;
|
|
239
242
|
manageHistory('vmg', vmgKts); manageHistory('sog', sogKts);
|
|
240
243
|
if (displayModeSog === 'VMG') { ui.sog.innerText = vmgKts.toFixed(1); ui.sog.style.color = "#64ffda"; document.getElementById('sog-vmg-label').textContent = 'VMG'; }
|
|
241
244
|
else { ui.sog.innerText = sogKts.toFixed(1); ui.sog.style.color = (sogKts-stwKts > 0.3) ? "#2ecc71" : (sogKts-stwKts < -0.3 ? "#e74c3c" : "#fff"); document.getElementById('sog-vmg-label').textContent = 'SOG'; }
|
|
@@ -244,38 +247,27 @@ function startDisplayLoop() {
|
|
|
244
247
|
if (store.raw["environment.wind.speedTrue"] !== undefined) { const twsKts = msToKts(store.raw["environment.wind.speedTrue"]); ui.tws.innerText = twsKts.toFixed(1); ui.tws.style.color = (twsKts >= CONFIG.graphs.reef2) ? "#e74c3c" : (twsKts >= CONFIG.graphs.reef1 ? "#e67e22" : "#fff"); manageHistory('tws', twsKts); }
|
|
245
248
|
if (store.raw["environment.wind.speedApparent"] !== undefined) ui.awsSvg.textContent = msToKts(store.raw["environment.wind.speedApparent"]).toFixed(1);
|
|
246
249
|
|
|
247
|
-
// Lancette (Sempre fluide ogni 1s)
|
|
248
250
|
const smAwa = getCircularAverageFromBuffer(store.smoothBuf.awa, CONFIG.averages.smoothWindow, true);
|
|
249
251
|
if (smAwa) { curAwaRot = getShortestRotation(curAwaRot, radToDeg(smAwa.val)); ui.awa.setAttribute('transform', `rotate(${curAwaRot}, 200, 200)`); }
|
|
250
252
|
const smTwa = getCircularAverageFromBuffer(store.smoothBuf.twa, CONFIG.averages.smoothWindow, true);
|
|
251
253
|
if (smTwa) { curTwaRot = getShortestRotation(curTwaRot, radToDeg(smTwa.val)); ui.twa.setAttribute('transform', `rotate(${curTwaRot}, 200, 200)`); }
|
|
252
254
|
|
|
253
|
-
// Leeway (Ogni 1s)
|
|
254
255
|
if (store.raw["navigation.courseOverGroundTrue"] !== undefined && store.raw["navigation.headingTrue"] !== undefined) {
|
|
255
|
-
let
|
|
256
|
-
let driftDeg = radToDeg(driftRad);
|
|
256
|
+
let driftDeg = radToDeg((store.raw["navigation.courseOverGroundTrue"] - store.raw["navigation.headingTrue"] + Math.PI * 3) % (Math.PI * 2) - Math.PI);
|
|
257
257
|
if (sogKts < CONFIG.averages.minSpeed) smoothedLeeway = 0; else smoothedLeeway = (smoothedLeeway * 0.9) + (driftDeg * 0.1);
|
|
258
258
|
curTrackRot = getShortestRotation(curTrackRot, smoothedLeeway); ui.track.setAttribute('transform', `rotate(${curTrackRot}, 200, 200)`);
|
|
259
|
-
const isContaminated = Math.abs(sogKts - stwKts) > 0.5 && Math.abs(smoothedLeeway) > 7;
|
|
260
|
-
ui.leewayVal.style.color = isContaminated ? "#f39c12" : "#fff";
|
|
259
|
+
const isContaminated = Math.abs(sogKts - stwKts) > 0.5 && Math.abs(smoothedLeeway) > 7; ui.leewayVal.style.color = isContaminated ? "#f39c12" : "#fff";
|
|
261
260
|
updateLeewayDisplay(Math.max(-20, Math.min(20, smoothedLeeway)));
|
|
262
261
|
}
|
|
263
262
|
|
|
264
|
-
updateWindTrend(); //
|
|
263
|
+
updateWindTrend(); // Fast update
|
|
265
264
|
|
|
266
|
-
//
|
|
267
|
-
// HEAVY TIER (Ogni 2s) - Disegno Sparklines SVG
|
|
268
|
-
// ==========================================================
|
|
265
|
+
// --- HEAVY TIER (2s) ---
|
|
269
266
|
if (tick % 2 === 0) {
|
|
270
|
-
refreshGraph('stw');
|
|
271
|
-
refreshGraph(displayModeSog === 'VMG' ? 'vmg' : 'sog');
|
|
272
|
-
refreshGraph('depth');
|
|
273
|
-
refreshGraph('tws');
|
|
267
|
+
refreshGraph('stw'); refreshGraph(displayModeSog === 'VMG' ? 'vmg' : 'sog'); refreshGraph('depth'); refreshGraph('tws');
|
|
274
268
|
}
|
|
275
269
|
|
|
276
|
-
//
|
|
277
|
-
// SLOW TIER (Ogni 3s) - Medie MEAN e Calcoli TACK
|
|
278
|
-
// ==========================================================
|
|
270
|
+
// --- SLOW TIER (3s) ---
|
|
279
271
|
if (tick % 3 === 0) {
|
|
280
272
|
let hObj = getCircularAverageFromBuffer(store.longBuf.hdg, CONFIG.averages.longWindow, false),
|
|
281
273
|
cObj = getCircularAverageFromBuffer(store.longBuf.cog, CONFIG.averages.longWindow, false),
|
|
@@ -291,7 +283,6 @@ function startDisplayLoop() {
|
|
|
291
283
|
}
|
|
292
284
|
};
|
|
293
285
|
upUI(ui.hdg, hObj, true); upUI(ui.cog, cObj, true); upUI(ui.awaAvg, awObj, false); upUI(ui.twaAvg, twObj, false); upUI(ui.twdAvg, twdObj, true);
|
|
294
|
-
|
|
295
286
|
if (hObj && twObj && hObj.val !== null) {
|
|
296
287
|
const tackHdgDeg = radToDeg((hObj.val - (twObj.val * 2) + Math.PI * 2) % (Math.PI * 2));
|
|
297
288
|
ui.tackHdg.innerHTML = `${Math.round((tackHdgDeg + 360) % 360).toString().padStart(3, '0')}°`;
|
|
@@ -300,25 +291,18 @@ function startDisplayLoop() {
|
|
|
300
291
|
ui.tackCog.innerHTML = `${Math.round((tackCogDeg + 360) % 360).toString().padStart(3, '0')}°`;
|
|
301
292
|
}
|
|
302
293
|
}
|
|
303
|
-
|
|
304
294
|
const smHdg = getCircularAverageFromBuffer(store.smoothBuf.hdg, 2000, false), smTwd = getCircularAverageFromBuffer(store.smoothBuf.twd, 2000, false);
|
|
305
295
|
if (smHdg && smTwd) {
|
|
306
296
|
curWindCompassRot = getShortestRotation(curWindCompassRot, radToDeg(smTwd.val)); ui.twdArrow.setAttribute('transform', `rotate(${curWindCompassRot}, 20, 20)`);
|
|
307
297
|
curBoatCompassRot = getShortestRotation(curBoatCompassRot, radToDeg(smHdg.val)); ui.twdBoat.setAttribute('transform', `rotate(${curBoatCompassRot}, 20, 20)`);
|
|
308
298
|
}
|
|
309
299
|
}
|
|
310
|
-
|
|
311
|
-
// Pulizia buffer
|
|
312
|
-
if (tick % 60 === 0) { // Ogni minuto pulisce tutto per sicurezza
|
|
313
|
-
for (let b in store.smoothBuf) { while (store.smoothBuf[b].length > 0 && (now - store.smoothBuf[b][0].time) > CONFIG.averages.smoothWindow) store.smoothBuf[b].shift(); }
|
|
314
|
-
for (let b in store.longBuf) { while (store.longBuf[b].length > 0 && (now - store.longBuf[b][0].time) > CONFIG.averages.longWindow) store.longBuf[b].shift(); }
|
|
315
|
-
tick = 0; // Reset contatore
|
|
316
|
-
}
|
|
300
|
+
if (tick % 60 === 0) tick = 0;
|
|
317
301
|
}, RENDER_INTERVAL_MS);
|
|
318
302
|
}
|
|
319
303
|
|
|
320
304
|
// ==========================================================================
|
|
321
|
-
//
|
|
305
|
+
// 8. INTERAZIONI E RETE
|
|
322
306
|
// ==========================================================================
|
|
323
307
|
async function fetchServerConfig() {
|
|
324
308
|
if (!window.location.protocol.includes("http")) return;
|
|
@@ -343,43 +327,9 @@ async function fetchServerConfig() {
|
|
|
343
327
|
}
|
|
344
328
|
}
|
|
345
329
|
|
|
346
|
-
/**
|
|
347
|
-
* Gestisce l'archiviazione dei dati storici.
|
|
348
|
-
* Il disegno grafico viene ora gestito separatamente nel loop pesante.
|
|
349
|
-
*/
|
|
350
330
|
function manageHistory(t, v) {
|
|
351
|
-
const n = Date.now();
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
if (n - store.lastUpdates[t] > interval || store.histories[t].length === 0) {
|
|
355
|
-
store.histories[t].push(v);
|
|
356
|
-
if (store.histories[t].length > CONFIG.graphs.samples) store.histories[t].shift();
|
|
357
|
-
store.lastUpdates[t] = n;
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
/**
|
|
362
|
-
* Funzione dedicata al ridisegno dei grafici (chiamata dal loop HEAVY)
|
|
363
|
-
*/
|
|
364
|
-
function refreshGraph(t) {
|
|
365
|
-
const type = (t === 'vmg') ? 'sog' : t;
|
|
366
|
-
const data = store.histories[t];
|
|
367
|
-
if (!data || data.length < 2) return;
|
|
368
|
-
|
|
369
|
-
const mode = graphModes[type];
|
|
370
|
-
const cfg = calculateScale(type, data, mode);
|
|
371
|
-
|
|
372
|
-
// Aggiornamento stile box Hercules
|
|
373
|
-
const graphEl = document.getElementById(type + '-graph');
|
|
374
|
-
if (graphEl) {
|
|
375
|
-
const box = graphEl.closest('.data-box');
|
|
376
|
-
if (box) {
|
|
377
|
-
if (mode === 'hercules') box.classList.add('box-hercules');
|
|
378
|
-
else box.classList.remove('box-hercules');
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
updateScaleLabels(type, cfg.min, cfg.max);
|
|
382
|
-
drawGraph(data, type + '-graph', cfg.min, cfg.max, t === 'tws', mode === 'hercules');
|
|
331
|
+
const n = Date.now(); const interval = simulationMode ? SIM_SAMPLE_INTERVAL : (CONFIG.graphs.historyMinutes * 60000) / CONFIG.graphs.samples;
|
|
332
|
+
if (n - store.lastUpdates[t] > interval || store.histories[t].length === 0) { store.histories[t].push(v); if (store.histories[t].length > CONFIG.graphs.samples) store.histories[t].shift(); store.lastUpdates[t] = n; }
|
|
383
333
|
}
|
|
384
334
|
|
|
385
335
|
function calculateScale(type, data, mode) {
|
|
@@ -409,9 +359,6 @@ function drawGraph(d, id, min, max, isTws, isHercules) {
|
|
|
409
359
|
svg.innerHTML = isTws ? `${grids}<path d="${pD} L ${((d.length-1)/(CONFIG.graphs.samples-1))*w} ${h} L 0 ${h} Z" fill="rgba(255,255,255,0.08)" stroke="none" />${cS}` : `${grids}<path d="${pD} L ${((d.length-1)/(CONFIG.graphs.samples-1))*w} ${h} L 0 ${h} Z" fill="${colorKey}22" stroke="none" /><path d="${pD}" class="${isHercules?'line-hercules':''}" fill="none" stroke="${colorKey}" />`;
|
|
410
360
|
}
|
|
411
361
|
|
|
412
|
-
// ==========================================================================
|
|
413
|
-
// 8. INTERAZIONI E SIMULATORE
|
|
414
|
-
// ==========================================================================
|
|
415
362
|
function toggleFocusMode(type, element) {
|
|
416
363
|
const container = document.querySelector('.main-container'); const parentPanel = element.closest('.side-panel'); const isLeft = parentPanel.classList.contains('left-panel');
|
|
417
364
|
isFocusActive = !isFocusActive;
|
|
@@ -440,44 +387,14 @@ if (ui.hotspot) {
|
|
|
440
387
|
|
|
441
388
|
function connect() {
|
|
442
389
|
if (simulationMode) return;
|
|
443
|
-
|
|
444
390
|
let addr = (window.location.protocol.includes("http")) ? window.location.host : CONFIG.server.fallbackIp;
|
|
445
391
|
const protocol = (window.location.protocol === 'https:') ? 'wss' : 'ws';
|
|
446
|
-
|
|
447
392
|
try {
|
|
448
393
|
socket = new WebSocket(`${protocol}://${addr}/signalk/v1/stream?subscribe=self`);
|
|
449
|
-
|
|
450
|
-
socket.
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
reconnectDelay = 1000; // Reset del ritardo dopo una connessione riuscita
|
|
454
|
-
};
|
|
455
|
-
|
|
456
|
-
socket.onmessage = (e) => {
|
|
457
|
-
if (simulationMode) return;
|
|
458
|
-
const d = JSON.parse(e.data);
|
|
459
|
-
if (d.updates) d.updates.forEach(u => u.values && u.values.forEach(v => processIncomingData(v.path, v.value)));
|
|
460
|
-
};
|
|
461
|
-
|
|
462
|
-
socket.onclose = () => {
|
|
463
|
-
if (!simulationMode) {
|
|
464
|
-
ui.status.className = "offline";
|
|
465
|
-
ui.status.innerText = "RECONNECTING...";
|
|
466
|
-
|
|
467
|
-
// Exponential Backoff: aumenta il tempo ad ogni tentativo fallito
|
|
468
|
-
setTimeout(connect, reconnectDelay);
|
|
469
|
-
reconnectDelay = Math.min(reconnectDelay * 1.5, 10000); // Max 10 secondi
|
|
470
|
-
}
|
|
471
|
-
};
|
|
472
|
-
|
|
473
|
-
socket.onerror = () => {
|
|
474
|
-
socket.close(); // Forza il trigger di onclose
|
|
475
|
-
};
|
|
476
|
-
|
|
477
|
-
} catch (e) {
|
|
478
|
-
setTimeout(connect, reconnectDelay);
|
|
479
|
-
reconnectDelay = Math.min(reconnectDelay * 1.5, 10000);
|
|
480
|
-
}
|
|
394
|
+
socket.onopen = () => { ui.status.className = "online"; ui.status.innerText = "ONLINE"; reconnectDelay = 1000; };
|
|
395
|
+
socket.onmessage = (e) => { if (simulationMode) return; const d = JSON.parse(e.data); if (d.updates) d.updates.forEach(u => u.values && u.values.forEach(v => processIncomingData(v.path, v.value))); };
|
|
396
|
+
socket.onclose = () => { if (!simulationMode) { ui.status.className = "offline"; ui.status.innerText = "RECONNECTING..."; setTimeout(connect, reconnectDelay); reconnectDelay = Math.min(reconnectDelay * 1.5, 10000); } };
|
|
397
|
+
} catch (e) { setTimeout(connect, reconnectDelay); reconnectDelay = Math.min(reconnectDelay * 1.5, 10000); }
|
|
481
398
|
}
|
|
482
399
|
|
|
483
400
|
ui.depth.closest('.data-box').addEventListener('click', (function() { let dC = 0, lC = 0; return function() { const n = Date.now(); if (n - lC < 500) dC++; else dC = 1; lC = n; if (dC === 3) { simulationMode = !simulationMode; if (simulationMode) { if (socket) socket.close(); startDynamicSimulation(); } else location.reload(); dC = 0; } }; })());
|
|
@@ -507,7 +424,7 @@ function startDynamicSimulation() {
|
|
|
507
424
|
}
|
|
508
425
|
|
|
509
426
|
// ==========================================================================
|
|
510
|
-
//
|
|
427
|
+
// 11. INIT
|
|
511
428
|
// ==========================================================================
|
|
512
429
|
window.addEventListener('contextmenu', e => e.preventDefault(), true);
|
|
513
430
|
(function genTicks() { const c = document.getElementById('ticks'); if (c) { for (let i = 0; i < 360; i += 10) { const l = document.createElementNS("http://www.w3.org/2000/svg", "line"); const m = i % 30 === 0; l.setAttribute("x1", "200"); l.setAttribute("y1", "40"); l.setAttribute("x2", "200"); l.setAttribute("y2", (m ? 60 : 50)); l.setAttribute("stroke", m ? "#fff" : "#666"); l.setAttribute("stroke-width", m ? "2" : "1"); l.setAttribute("transform", `rotate(${i}, 200, 200)`); c.appendChild(l); } } })();
|