@sailingrotevista/rotevista-dash 7.0.2 → 7.0.3

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 CHANGED
@@ -30,7 +30,7 @@ let CONFIG = {
30
30
  tws: { stdMax: 15, hercSpan: 2, step: 1 },
31
31
  depth: { stdMax: 5, hercSpan: 2, step: 1 }
32
32
  },
33
- server: { fallbackIp: "192.168.111.240:3000" }
33
+ server: { fallbackIp: "venus.local:3000" }
34
34
  };
35
35
 
36
36
  const RENDER_INTERVAL_MS = 1000;
package/index.js CHANGED
@@ -24,11 +24,12 @@ module.exports = function (app) {
24
24
  const CALM_THRESHOLD_KTS = 1.5; // Soglia di calma piatta (anello a 360°)
25
25
  const PRESSURE_FILTER_RATIO = 0.40; // Filtro di pressione dinamico (40% del picco per ignorare i cali)
26
26
 
27
- // Database dello storico in RAM sul server (Sintonizzato Pro v6.0)
27
+ // Database dello storico in RAM sul server (Sintonizzato Pro v6.0)
28
28
  let histories = { stw: [], sog: [], depth: [], tws: [], vmg: [], aws: [], twd: [] };
29
29
  let graphTempBuf = { stw: [], sog: [], depth: [], tws: [], vmg: [], aws: [], twd: [] };
30
30
  let lastUpdates = { stw: 0, sog: 0, depth: 0, tws: 0, vmg: 0, aws: 0, twd: 0 };
31
31
  let raw = {};
32
+ let lastPathProcessTimes = {}; // Registro dei timestamp per limitazione di frequenza a 1Hz
32
33
 
33
34
  // Nuovo database dedicato per gli archi storici della bussola (6 ore = 12 slot)
34
35
  let windRadarSlots = [];
@@ -136,96 +137,125 @@ module.exports = function (app) {
136
137
  app.debug(msg);
137
138
  };
138
139
 
139
- /**
140
- * processIncomingDelta: Decodifica i dati dei sensori in Knots/Meters ed esegue l'aggregazione
141
- * Gestisce l'architettura "Nativo Prima, Fallback Dopo" per il vento reale.
142
- */
143
- function processIncomingDelta(path, val) {
144
- if (val === null || val === undefined) return;
145
- raw[path] = val;
140
+ /**
141
+ * processIncomingDelta: Decodifica i dati dei sensori in Knots/Meters ed esegue l'aggregazione
142
+ * Applica un filtro passa-basso continuo a ogni pacchetto e storicizza a 1Hz con dati stabilizzati.
143
+ */
144
+ function processIncomingDelta(path, val) {
145
+ if (val === null || val === undefined) return;
146
+
147
+ const now = Date.now();
148
+ const alpha = 0.20; // Coefficiente di smoothing (Filtro passa-basso: reattività ~2 secondi)
149
+
150
+ // FILTRO PASSA-BASSO CONTINUO IN TEMPO REALE (Previene gli Spike prima della storicizzazione)
151
+ if (path === 'navigation.position') {
152
+ raw[path] = val; // Le coordinate GPS non sono soggette a filtri di smorzamento o ritardo
153
+
154
+ // Chiamata periodica Open-Meteo
155
+ if (val.latitude !== undefined && val.longitude !== undefined) {
156
+ const current30mSlot = Math.floor(now / 1800000) * 1800000;
157
+ if (current30mSlot > lastForecast30mSlot) {
158
+ fetchOpenMeteoForecast(val, current30mSlot);
159
+ }
160
+ }
161
+ return; // Esce subito
162
+ }
146
163
 
147
- const now = Date.now();
164
+ if (path.includes('angle') || path.includes('heading') || path.includes('course') || path.includes('direction') || path.includes('twd')) {
165
+ // 1. Caso Angolare (Radianti): Calcolo differenziale circolare per gestire l'oltrepasso dello 0/360 gradi
166
+ if (raw[path] !== undefined) {
167
+ let diff = Math.atan2(Math.sin(val - raw[path]), Math.cos(val - raw[path]));
168
+ raw[path] = (raw[path] + diff * alpha + Math.PI * 2) % (Math.PI * 2);
169
+ } else {
170
+ raw[path] = val;
171
+ }
172
+ } else {
173
+ // 2. Caso Lineare (Velocità e Profondità): Smorzamento continuo
174
+ if (raw[path] !== undefined) {
175
+ raw[path] = (val * alpha) + (raw[path] * (1 - alpha));
176
+ } else {
177
+ raw[path] = val;
178
+ }
179
+ }
148
180
 
149
- // 1. Cattura dei dati nativi (Se presenti, li scrive direttamente nello storico)
150
- if (path === 'navigation.position') {
151
- // Trigger allineato all'orologio: calcoliamo il confine della mezz'ora corrente dell'orologio
152
- if (val && val.latitude !== undefined && val.longitude !== undefined) {
153
- const current30mSlot = Math.floor(Date.now() / 1800000) * 1800000;
154
- // Se siamo entrati in una nuova mezz'ora di orologio dall'ultimo download, avviamo il fetch
155
- if (current30mSlot > lastForecast30mSlot) {
156
- fetchOpenMeteoForecast(val, current30mSlot);
181
+ // LIMITATORE DI FREQUENZA (RATE LIMITER) A 1HZ PER PERCORSO ATTIVO:
182
+ // La scrittura nello storico e i calcoli derivati vengono eseguiti al massimo una volta al secondo,
183
+ // leggendo il valore "raw[path]" stabilizzato continuamente dal filtro passa-basso superiore.
184
+ if (!lastPathProcessTimes[path]) lastPathProcessTimes[path] = 0;
185
+ if (now - lastPathProcessTimes[path] < 1000) {
186
+ return; // Esce subito risparmiando la CPU se il sensore ha già aggiornato nell'ultimo secondo
157
187
  }
158
- }
159
- }
160
- else if (path === 'navigation.speedThroughWater') {
161
- manageHistory('stw', val * 1.94384);
162
- }
163
- else if (path === 'navigation.speedOverGround') {
164
- manageHistory('sog', val * 1.94384);
165
- }
166
- else if (path === 'environment.depth.belowTransducer') {
167
- manageHistory('depth', val);
168
- }
169
- else if (path === 'environment.wind.speedApparent') {
170
- manageHistory('aws', val * 1.94384);
171
- }
172
- else if (path === 'environment.wind.angleApparent') {
173
- raw[path] = val; // BUG RISOLTO: Acquisizione dell'AWA mancante inserita!
174
- }
175
- else if (path === 'environment.wind.speedTrue') {
188
+ lastPathProcessTimes[path] = now;
189
+
190
+ // Da qui in poi, l'esecuzione della storia e del vento reale avviene rigorosamente a 1Hz con dati puliti:
191
+ const smoothedVal = raw[path];
192
+
193
+ if (path === 'navigation.speedThroughWater') {
194
+ manageHistory('stw', smoothedVal * 1.94384);
195
+ }
196
+ else if (path === 'navigation.speedOverGround') {
197
+ manageHistory('sog', smoothedVal * 1.94384);
198
+ }
199
+ else if (path === 'environment.depth.belowTransducer') {
200
+ manageHistory('depth', smoothedVal);
201
+ }
202
+ else if (path === 'environment.wind.speedApparent') {
203
+ manageHistory('aws', smoothedVal * 1.94384);
204
+ }
205
+ else if (path === 'environment.wind.angleApparent') {
206
+ // Già gestito e normalizzato dal filtro passa-basso superiore
207
+ }
208
+ else if (path === 'environment.wind.speedTrue') {
176
209
  lastNativeTwsTime = now; // Rilevato TWS nativo della centralina!
177
- const cleanVal = (val && typeof val === 'object' && val.value !== undefined) ? val.value : val;
178
- manageHistory('tws', cleanVal * 1.94384);
179
- }
180
- // --- DECODIFICA PRUA MAGNETICA SERVER-SIDE ---
181
- else if (path === 'navigation.headingMagnetic') {
182
- const hasTrueHdg = raw['navigation.headingTrue'] !== undefined;
183
- if (!hasTrueHdg) {
184
- const variation = raw['navigation.magneticVariation'] || 0;
185
- const cleanVal = (val && typeof val === 'object' && val.value !== undefined) ? val.value : val;
186
- raw['navigation.headingTrue'] = (cleanVal + variation + 2 * Math.PI) % (2 * Math.PI);
187
- }
188
- }
189
- else if (path === 'environment.wind.directionTrue') {
190
- lastNativeTwdTime = now; // Rilevato TWD nativo della centralina!
191
- const cleanVal = (val && typeof val === 'object' && val.value !== undefined) ? val.value : val;
192
- manageHistory('twd', cleanVal);
193
- }
210
+ manageHistory('tws', smoothedVal * 1.94384);
211
+ }
212
+ // --- DECODIFICA PRUA MAGNETICA SERVER-SIDE ---
213
+ else if (path === 'navigation.headingMagnetic') {
214
+ const hasTrueHdg = raw['navigation.headingTrue'] !== undefined;
215
+ if (!hasTrueHdg) {
216
+ const variation = raw['navigation.magneticVariation'] || 0;
217
+ raw['navigation.headingTrue'] = (smoothedVal + variation + 2 * Math.PI) % (2 * Math.PI);
218
+ }
219
+ }
220
+ else if (path === 'environment.wind.directionTrue') {
221
+ lastNativeTwdTime = now; // Rilevato TWD nativo della centralina!
222
+ manageHistory('twd', smoothedVal);
223
+ }
194
224
 
195
- // 2. Calcolo combinato di FALLBACK (Si attiva solo se la centralina non invia TWS/TWD nativi)
196
- const aws = raw["environment.wind.speedApparent"];
197
- const awa = raw["environment.wind.angleApparent"];
198
- const stw = raw["navigation.speedThroughWater"] || 0;
199
- const sog = raw["navigation.speedOverGround"] || 0;
200
- const hdg = raw["navigation.headingTrue"];
201
- const cog = raw["navigation.courseOverGroundTrue"] || 0;
202
-
203
- if (aws !== undefined && awa !== undefined) {
204
- const awsKts = aws * 1.94384;
205
- const stwKts = stw * 1.94384;
206
- const tw_water_x = awsKts * Math.cos(awa) - stwKts;
207
- const tw_water_y = awsKts * Math.sin(awa);
208
-
209
- // Calcoliamo il TWS di fallback solo se non abbiamo visto dati nativi negli ultimi 5 secondi
210
- if (now - lastNativeTwsTime > 5000) {
211
- const tws = Math.sqrt(tw_water_x * tw_water_x + tw_water_y * tw_water_y);
212
- manageHistory('tws', tws);
213
- }
225
+ // 2. Calcolo combinato di FALLBACK (Si attiva solo se la centralina non invia TWS/TWD nativi)
226
+ const aws = raw["environment.wind.speedApparent"];
227
+ const awa = raw["environment.wind.angleApparent"];
228
+ const stw = raw["navigation.speedThroughWater"] || 0;
229
+ const sog = raw["navigation.speedOverGround"] || 0;
230
+ const hdg = raw["navigation.headingTrue"];
231
+ const cog = raw["navigation.courseOverGroundTrue"] || 0;
232
+
233
+ if (aws !== undefined && awa !== undefined) {
234
+ const awsKts = aws * 1.94384;
235
+ const stwKts = stw * 1.94384;
236
+ const tw_water_x = awsKts * Math.cos(awa) - stwKts;
237
+ const tw_water_y = awsKts * Math.sin(awa);
238
+
239
+ // Calcoliamo il TWS di fallback solo se non abbiamo visto dati nativi negli ultimi 5 secondi
240
+ if (now - lastNativeTwsTime > 5000) {
241
+ const tws = Math.sqrt(tw_water_x * tw_water_x + tw_water_y * tw_water_y);
242
+ manageHistory('tws', tws);
243
+ }
214
244
 
215
- const twa = Math.atan2(tw_water_y, tw_water_x);
216
-
217
- // La VMG viene sempre calcolata a livello server poiché raramente è nativa
218
- const vmg = Math.abs(stwKts * Math.cos(twa));
219
- manageHistory('vmg', vmg);
220
-
221
- // Calcoliamo il TWD di fallback solo se non abbiamo visto dati nativi negli ultimi 5 secondi
222
- if (hdg !== undefined && (now - lastNativeTwdTime > 5000)) {
223
- const twd = (hdg + twa + 2 * Math.PI) % (2 * Math.PI);
224
- manageHistory('twd', twd); // BUG RISOLTO: Rimossa la riassegnazione di "const" che mandava in crash il server
225
- }
226
- }
227
- }
245
+ const twa = Math.atan2(tw_water_y, tw_water_x);
246
+
247
+ // La VMG viene sempre calcolata a livello server poiché raramente è nativa
248
+ const vmg = Math.abs(stwKts * Math.cos(twa));
249
+ manageHistory('vmg', vmg);
228
250
 
251
+ // Calcoliamo il TWD di fallback solo se non abbiamo visto dati nativi negli ultimi 5 secondi
252
+ if (hdg !== undefined && (now - lastNativeTwdTime > 5000)) {
253
+ const twd = (hdg + twa + 2 * Math.PI) % (2 * Math.PI);
254
+ manageHistory('twd', twd);
255
+ }
256
+ }
257
+ }
258
+
229
259
  /**
230
260
  * manageHistory: Versione Server-side dell'aggregatore matematico tattico
231
261
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sailingrotevista/rotevista-dash",
3
- "version": "7.0.2",
3
+ "version": "7.0.3",
4
4
  "description": "Wind Dashboard with navigation and course aids",
5
5
  "main": "index.js",
6
6
  "publishConfig": {
File without changes