@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 +1 -1
- package/index.js +114 -84
- package/package.json +1 -1
- /package/{sample_radar.html → debug_sample_radar.html} +0 -0
package/app.js
CHANGED
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
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
File without changes
|