@sailingrotevista/rotevista-dash 1.0.17 → 1.0.19
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 +48 -131
- package/package.json +1 -1
- package/test.html +71 -0
package/app.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// ==========================================================================
|
|
2
|
-
// 1. CONFIGURAZIONE
|
|
2
|
+
// 1. CONFIGURAZIONE DI DEFAULT (Sovrascritta dal Server)
|
|
3
3
|
// ==========================================================================
|
|
4
4
|
let CONFIG = {
|
|
5
5
|
alarms: {
|
|
@@ -14,23 +14,22 @@ let CONFIG = {
|
|
|
14
14
|
minSpeed: 0.5
|
|
15
15
|
},
|
|
16
16
|
graphs: {
|
|
17
|
-
reef1: 15.0, // Soglia
|
|
18
|
-
reef2: 20.0, // Soglia
|
|
17
|
+
reef1: 15.0, // Soglia Arancio
|
|
18
|
+
reef2: 20.0, // Soglia Rossa
|
|
19
19
|
realInterval: 5000,
|
|
20
20
|
samples: 60
|
|
21
21
|
},
|
|
22
22
|
scales: {
|
|
23
|
-
stw:
|
|
24
|
-
sog:
|
|
25
|
-
tws:
|
|
26
|
-
depth: { stdMax:
|
|
23
|
+
stw: { stdMax: 12, hercSpan: 4, step: 2 },
|
|
24
|
+
sog: { stdMax: 12, hercSpan: 4, step: 2 },
|
|
25
|
+
tws: { stdMax: 25, hercSpan: 10, step: 5 },
|
|
26
|
+
depth: { stdMax: 20, hercSpan: 10, step: 10 }
|
|
27
27
|
},
|
|
28
28
|
server: {
|
|
29
29
|
fallbackIp: "192.168.111.240:3000"
|
|
30
30
|
}
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
// Costanti tecniche non configurabili
|
|
34
33
|
const RENDER_INTERVAL_MS = 1000;
|
|
35
34
|
const TIMEOUT_MS = 5000;
|
|
36
35
|
const SIM_SAMPLE_INTERVAL = 1000;
|
|
@@ -43,7 +42,6 @@ let socket, renderInterval, simInterval;
|
|
|
43
42
|
let lastAvgUIUpdate = 0, audioCtx = null, lastAlarmTime = 0;
|
|
44
43
|
let curAwaRot = 0, curTwaRot = 0, curTrackRot = 0, curTwdRoseRot = 0;
|
|
45
44
|
|
|
46
|
-
// Modalità Scale Grafici (Local al tablet)
|
|
47
45
|
const graphModes = {
|
|
48
46
|
stw: localStorage.getItem('mode_stw') || 'standard',
|
|
49
47
|
sog: localStorage.getItem('mode_sog') || 'standard',
|
|
@@ -74,53 +72,37 @@ const ui = {
|
|
|
74
72
|
};
|
|
75
73
|
|
|
76
74
|
// ==========================================================================
|
|
77
|
-
// 3.
|
|
75
|
+
// 3. COMUNICAZIONE CON IL SERVER (FETCH CONFIG)
|
|
78
76
|
// ==========================================================================
|
|
79
77
|
async function fetchServerConfig() {
|
|
80
78
|
if (!window.location.protocol.includes("http")) return;
|
|
81
|
-
|
|
82
|
-
// Elenco di tutti i percorsi possibili che SignalK usa per i plugin
|
|
83
79
|
const pluginID = 'rotevista-dash';
|
|
84
|
-
const scopeID = '@sailingrotevista/rotevista-dash';
|
|
85
|
-
|
|
86
80
|
const possibleUrls = [
|
|
87
|
-
`/plugins/${pluginID}/
|
|
88
|
-
`/plugins/${
|
|
89
|
-
`/signalk/v1/api/plugins/${pluginID}/settings`,
|
|
90
|
-
`/signalk/v1/api/plugins/${scopeID}/settings`
|
|
81
|
+
`/skServer/plugins/${pluginID}/config`,
|
|
82
|
+
`/plugins/${pluginID}/config`
|
|
91
83
|
];
|
|
92
84
|
|
|
93
|
-
console.log("Dashboard: Inizio scansione configurazione server...");
|
|
94
|
-
|
|
95
85
|
for (let url of possibleUrls) {
|
|
96
86
|
try {
|
|
97
87
|
const response = await fetch(url);
|
|
98
88
|
if (response.ok) {
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (serverConfig.averages) CONFIG.averages = { ...CONFIG.averages, ...serverConfig.averages };
|
|
108
|
-
|
|
109
|
-
console.log("%c SUCCESS: Configurazione caricata da: " + url, "color: #2ecc71; font-weight: bold;");
|
|
110
|
-
console.log("Dati applicati:", CONFIG);
|
|
111
|
-
return; // Esci: abbiamo trovato i dati!
|
|
89
|
+
const data = await response.json();
|
|
90
|
+
const actualConfig = data.configuration || data;
|
|
91
|
+
if (actualConfig && typeof actualConfig === 'object') {
|
|
92
|
+
if (actualConfig.alarms) CONFIG.alarms = { ...CONFIG.alarms, ...actualConfig.alarms };
|
|
93
|
+
if (actualConfig.graphs) CONFIG.graphs = { ...CONFIG.graphs, ...actualConfig.graphs };
|
|
94
|
+
if (actualConfig.averages) CONFIG.averages = { ...CONFIG.averages, ...actualConfig.averages };
|
|
95
|
+
console.log("SUCCESS: Configurazione caricata da " + url);
|
|
96
|
+
return;
|
|
112
97
|
}
|
|
113
98
|
}
|
|
114
|
-
} catch (e) {
|
|
115
|
-
// Ignora l'errore e prova il prossimo URL
|
|
116
|
-
}
|
|
99
|
+
} catch (e) { }
|
|
117
100
|
}
|
|
118
|
-
|
|
119
|
-
console.warn("Dashboard: Nessuna impostazione trovata sul server (o plugin mai configurato). Uso i default.");
|
|
101
|
+
console.log("Info: Uso configurazione di default.");
|
|
120
102
|
}
|
|
121
103
|
|
|
122
104
|
// ==========================================================================
|
|
123
|
-
// 4. MATEMATICA E
|
|
105
|
+
// 4. MATEMATICA E GESTIONE DATI
|
|
124
106
|
// ==========================================================================
|
|
125
107
|
function radToDeg(rad) { return rad * (180 / Math.PI); }
|
|
126
108
|
function degToRad(deg) { return deg * (Math.PI / 180); }
|
|
@@ -136,26 +118,13 @@ function getCircularAverageFromBuffer(bufferArray, windowMs, signed = false) {
|
|
|
136
118
|
validData.forEach(item => { sSin += Math.sin(item.val); sCos += Math.cos(item.val); });
|
|
137
119
|
let R = Math.sqrt(sSin * sSin + sCos * sCos) / validData.length;
|
|
138
120
|
let timeSpan = validData[validData.length - 1].time - validData[0].time;
|
|
139
|
-
// Usa le soglie CONFIG
|
|
140
121
|
let isStable = (timeSpan >= windowMs - CONFIG.averages.stabilityTolerance) && (R > CONFIG.averages.stabilityThreshold);
|
|
141
|
-
let
|
|
142
|
-
let avgDeg = Math.round(radToDeg(avgRad));
|
|
122
|
+
let avgDeg = Math.round(radToDeg(Math.atan2(sSin, sCos)));
|
|
143
123
|
return { val: signed ? avgDeg : (avgDeg + 360) % 360, stable: isStable };
|
|
144
124
|
}
|
|
145
125
|
|
|
146
|
-
function cleanBuffer(bufferArray, windowMs) {
|
|
147
|
-
const now = Date.now();
|
|
148
|
-
while (bufferArray.length > 0 && (now - bufferArray[0].time) > windowMs) bufferArray.shift();
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// ==========================================================================
|
|
152
|
-
// 5. GESTIONE DATI IN INGRESSO
|
|
153
|
-
// ==========================================================================
|
|
154
126
|
function processIncomingData(path, val) {
|
|
155
|
-
const now = Date.now();
|
|
156
|
-
store.timestamps[path] = now;
|
|
157
|
-
store.raw[path] = val;
|
|
158
|
-
|
|
127
|
+
const now = Date.now(); store.timestamps[path] = now; store.raw[path] = val;
|
|
159
128
|
if (path === "navigation.headingTrue") { store.smoothBuf.hdg.push({ val: val, time: now }); store.longBuf.hdg.push({ val: val, time: now }); }
|
|
160
129
|
if (path === "navigation.courseOverGroundTrue") { store.smoothBuf.cog.push({ val: val, time: now }); store.longBuf.cog.push({ val: val, time: now }); }
|
|
161
130
|
if (path === "environment.wind.angleApparent") { store.smoothBuf.awa.push({ val: val, time: now }); store.longBuf.awa.push({ val: val, time: now }); }
|
|
@@ -164,26 +133,20 @@ function processIncomingData(path, val) {
|
|
|
164
133
|
}
|
|
165
134
|
|
|
166
135
|
// ==========================================================================
|
|
167
|
-
//
|
|
136
|
+
// 5. RENDERING ENGINE
|
|
168
137
|
// ==========================================================================
|
|
169
138
|
function startDisplayLoop() {
|
|
170
|
-
if (renderInterval) clearInterval(renderInterval);
|
|
171
139
|
renderInterval = setInterval(() => {
|
|
172
140
|
const now = Date.now();
|
|
173
|
-
|
|
174
|
-
// 6.1 Watchdog
|
|
175
141
|
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 };
|
|
176
142
|
for (let p in pathsToWatch) { if (!store.timestamps[p] || (now - store.timestamps[p] > TIMEOUT_MS)) { pathsToWatch[p][pathsToWatch[p] === ui.awsSvg ? 'textContent' : 'innerText'] = "---"; delete store.raw[p]; } }
|
|
177
143
|
|
|
178
|
-
// 6.2 Render Dati
|
|
179
144
|
if (store.raw["navigation.speedThroughWater"] !== undefined) { const v = msToKts(store.raw["navigation.speedThroughWater"]); ui.stw.innerText = v.toFixed(1); manageHistory('stw', v); }
|
|
180
|
-
let curSog = 0;
|
|
181
|
-
if (store.raw["navigation.speedOverGround"] !== undefined) { curSog = msToKts(store.raw["navigation.speedOverGround"]); ui.sog.innerText = curSog.toFixed(1); manageHistory('sog', curSog); }
|
|
145
|
+
let curSog = 0; if (store.raw["navigation.speedOverGround"] !== undefined) { curSog = msToKts(store.raw["navigation.speedOverGround"]); ui.sog.innerText = curSog.toFixed(1); manageHistory('sog', curSog); }
|
|
182
146
|
if (store.raw["environment.depth.belowTransducer"] !== undefined) { const d = store.raw["environment.depth.belowTransducer"]; ui.depth.innerText = d.toFixed(1); checkDepthAlarm(d); manageHistory('depth', d); }
|
|
183
147
|
if (store.raw["environment.wind.speedTrue"] !== undefined) { const w = msToKts(store.raw["environment.wind.speedTrue"]); ui.tws.innerText = w.toFixed(1); manageHistory('tws', w); }
|
|
184
148
|
if (store.raw["environment.wind.speedApparent"] !== undefined) ui.awsSvg.textContent = msToKts(store.raw["environment.wind.speedApparent"]).toFixed(1);
|
|
185
149
|
|
|
186
|
-
// 6.3 Render Quadrante (AWA, TWA, Drift)
|
|
187
150
|
const smAwa = getCircularAverageFromBuffer(store.smoothBuf.awa, CONFIG.averages.smoothWindow, true);
|
|
188
151
|
if (smAwa) { curAwaRot = getShortestRotation(curAwaRot, smAwa.val); ui.awa.setAttribute('transform', `rotate(${curAwaRot}, 200, 200)`); }
|
|
189
152
|
const smTwa = getCircularAverageFromBuffer(store.smoothBuf.twa, CONFIG.averages.smoothWindow, true);
|
|
@@ -192,12 +155,10 @@ function startDisplayLoop() {
|
|
|
192
155
|
if (store.raw["navigation.courseOverGroundTrue"] && store.raw["navigation.headingTrue"]) {
|
|
193
156
|
let drift = (radToDeg(store.raw["navigation.courseOverGroundTrue"]) - radToDeg(store.raw["navigation.headingTrue"]) + 360) % 360;
|
|
194
157
|
if (curSog < CONFIG.averages.minSpeed) drift = 0;
|
|
195
|
-
curTrackRot = getShortestRotation(curTrackRot, drift);
|
|
196
|
-
ui.track.setAttribute('transform', `rotate(${curTrackRot}, 200, 200)`);
|
|
158
|
+
curTrackRot = getShortestRotation(curTrackRot, drift); ui.track.setAttribute('transform', `rotate(${curTrackRot}, 200, 200)`);
|
|
197
159
|
let ds = drift > 180 ? drift - 360 : drift; updateLeewayDisplay(Math.max(-20, Math.min(20, ds)));
|
|
198
160
|
} else updateLeewayDisplay(0);
|
|
199
|
-
|
|
200
|
-
// 6.4 Render Medie Lunghe (MEAN) e TACK
|
|
161
|
+
|
|
201
162
|
if (now - lastAvgUIUpdate > 3000) {
|
|
202
163
|
let hObj = getCircularAverageFromBuffer(store.longBuf.hdg, CONFIG.averages.longWindow, false),
|
|
203
164
|
cObj = getCircularAverageFromBuffer(store.longBuf.cog, CONFIG.averages.longWindow, false),
|
|
@@ -209,8 +170,7 @@ function startDisplayLoop() {
|
|
|
209
170
|
if (!obj) { el.innerHTML = "---°"; el.classList.remove('unstable-data'); }
|
|
210
171
|
else {
|
|
211
172
|
el.innerHTML = `${obj.val.toString().padStart(3, '0')}°`;
|
|
212
|
-
if (obj.stable || curSog < CONFIG.averages.minSpeed) el.classList.remove('unstable-data');
|
|
213
|
-
else el.classList.add('unstable-data');
|
|
173
|
+
if (obj.stable || curSog < CONFIG.averages.minSpeed) el.classList.remove('unstable-data'); else el.classList.add('unstable-data');
|
|
214
174
|
}
|
|
215
175
|
};
|
|
216
176
|
upUI(ui.hdg, hObj); upUI(ui.cog, cObj); upUI(ui.awaAvg, awObj); upUI(ui.twaAvg, twObj); upUI(ui.twdAvg, twdObj);
|
|
@@ -219,21 +179,18 @@ function startDisplayLoop() {
|
|
|
219
179
|
let tA = twObj.val * 2;
|
|
220
180
|
ui.tackHdg.innerHTML = `${Math.round((hObj.val - tA + 360) % 360).toString().padStart(3, '0')}°`;
|
|
221
181
|
if (cObj) ui.tackCog.innerHTML = `${Math.round((cObj.val - tA + 360) % 360).toString().padStart(3, '0')}°`;
|
|
222
|
-
|
|
223
|
-
if (tStable) { ui.tackHdg.classList.remove('unstable-data'); ui.tackCog.classList.remove('unstable-data'); }
|
|
224
|
-
else { ui.tackHdg.classList.add('unstable-data'); ui.tackCog.classList.add('unstable-data'); }
|
|
182
|
+
if (hObj.stable && twObj.stable || curSog < CONFIG.averages.minSpeed) { ui.tackHdg.classList.remove('unstable-data'); ui.tackCog.classList.remove('unstable-data'); } else { ui.tackHdg.classList.add('unstable-data'); ui.tackCog.classList.add('unstable-data'); }
|
|
225
183
|
}
|
|
226
184
|
if (twdObj) { curTwdRoseRot = getShortestRotation(curTwdRoseRot, twdObj.val); ui.twdArrow.setAttribute('transform', `rotate(${curTwdRoseRot}, 20, 20)`); }
|
|
227
185
|
lastAvgUIUpdate = now;
|
|
228
186
|
}
|
|
229
|
-
|
|
230
|
-
for (let b in store.
|
|
231
|
-
for (let b in store.longBuf) cleanBuffer(store.longBuf[b], CONFIG.averages.longWindow);
|
|
187
|
+
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(); }
|
|
188
|
+
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(); }
|
|
232
189
|
}, RENDER_INTERVAL_MS);
|
|
233
190
|
}
|
|
234
191
|
|
|
235
192
|
// ==========================================================================
|
|
236
|
-
//
|
|
193
|
+
// 6. CONNESSIONE SIGNALK (subscribe=self)
|
|
237
194
|
// ==========================================================================
|
|
238
195
|
function connect() {
|
|
239
196
|
if (simulationMode) return;
|
|
@@ -241,32 +198,28 @@ function connect() {
|
|
|
241
198
|
try {
|
|
242
199
|
socket = new WebSocket(`ws://${addr}/signalk/v1/stream?subscribe=self`);
|
|
243
200
|
socket.onopen = () => { ui.status.className = "online"; ui.status.innerText = "ONLINE"; };
|
|
244
|
-
socket.onmessage = (e) => {
|
|
201
|
+
socket.onmessage = (e) => {
|
|
202
|
+
const d = JSON.parse(e.data);
|
|
203
|
+
if (d.updates) d.updates.forEach(u => u.values && u.values.forEach(v => processIncomingData(v.path, v.value)));
|
|
204
|
+
};
|
|
245
205
|
socket.onclose = () => !simulationMode && setTimeout(connect, 5000);
|
|
246
206
|
} catch (e) { setTimeout(connect, 5000); }
|
|
247
207
|
}
|
|
248
208
|
|
|
249
209
|
// ==========================================================================
|
|
250
|
-
//
|
|
210
|
+
// 7. FUNZIONI GRAFICHE (Hercules Scalable)
|
|
251
211
|
// ==========================================================================
|
|
252
|
-
function updateLeewayDisplay(deg) {
|
|
253
|
-
const c = 125, px = 125/20; let w = Math.min(Math.abs(deg)*px, 125);
|
|
254
|
-
ui.leewayMask.setAttribute('x', deg >= 0 ? c : c - w); ui.leewayMask.setAttribute('width', w);
|
|
255
|
-
ui.leewayVal.textContent = `LEEWAY: ${deg.toFixed(1)}°`;
|
|
256
|
-
}
|
|
212
|
+
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)}°`; }
|
|
257
213
|
|
|
258
214
|
function manageHistory(t, v) {
|
|
259
215
|
const n = Date.now(), i = simulationMode ? SIM_SAMPLE_INTERVAL : CONFIG.graphs.realInterval;
|
|
260
216
|
if (n - store.lastUpdates[t] > i || store.histories[t].length === 0) {
|
|
261
|
-
store.histories[t].push(v); if (store.histories[t].length > CONFIG.graphs.samples) store.histories[t].shift();
|
|
262
|
-
store.lastUpdates[t] = n;
|
|
217
|
+
store.histories[t].push(v); if (store.histories[t].length > CONFIG.graphs.samples) store.histories[t].shift(); store.lastUpdates[t] = n;
|
|
263
218
|
}
|
|
264
219
|
const mode = graphModes[t];
|
|
265
220
|
const cfg = calculateScale(t, store.histories[t], mode);
|
|
266
|
-
|
|
267
221
|
const box = document.getElementById(t + '-graph').closest('.data-box');
|
|
268
222
|
if (mode === 'hercules') box.classList.add('box-hercules'); else box.classList.remove('box-hercules');
|
|
269
|
-
|
|
270
223
|
updateScaleLabels(t, cfg.min, cfg.max);
|
|
271
224
|
drawGraph(store.histories[t], t + '-graph', cfg.min, cfg.max, t === 'tws', mode === 'hercules');
|
|
272
225
|
}
|
|
@@ -274,7 +227,6 @@ function manageHistory(t, v) {
|
|
|
274
227
|
function calculateScale(type, data, mode) {
|
|
275
228
|
const s = CONFIG.scales[type] || { stdMax: 12, hercSpan: 4, step: 2 };
|
|
276
229
|
let aMin = Math.min(...data), aMax = Math.max(...data);
|
|
277
|
-
|
|
278
230
|
if (mode === 'hercules') {
|
|
279
231
|
let avg = (aMin + aMax) / 2;
|
|
280
232
|
let span = Math.max(s.hercSpan, Math.ceil(aMax - aMin));
|
|
@@ -282,8 +234,7 @@ function calculateScale(type, data, mode) {
|
|
|
282
234
|
let min = Math.max(0, Math.floor(avg - (span / 2)));
|
|
283
235
|
return { min, max: min + span };
|
|
284
236
|
} else {
|
|
285
|
-
|
|
286
|
-
return { min: 0, max: max };
|
|
237
|
+
return { min: 0, max: Math.max(s.stdMax, Math.ceil(aMax / s.step) * s.step) };
|
|
287
238
|
}
|
|
288
239
|
}
|
|
289
240
|
|
|
@@ -296,34 +247,22 @@ function drawGraph(d, id, min, max, isTws, isHercules) {
|
|
|
296
247
|
const svg = document.getElementById(id); if (!svg || d.length < 2) return;
|
|
297
248
|
const w = 200, h = 40, range = max - min;
|
|
298
249
|
let gH = ""; [0.25, 0.5, 0.75].forEach(p => { gH += `<line x1="0" y1="${h-(p*h)}" x2="${w}" y2="${h-(p*h)}" stroke="rgba(255,255,255,0.08)" stroke-width="0.5" />`; });
|
|
299
|
-
|
|
300
250
|
let pD = "", cS = "";
|
|
301
251
|
d.forEach((v, i) => {
|
|
302
|
-
const x = (i/(CONFIG.graphs.samples-1))*w, y = h-(Math.max(0,Math.min(1,(v-min)/range))*h);
|
|
303
|
-
pD += `${i===0?'M':'L'} ${x} ${y} `;
|
|
252
|
+
const x = (i/(CONFIG.graphs.samples-1))*w, y = h-(Math.max(0,Math.min(1,(v-min)/range))*h); pD += `${i===0?'M':'L'} ${x} ${y} `;
|
|
304
253
|
if (isTws && i > 0) {
|
|
305
254
|
const px = ((i-1)/(CONFIG.graphs.samples-1))*w, py = h-(Math.max(0,Math.min(1,(d[i-1]-min)/range))*h);
|
|
306
255
|
let c = "#f1c40f"; if (v >= CONFIG.graphs.reef2) c = "#e74c3c"; else if (v >= CONFIG.graphs.reef1) c = "#e67e22";
|
|
307
256
|
cS += `<line x1="${px}" y1="${py}" x2="${x}" y2="${y}" stroke="${c}" stroke-width="${isHercules?3:2}" class="${isHercules?'line-hercules':''}" />`;
|
|
308
257
|
}
|
|
309
258
|
});
|
|
310
|
-
|
|
311
|
-
const areaPath = pD + ` L ${((d.length-1)/(CONFIG.graphs.samples-1))*w} ${h} L 0 ${h} Z`;
|
|
312
259
|
const clrs = { 'stw-graph': '#2ecc71', 'sog-graph': '#f39c12', 'depth-graph': '#3498db', 'tws-graph': '#f1c40f' };
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
if (isTws) {
|
|
316
|
-
svg.innerHTML = `${gH}<path d="${areaPath}" fill="rgba(241,196,15,0.12)" stroke="none" />${cS}`;
|
|
317
|
-
} else {
|
|
318
|
-
svg.innerHTML = `${gH}<path d="${areaPath}" fill="${clrs[id]}22" stroke="none" /><path d="${pD}" class="${lClass}" fill="none" stroke="${clrs[id]}" stroke-width="1.5" />`;
|
|
319
|
-
}
|
|
260
|
+
svg.innerHTML = isTws ? `${gH}<path d="${pD + ` L ${((d.length-1)/(CONFIG.graphs.samples-1))*w} ${h} L 0 ${h} Z`}" fill="rgba(241,196,15,0.1)" />${cS}` : `${gH}<path d="${pD + ` L ${((d.length-1)/(CONFIG.graphs.samples-1))*w} ${h} L 0 ${h} Z`}" fill="${clrs[id]}22" stroke="none" /><path d="${pD}" class="${isHercules?'line-hercules':''}" fill="none" stroke="${clrs[id]}" stroke-width="1.5" />`;
|
|
320
261
|
}
|
|
321
262
|
|
|
322
263
|
// ==========================================================================
|
|
323
|
-
//
|
|
264
|
+
// 8. EVENTI E INIZIALIZZAZIONE
|
|
324
265
|
// ==========================================================================
|
|
325
|
-
|
|
326
|
-
// Cambio Scala Hercules (Doppio Click)
|
|
327
266
|
['stw', 'sog', 'tws', 'depth'].forEach(type => {
|
|
328
267
|
const el = document.getElementById(type + '-graph').closest('.data-box');
|
|
329
268
|
el.addEventListener('dblclick', (e) => {
|
|
@@ -334,35 +273,17 @@ function drawGraph(d, id, min, max, isTws, isHercules) {
|
|
|
334
273
|
});
|
|
335
274
|
});
|
|
336
275
|
|
|
337
|
-
// Fullscreen (Tocco hotspot)
|
|
338
276
|
if (ui.hotspot) {
|
|
339
277
|
ui.hotspot.addEventListener('click', (e) => {
|
|
340
|
-
e.preventDefault();
|
|
341
|
-
const doc = document.documentElement, isF = document.fullscreenElement || document.webkitFullscreenElement;
|
|
278
|
+
e.preventDefault(); const doc = document.documentElement, isF = document.fullscreenElement || document.webkitFullscreenElement;
|
|
342
279
|
if (!isF) { if (doc.requestFullscreen) doc.requestFullscreen(); else if (doc.webkitRequestFullscreen) doc.webkitRequestFullscreen(); }
|
|
343
280
|
else { if (document.exitFullscreen) document.exitFullscreen(); else if (document.webkitExitFullscreen) document.webkitExitFullscreen(); }
|
|
344
281
|
});
|
|
345
282
|
}
|
|
346
283
|
|
|
347
|
-
|
|
348
|
-
(function generateTicks() {
|
|
349
|
-
const c = document.getElementById('ticks');
|
|
350
|
-
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); } }
|
|
351
|
-
})();
|
|
352
|
-
|
|
353
|
-
// ==========================================================================
|
|
354
|
-
// 9. LANCIO APPLICAZIONE (Sincronizzato)
|
|
355
|
-
// ==========================================================================
|
|
356
|
-
async function init() {
|
|
357
|
-
// 1. Prima scarichiamo la configurazione dal server
|
|
358
|
-
await fetchServerConfig();
|
|
359
|
-
|
|
360
|
-
// 2. Poi facciamo partire tutto il resto
|
|
361
|
-
startDisplayLoop();
|
|
362
|
-
connect();
|
|
363
|
-
}
|
|
284
|
+
(function generateTicks() { 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); } } })();
|
|
364
285
|
|
|
365
|
-
|
|
286
|
+
async function init() { await fetchServerConfig(); startDisplayLoop(); connect(); }
|
|
366
287
|
window.addEventListener('load', init);
|
|
367
288
|
|
|
368
289
|
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'); }
|
|
@@ -373,11 +294,7 @@ ui.depth.closest('.data-box').addEventListener('click', (function() {
|
|
|
373
294
|
let dC = 0, lC = 0;
|
|
374
295
|
return function() {
|
|
375
296
|
const n = Date.now(); if (n - lC < 500) dC++; else dC = 1; lC = n;
|
|
376
|
-
if (dC === 3) {
|
|
377
|
-
simulationMode = !simulationMode;
|
|
378
|
-
if (simulationMode) { if (socket) socket.close(); ui.status.innerText = "SIM ATTIVO"; simInterval = setInterval(() => { const h = Math.random()*360; processIncomingData("navigation.headingTrue", degToRad(h)); processIncomingData("navigation.speedOverGround", ktsToMs(8)); }, 200); }
|
|
379
|
-
else location.reload();
|
|
380
|
-
dC = 0;
|
|
381
|
-
}
|
|
297
|
+
if (dC === 3) { simulationMode = !simulationMode; if (simulationMode) { if (socket) socket.close(); ui.status.innerText = "SIM ATTIVO"; simInterval = setInterval(() => { const h = Math.random()*360; processIncomingData("navigation.headingTrue", degToRad(h)); processIncomingData("navigation.speedOverGround", ktsToMs(8)); }, 200); } else location.reload(); dC = 0; }
|
|
382
298
|
};
|
|
383
299
|
})());
|
|
300
|
+
---Fine Codice ---
|
package/package.json
CHANGED
package/test.html
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="it">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>SignalK Config Scanner</title>
|
|
6
|
+
<style>
|
|
7
|
+
body { background: #121212; color: #fff; font-family: monospace; padding: 20px; }
|
|
8
|
+
h1 { color: #2ecc71; }
|
|
9
|
+
.log-entry { margin-bottom: 10px; padding: 10px; border-left: 3px solid #444; background: #1e1e1e; }
|
|
10
|
+
.success { border-color: #2ecc71; color: #2ecc71; }
|
|
11
|
+
.error { border-color: #e74c3c; color: #e74c3c; }
|
|
12
|
+
.pending { border-color: #f1c40f; color: #f1c40f; }
|
|
13
|
+
pre { background: #000; padding: 10px; overflow-x: auto; color: #aaa; }
|
|
14
|
+
</style>
|
|
15
|
+
</head>
|
|
16
|
+
<body>
|
|
17
|
+
<h1>SignalK Plugin Configuration Scanner</h1>
|
|
18
|
+
<p>Questo test proverà a trovare l'indirizzo esatto dove SignalK salva le tue impostazioni.</p>
|
|
19
|
+
<button id="scan-btn" style="padding: 10px 20px; cursor:pointer;">INIZIA SCANSIONE</button>
|
|
20
|
+
<div id="results" style="margin-top: 20px;"></div>
|
|
21
|
+
|
|
22
|
+
<script>
|
|
23
|
+
const results = document.getElementById('results');
|
|
24
|
+
const btn = document.getElementById('scan-btn');
|
|
25
|
+
|
|
26
|
+
const urlsToTest = [
|
|
27
|
+
'/plugins/rotevista-dash/settings',
|
|
28
|
+
'/plugins/@sailingrotevista%2frotevista-dash/settings',
|
|
29
|
+
'/signalk/v1/api/plugins/rotevista-dash/settings',
|
|
30
|
+
'/signalk/v1/api/plugins/@sailingrotevista%2frotevista-dash/settings',
|
|
31
|
+
'/signalk/v1/applicationData/user/rotevista-dash/1.0/settings'
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
function log(msg, type = 'pending', data = null) {
|
|
35
|
+
const div = document.createElement('div');
|
|
36
|
+
div.className = `log-entry ${type}`;
|
|
37
|
+
div.innerHTML = `<strong>[TEST]</strong> ${msg}`;
|
|
38
|
+
if (data) {
|
|
39
|
+
const pre = document.createElement('pre');
|
|
40
|
+
pre.innerText = JSON.stringify(data, null, 2);
|
|
41
|
+
div.appendChild(pre);
|
|
42
|
+
}
|
|
43
|
+
results.appendChild(div);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function startScan() {
|
|
47
|
+
results.innerHTML = '';
|
|
48
|
+
log("Inizio scansione percorsi...");
|
|
49
|
+
|
|
50
|
+
for (let url of urlsToTest) {
|
|
51
|
+
log(`Provo URL: ${url}...`);
|
|
52
|
+
try {
|
|
53
|
+
const response = await fetch(url);
|
|
54
|
+
if (response.ok) {
|
|
55
|
+
const data = await response.json();
|
|
56
|
+
log(`TROVATO! Risposta OK da: ${url}`, 'success', data);
|
|
57
|
+
} else {
|
|
58
|
+
log(`FALLITO (${response.status}) su: ${url}`, 'error');
|
|
59
|
+
}
|
|
60
|
+
} catch (e) {
|
|
61
|
+
log(`ERRORE DI RETE su: ${url}`, 'error');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
log("Scansione terminata.");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
btn.onclick = startScan;
|
|
68
|
+
</script>
|
|
69
|
+
</body>
|
|
70
|
+
</html>
|
|
71
|
+
---Fine Codice ---
|