@sailingrotevista/rotevista-dash 6.0.13 → 6.1.1

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 (4) hide show
  1. package/app.js +39 -54
  2. package/debug.html +131 -48
  3. package/index.js +5 -5
  4. package/package.json +1 -1
package/app.js CHANGED
@@ -22,12 +22,12 @@ let CONFIG = {
22
22
  minSpeed: 0.5,
23
23
  stabilityBreakout: 15
24
24
  },
25
- graphs: { reef1: 10, reef2: 15, historyMinutes: 10, samples: 60 },
25
+ graphs: { reef1: 5, reef2: 10, historyMinutes: 10, samples: 60 },
26
26
  scales: {
27
27
  stw: { stdMax: 4, hercSpan: 2, step: 1 },
28
28
  sog: { stdMax: 4, hercSpan: 2, step: 1 },
29
29
  tws: { stdMax: 15, hercSpan: 2, step: 1 },
30
- depth: { stdMax: 8, hercSpan: 1, step: 1 }
30
+ depth: { stdMax: 5, hercSpan: 2, step: 1 }
31
31
  },
32
32
  server: { fallbackIp: "192.168.111.240:3000" }
33
33
  };
@@ -328,7 +328,8 @@ function computeTrueWind() {
328
328
  const tw_ground_x = aws * Math.cos(awa) - sog * Math.cos(drift), tw_ground_y = aws * Math.sin(awa) - sog * Math.sin(drift);
329
329
  const tws_ground = Math.sqrt(tw_ground_x * tw_ground_x + tw_ground_y * tw_ground_y);
330
330
 
331
- const now = Date.now();
331
+ // Usiamo il tempo esatto di arrivo del pacchetto del vento per la coerenza dei buffer
332
+ const now = store.timestamps["environment.wind.speedApparent"] || Date.now();
332
333
  store.raw["environment.wind.speedTrue"] = tws_water;
333
334
  if (tws_water > 0.05) {
334
335
  const twa = Math.atan2(tw_water_y, tw_water_x);
@@ -372,8 +373,9 @@ function getSourcePriorityScore(sourceName) {
372
373
  return 30; // Punteggio standard per sorgenti sconosciute
373
374
  }
374
375
 
375
- function processIncomingData(path, val, source) {
376
- const now = Date.now();
376
+ function processIncomingData(path, val, source, timeMs) {
377
+ // Usiamo il tempo reale del pacchetto del server per eliminare lo sfasamento
378
+ const now = timeMs || Date.now();
377
379
  const score = getSourcePriorityScore(source);
378
380
 
379
381
  // Gestione dello Smart Lock
@@ -891,6 +893,12 @@ async function fetchServerHistory() {
891
893
  store.histories[key] = data[key];
892
894
  }
893
895
  }
896
+ // --- SILLABAZIONE STRATEGICA DELLA BUSSOLA METEO (TWD) ---
897
+ // Se il server ci invia lo storico del TWD, lo inseriamo direttamente nella memoria a lungo termine
898
+ if (data.twd && data.twd.length > 0) {
899
+ store.longBuf.twd = data.twd;
900
+ console.log(`📈 Memoria strategica TWD sincronizzata dal server (${data.twd.length} punti).`);
901
+ }
894
902
  console.log("📈 Storico dei grafici pre-popolato caricato dal server.");
895
903
  }
896
904
  } catch (err) {
@@ -1440,6 +1448,8 @@ function connect() {
1440
1448
  const d = JSON.parse(e.data);
1441
1449
  if (d.updates) {
1442
1450
  d.updates.forEach(u => {
1451
+ // Sincronizzazione dell'orologio sul tempo reale del server (NMEA/GPS)
1452
+ const timeMs = u.timestamp ? new Date(u.timestamp).getTime() : Date.now();
1443
1453
  let sourceLabel = "Unknown";
1444
1454
  if (u.$source) {
1445
1455
  sourceLabel = u.$source;
@@ -1452,7 +1462,7 @@ function connect() {
1452
1462
  }
1453
1463
 
1454
1464
  if (u.values) {
1455
- u.values.forEach(v => processIncomingData(v.path, v.value, sourceLabel));
1465
+ u.values.forEach(v => processIncomingData(v.path, v.value, sourceLabel, timeMs));
1456
1466
  }
1457
1467
  });
1458
1468
  }
@@ -1491,56 +1501,31 @@ window.addEventListener('contextmenu', e => e.preventDefault(), true);
1491
1501
  async function init() {
1492
1502
  loadDashboardState();
1493
1503
 
1494
- // Proviamo un primo caricamento configurazioni
1495
- await fetchServerConfig();
1496
- startDisplayLoop();
1497
- connect();
1498
-
1499
- setInterval(watchConfigChanges, 10000);
1504
+ // Rileviamo se siamo sul Mac tramite file:// (Ambiente di sviluppo locale)
1505
+ const isLocalFile = (window.location.protocol === 'file:');
1500
1506
 
1501
- // ==========================================================================
1502
- // RICONNESSIONE RAPIDA AL RISVEGLIO (VISIBILITY WATCHDOG)
1503
- // ==========================================================================
1504
-
1505
- // Quando sblocchi l'iPad o riapri la scheda, sincronizziamo in modo intelligente
1506
- document.addEventListener('visibilitychange', () => {
1507
- if (document.visibilityState === 'visible') {
1508
- // 1. Se la connessione è già attiva e sana, scarichiamo solo lo storico senza disconnetterci
1509
- if (socket && socket.readyState === WebSocket.OPEN) {
1510
- console.log("⏰ Schermo sbloccato: connessione attiva, sincronizzazione dello storico.");
1511
- fetchServerHistory().then(() => {
1512
- ['stw', 'sog', 'depth', 'tws'].forEach(refreshGraph);
1513
- }).catch(err => {});
1514
- }
1515
- // 2. Se la connessione è persa o morta, forzatura riconnessione rapida
1516
- else {
1517
- console.log("⏰ Schermo sbloccato: connessione assente, forzatura riconnessione rapida.");
1518
- if (socket) {
1519
- try {
1520
- socket.close();
1521
- } catch (e) {
1522
- connect();
1523
- }
1524
- } else {
1525
- connect();
1526
- }
1527
- }
1528
- }
1529
- });
1507
+ // 1. CARICAMENTO STORICO GRAFICI REALI DAL CERBO GX
1508
+ try {
1509
+ await fetchServerHistory();
1510
+ } catch (err) {
1511
+ console.warn("⚠️ Impossibile caricare lo storico reale dal server.");
1512
+ }
1530
1513
 
1531
- // Rileva se il dispositivo è andato in sospensione misurando il ritardo dei secondi
1532
- let lastHeartbeat = Date.now();
1533
- setInterval(() => {
1534
- const now = Date.now();
1535
- const diff = now - lastHeartbeat;
1536
- lastHeartbeat = now;
1537
-
1538
- // Se passano più di 6 secondi tra un ciclo e l'altro (invece di 1 secondo),
1539
- // significa che il PC era in sospensione. Avviamo il risveglio.
1540
- if (diff > 6000) {
1541
- handleWakeUp();
1542
- }
1543
- }, 1000);
1514
+ // 2. CARICAMENTO CONFIGURAZIONI REALI (Bypassato su Mac per preservare i tuoi test!)
1515
+ if (!isLocalFile) {
1516
+ await fetchServerConfig();
1517
+ } else {
1518
+ // Mantiene la CONFIG locale di app.js per farti fare le prove delle scale sul Mac
1519
+ console.log("🎮 Esecuzione locale file://: utilizzo delle calibrazioni di CONFIG locali di debug.");
1520
+ }
1521
+
1522
+ startDisplayLoop();
1523
+ connect(); // Si collegherà in tempo reale al WebSocket reale della barca
1524
+
1525
+ // Controlla le modifiche di configurazione sul Cerbo solo se non siamo sul Mac via file://
1526
+ if (!isLocalFile) {
1527
+ setInterval(watchConfigChanges, 10000);
1528
+ }
1544
1529
  }
1545
1530
 
1546
1531
 
package/debug.html CHANGED
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>SignalK Data Debugger & Logger CSV</title>
6
+ <title>SignalK Data Debugger & Logger CSV (Pro v6.0)</title>
7
7
  <style>
8
8
  body {
9
9
  background-color: #121212;
@@ -19,9 +19,8 @@
19
19
  h1 { color: #4CAF50; border-bottom: 1px solid #333; padding-bottom: 10px; margin-top: 0; flex-shrink: 0; }
20
20
 
21
21
  /* Controlli in alto */
22
- .controls { margin-bottom: 20px; display: flex; gap: 10px; align-items: center; flex-shrink: 0; flex-wrap: wrap; }
23
- input[type="text"], select { padding: 8px; background: #222; color: #fff; border: 1px solid #444; }
24
- input[type="text"] { width: 200px; }
22
+ .controls { margin-bottom: 15px; display: flex; gap: 10px; align-items: center; flex-shrink: 0; flex-wrap: wrap; }
23
+ input[type="text"] { padding: 8px; background: #222; color: #fff; border: 1px solid #444; width: 200px; }
25
24
  button { padding: 8px 15px; background: #4CAF50; color: white; border: none; cursor: pointer; font-weight: bold; border-radius: 4px; }
26
25
  button:hover { opacity: 0.8; }
27
26
  button.stop { background: #f44336; }
@@ -30,6 +29,41 @@
30
29
  #status { font-weight: bold; color: #f44336; margin-left: auto; }
31
30
  #status.online { color: #4CAF50; }
32
31
 
32
+ /* Pannello dei filtri avanzati */
33
+ .filter-container {
34
+ background: #1a1a1a;
35
+ border: 1px solid #333;
36
+ border-radius: 8px;
37
+ padding: 15px;
38
+ margin-bottom: 20px;
39
+ flex-shrink: 0;
40
+ }
41
+ .presets {
42
+ margin: 10px 0;
43
+ display: flex;
44
+ gap: 10px;
45
+ flex-wrap: wrap;
46
+ }
47
+ .checkbox-grid {
48
+ display: grid;
49
+ grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
50
+ gap: 8px;
51
+ margin-top: 12px;
52
+ border-top: 1px solid #222;
53
+ padding-top: 10px;
54
+ }
55
+ .checkbox-item {
56
+ display: flex;
57
+ align-items: center;
58
+ gap: 8px;
59
+ font-size: 11px;
60
+ color: #ccc;
61
+ cursor: pointer;
62
+ }
63
+ .checkbox-item input {
64
+ cursor: pointer;
65
+ }
66
+
33
67
  /* Layout a due pannelli (Sopra Tabella, Sotto Log) */
34
68
  .panels-container {
35
69
  display: flex;
@@ -47,18 +81,31 @@
47
81
  border-radius: 8px;
48
82
  padding: 10px;
49
83
  overflow-x: auto;
84
+ max-height: 250px;
85
+ overflow-y: auto;
50
86
  }
51
- table { width: 100%; border-collapse: collapse; font-size: 14px; }
52
- th, td { border: 1px solid #333; padding: 8px; text-align: left; }
53
- th { background-color: #222; color: #aaa; text-transform: uppercase; position: sticky; top: 0; }
87
+ table {
88
+ width: 100%;
89
+ border-collapse: collapse;
90
+ font-size: 13px;
91
+ table-layout: fixed; /* BLOCCO RIGIDO DELLE COLONNE (Evita ridimensionamenti continui) */
92
+ }
93
+ th, td {
94
+ border: 1px solid #333;
95
+ padding: 6px 8px;
96
+ text-align: left;
97
+ overflow: hidden;
98
+ word-wrap: break-word; /* Forza l'andamento a capo dei testi lunghi dentro le colonne fisse */
99
+ }
100
+ th { background-color: #222; color: #aaa; text-transform: uppercase; position: sticky; top: 0; z-index: 10; }
54
101
  .val-cell { min-width: 120px; }
55
102
 
56
103
  /* Stili Testo Tabella */
57
- .value { color: #64ffda; font-weight: bold; font-size: 16px; }
58
- .unit { color: #888; font-size: 12px; margin-left: 5px; }
59
- .source { color: #ffb74d; font-size: 12px; word-break: break-all; }
60
- .time { color: #90caf9; font-size: 12px; }
61
- .raw { color: #777; font-size: 11px; display: block; margin-top: 4px; }
104
+ .value { color: #64ffda; font-weight: bold; font-size: 15px; }
105
+ .unit { color: #888; font-size: 11px; margin-left: 5px; }
106
+ .source { color: #ffb74d; font-size: 11px; word-break: break-all; }
107
+ .time { color: #90caf9; font-size: 11px; }
108
+ .raw { color: #777; font-size: 10px; display: block; margin-top: 2px; }
62
109
 
63
110
  /* Pannello Inferiore: Log Sequenziale in Formato Testo (simil-CSV) */
64
111
  .log-panel {
@@ -78,7 +125,7 @@
78
125
  justify-content: space-between;
79
126
  align-items: center;
80
127
  border-radius: 8px 8px 0 0;
81
- font-size: 14px;
128
+ font-size: 13px;
82
129
  font-weight: bold;
83
130
  color: #aaa;
84
131
  }
@@ -112,26 +159,29 @@
112
159
  </head>
113
160
  <body>
114
161
 
115
- <h1>SignalK Data Debugger & Logger CSV</h1>
162
+ <h1>SignalK Data Debugger & Logger CSV (Pro v6.0)</h1>
116
163
 
117
164
  <div class="controls">
118
165
  <label>Server IP:</label>
119
166
  <input type="text" id="server-ip" value="192.168.111.240:3000">
120
167
  <button id="btn-connect" onclick="toggleConnection()">Connetti</button>
121
-
122
- <span style="margin-left: 20px; color:#aaa;">Filtra Log:</span>
123
- <select id="log-filter">
124
- <option value="all">Tutti i dati monitorati</option>
125
- <option value="navigation.position">Solo Posizione (GPS)</option>
126
- <option value="navigation.speedOverGround">Solo SOG (Velocità sul fondo)</option>
127
- <option value="navigation.courseOverGroundTrue">Solo COG (Rotta sul fondo)</option>
128
- <option value="wind">Solo Vento (TWS/TWA/AWS/AWA)</option>
129
- <option value="depth">Solo Profondità</option>
130
- </select>
131
-
132
168
  <span id="status">DISCONNESSO</span>
133
169
  </div>
134
170
 
171
+ <!-- AREA FILTRI AVANZATA CON CHECKBOX E PRESET -->
172
+ <div class="filter-container">
173
+ <span style="font-weight: bold; color: #4CAF50; font-size: 13px;">Seleziona percorsi da scrivere nel Log CSV:</span>
174
+ <div class="presets">
175
+ <button class="secondary" onclick="applyPreset('all')">Seleziona Tutto</button>
176
+ <button class="secondary" style="background-color: #2c3e50;" onclick="applyPreset('twd')">Analisi Bussola Meteo (TWD/Vento/Rotta)</button>
177
+ <button class="secondary" style="background-color: #0088cc;" onclick="applyPreset('gps')">Solo GPS / Rotte</button>
178
+ <button class="secondary" onclick="applyPreset('none')">Deseleziona Tutto</button>
179
+ </div>
180
+ <div id="path-checkboxes" class="checkbox-grid">
181
+ <!-- Generate via JS -->
182
+ </div>
183
+ </div>
184
+
135
185
  <div class="panels-container">
136
186
 
137
187
  <!-- PANNELLO TABELLA RIASSUNTIVA -->
@@ -139,10 +189,11 @@
139
189
  <table>
140
190
  <thead>
141
191
  <tr>
142
- <th width="30%">Path (Percorso)</th>
143
- <th width="20%">Valore Convertito</th>
144
- <th width="30%">Sorgente (Sensore - Dettagliato)</th>
145
- <th width="20%">Ultimo Ricevuto</th>
192
+ <!-- BLOCCO RIGIDO DELLE LARGHEZZE DELLE COLONNE -->
193
+ <th width="25%">Path (Percorso)</th>
194
+ <th width="31.5%">Valore Convertito</th>
195
+ <th width="31.5%">Sorgente (Sensore - Dettagliato)</th>
196
+ <th width="12%">Ultimo Ricevuto</th>
146
197
  </tr>
147
198
  </thead>
148
199
  <tbody id="data-table">
@@ -180,6 +231,7 @@
180
231
  "environment.wind.angleApparent",
181
232
  "environment.wind.speedTrue",
182
233
  "environment.wind.angleTrueWater",
234
+ "environment.wind.directionTrue",
183
235
  "environment.depth.belowTransducer"
184
236
  ];
185
237
 
@@ -192,37 +244,69 @@
192
244
  const radToDeg = (rad) => rad * (180 / Math.PI);
193
245
  const msToKts = (ms) => ms * 1.94384;
194
246
 
195
- // Inizializza la Tabella Riassuntiva
247
+ // Inizializza la Tabella Riassuntiva e genera le Checkbox
196
248
  const tbody = document.getElementById('data-table');
249
+ const checkboxGrid = document.getElementById('path-checkboxes');
250
+
197
251
  pathsToWatch.forEach(path => {
252
+ const safeId = path.replace(/\./g, '-');
253
+
254
+ // 1. Creazione righe Tabella
198
255
  const tr = document.createElement('tr');
199
- tr.id = `row-${path.replace(/\./g, '-')}`;
200
-
256
+ tr.id = `row-${safeId}`;
201
257
  tr.innerHTML = `
202
258
  <td><strong>${path}</strong></td>
203
- <td id="val-${path.replace(/\./g, '-')}" class="val-cell"><span class="value">---</span></td>
204
- <td id="src-${path.replace(/\./g, '-')}"><span class="source">---</span></td>
205
- <td id="time-${path.replace(/\./g, '-')}"><span class="time">---</span></td>
259
+ <td id="val-${safeId}" class="val-cell"><span class="value">---</span></td>
260
+ <td id="src-${safeId}"><span class="source">---</span></td>
261
+ <td id="time-${safeId}"><span class="time">---</span></td>
206
262
  `;
207
263
  tbody.appendChild(tr);
264
+
265
+ // 2. Creazione Checkbox
266
+ const label = document.createElement('label');
267
+ label.className = 'checkbox-item';
268
+ label.innerHTML = `
269
+ <input type="checkbox" id="chk-${safeId}" checked>
270
+ <span>${path}</span>
271
+ `;
272
+ checkboxGrid.appendChild(label);
208
273
  });
209
274
 
210
275
  const csvHeader = "Timestamp,Path,Value,Unit,Source,IsSpike";
211
276
  document.getElementById('log-container').value = csvHeader + "\n";
212
277
 
278
+ // =========================================================
279
+ // PRESET RAPIDI DI SELEZIONE
280
+ // =========================================================
281
+ function applyPreset(preset) {
282
+ pathsToWatch.forEach(path => {
283
+ const safeId = path.replace(/\./g, '-');
284
+ const chk = document.getElementById(`chk-${safeId}`);
285
+ if (!chk) return;
286
+
287
+ if (preset === 'all') {
288
+ chk.checked = true;
289
+ } else if (preset === 'none') {
290
+ chk.checked = false;
291
+ } else if (preset === 'gps') {
292
+ // Solo GPS, SOG e COG
293
+ const isGps = path.includes('position') || path === 'navigation.speedOverGround' || path === 'navigation.courseOverGroundTrue';
294
+ chk.checked = isGps;
295
+ } else if (preset === 'twd') {
296
+ // Analisi strategica bussola vento ( Heading, COG, AWA, TWA, TWD, TWS, AWS )
297
+ const isTwd = path.includes('wind') || path === 'navigation.headingTrue' || path === 'navigation.courseOverGroundTrue';
298
+ chk.checked = isTwd;
299
+ }
300
+ });
301
+ }
302
+
213
303
  // =========================================================
214
304
  // ESTRAZIONE AVANZATA DELLA SORGENTE (GPS / NMEA)
215
305
  // =========================================================
216
306
  function extractSourceIdentifier(update) {
217
- // 1. Cerca il campo standard moderno $source (es. "can0.GP" o "dbus.gps")
218
- if (update.$source) {
219
- return update.$source;
220
- }
221
-
222
- // 2. Se non presente, analizza l'oggetto o la stringa source tradizionale
307
+ if (update.$source) return update.$source;
223
308
  if (update.source) {
224
309
  if (typeof update.source === 'object') {
225
- // Estrae label, nome talker o identificativo bus hardware se disponibili
226
310
  return update.source.label ||
227
311
  update.source.talker ||
228
312
  update.source.src ||
@@ -230,7 +314,6 @@
230
314
  }
231
315
  return String(update.source);
232
316
  }
233
-
234
317
  return "Unknown-Source";
235
318
  }
236
319
 
@@ -272,7 +355,6 @@
272
355
 
273
356
  if (data.updates) {
274
357
  data.updates.forEach(update => {
275
- // Cattura l'identificativo sorgente con la nuova logica avanzata
276
358
  const sourceName = extractSourceIdentifier(update);
277
359
  const timeISO = new Date(update.timestamp).toISOString();
278
360
  const timeUI = new Date(update.timestamp).toLocaleTimeString() + '.' + new Date(update.timestamp).getMilliseconds().toString().padStart(3, '0');
@@ -331,7 +413,7 @@
331
413
  lastValues[path] = formattedVal;
332
414
  }
333
415
 
334
- } else if (path.includes("angle") || path.includes("heading") || path.includes("course")) {
416
+ } else if (path.includes("angle") || path.includes("heading") || path.includes("course") || path.includes("direction")) {
335
417
  formattedVal = radToDeg(rawValue).toFixed(2);
336
418
  unit = "deg";
337
419
 
@@ -357,9 +439,10 @@
357
439
  // 1. Aggiorna la Tabella in alto
358
440
  updateTableRow(path, formattedVal, unit, rawValue, source, timeUI);
359
441
 
360
- // 2. Aggiungi riga al Log CSV (rispettando il filtro della Dropdown)
361
- const filter = document.getElementById('log-filter').value;
362
- if (filter === "all" || path.includes(filter)) {
442
+ // 2. Aggiungi riga al Log CSV (Solo ed esclusivamente se la checkbox di questo percorso è spuntata!)
443
+ const safeId = path.replace(/\./g, '-');
444
+ const chk = document.getElementById(`chk-${safeId}`);
445
+ if (chk && chk.checked) {
363
446
  addLogEntry(timeISO, path, formattedVal, unit, source, isSpike);
364
447
  }
365
448
  }
package/index.js CHANGED
@@ -31,11 +31,11 @@ module.exports = function (app) {
31
31
  currentConfig = options;
32
32
  app.debug(`${plugin.name} started/updated with new options`);
33
33
 
34
- // Reset dello storico al riavvio del plugin per evitare incoerenze
35
- histories = { stw: [], sog: [], depth: [], tws: [], vmg: [], aws: [] };
36
- graphTempBuf = { stw: [], sog: [], depth: [], tws: [], vmg: [], aws: [] };
37
- lastUpdates = { stw: 0, sog: 0, depth: 0, tws: 0, vmg: 0, aws: 0 };
38
- raw = {};
34
+ // Reset dello storico al riavvio del plugin per evitare incoerenze (Sintonizzato Pro v6.0)
35
+ histories = { stw: [], sog: [], depth: [], tws: [], vmg: [], aws: [], twd: [] };
36
+ graphTempBuf = { stw: [], sog: [], depth: [], tws: [], vmg: [], aws: [], twd: [] };
37
+ lastUpdates = { stw: 0, sog: 0, depth: 0, tws: 0, vmg: 0, aws: 0, twd: 0 };
38
+ raw = {};
39
39
 
40
40
  // 2. Registra le rotte API solo la prima volta (Abilitate per CORS remoto)
41
41
  if (!routeRegistered) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sailingrotevista/rotevista-dash",
3
- "version": "6.0.13",
3
+ "version": "6.1.1",
4
4
  "description": "Wind Dashboard with navigation and course aids",
5
5
  "main": "index.js",
6
6
  "publishConfig": {