@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.
Files changed (3) hide show
  1. package/app.js +48 -131
  2. package/package.json +1 -1
  3. package/test.html +71 -0
package/app.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // ==========================================================================
2
- // 1. CONFIGURAZIONE DINAMICA (Default)
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 1° mano (Arancio)
18
- reef2: 20.0, // Soglia 2° mano (Rosso)
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: { stdMax: 6, hercSpan: 4, step: 2 },
24
- sog: { stdMax: 6, hercSpan: 4, step: 2 },
25
- tws: { stdMax: 20, hercSpan: 10, step: 5 },
26
- depth: { stdMax: 10, hercSpan: 10, step: 10 }
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. CARICAMENTO CONFIGURAZIONE DAL SERVER (VERSIONE UNIVERSALE)
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}/settings`,
88
- `/plugins/${scopeID}/settings`,
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 serverConfig = await response.json();
100
-
101
- // Se abbiamo ricevuto un oggetto valido
102
- if (serverConfig && typeof serverConfig === 'object' && Object.keys(serverConfig).length > 0) {
103
-
104
- // Merge profondo dei dati
105
- if (serverConfig.alarms) CONFIG.alarms = { ...CONFIG.alarms, ...serverConfig.alarms };
106
- if (serverConfig.graphs) CONFIG.graphs = { ...CONFIG.graphs, ...serverConfig.graphs };
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 CONVERSIONI
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 avgRad = Math.atan2(sSin, sCos);
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
- // 6. MOTORE DI RENDERING
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 = "---&deg;"; el.classList.remove('unstable-data'); }
210
171
  else {
211
172
  el.innerHTML = `${obj.val.toString().padStart(3, '0')}&deg;`;
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')}&deg;`;
221
181
  if (cObj) ui.tackCog.innerHTML = `${Math.round((cObj.val - tA + 360) % 360).toString().padStart(3, '0')}&deg;`;
222
- let tStable = (hObj.stable && twObj.stable) || (curSog < CONFIG.averages.minSpeed);
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.smoothBuf) cleanBuffer(store.smoothBuf[b], CONFIG.averages.smoothWindow);
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
- // 7. CONNESSIONE SIGNALK (subscribe=self)
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) => { 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))); };
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
- // 8. FUNZIONI GRAFICHE (Hercules Mode)
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
- let max = Math.max(s.stdMax, Math.ceil(aMax / s.step) * s.step);
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
- const lClass = isHercules ? 'line-hercules' : '';
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
- // 9. EVENTI E INIZIALIZZAZIONE
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
- // Generazione Tacche SVG
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
- // Avvio al caricamento della pagina
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sailingrotevista/rotevista-dash",
3
- "version": "1.0.17",
3
+ "version": "1.0.19",
4
4
  "description": "Public Wind Dashboard with navigation and course aids",
5
5
  "main": "index.js",
6
6
  "publishConfig": {
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 ---