@sailingrotevista/rotevista-dash 5.0.6 → 5.0.8

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 +116 -27
  2. package/index.js +32 -8
  3. package/package.json +1 -1
package/app.js CHANGED
@@ -33,7 +33,7 @@ let CONFIG = {
33
33
  };
34
34
 
35
35
  const RENDER_INTERVAL_MS = 1000;
36
- const TIMEOUT_MS = 5000;
36
+ const TIMEOUT_MS = 15000;
37
37
  const SIM_SAMPLE_INTERVAL = 1000;
38
38
  const DASH_VERSION = "3.8"; // Major Update: Smart Source Locking & Breathing Hercules Scale
39
39
 
@@ -75,6 +75,8 @@ const sourceLocks = {};
75
75
  const store = {
76
76
  raw: {},
77
77
  timestamps: {},
78
+ depthProtectedActive: false, // Memoria per lo stato della protezione profondità
79
+ herculesScales: {}, // Memoria per i limiti attivi della modalità Hercules
78
80
  smoothBuf: { hdg: [], cog: [], awa: [], twa: [], twd: [] },
79
81
  longBuf: { hdg: [], cog: [], awa: [], twa: [], twd: [] },
80
82
  histories: { stw: [], sog: [], depth: [], tws: [], vmg: [], aws: [] },
@@ -925,46 +927,133 @@ function manageHistory(type, value) {
925
927
  }
926
928
 
927
929
  /**
928
- * Gestione dinamica delle scale dei grafici (Involucro Elastico e Safety Zoom)
930
+ * Gestione dinamica delle scale dei grafici (Involucro Elastico Snapped e Safety Zoom)
931
+ * Implementa lo "Snap a Griglia" basato sull'hercSpan senza padding per tutti i sensori.
929
932
  */
930
933
  function calculateScale(type, data, mode) {
931
934
  const s = CONFIG.scales[type];
932
- let aMin = Math.min(...data), aMax = Math.max(...data);
935
+ const currentVal = data[data.length - 1];
933
936
 
934
- // --- SAFETY ZOOM PER PROFONDITÀ (FONDALE BASSO) ---
935
- if (type === 'depth' && mode !== 'hercules') {
936
- const currentDepth = data[data.length - 1];
937
- const shallowThreshold = Math.max(s.stdMax, 10);
938
- if (currentDepth <= shallowThreshold) {
939
- return { min: 0, max: shallowThreshold };
937
+ // Fallback di emergenza se il buffer è momentaneamente vuoto
938
+ if (currentVal === undefined || currentVal === null) {
939
+ return { min: 0, max: s ? s.stdMax : 10 };
940
+ }
941
+
942
+ // Inizializzazione della memoria delle scale nello store se non esiste
943
+ if (!store.herculesScales) store.herculesScales = {};
944
+ if (!store.herculesScales[type]) {
945
+ store.herculesScales[type] = { min: 0, max: s ? s.stdMax : 10 };
946
+ }
947
+ let currentScale = store.herculesScales[type];
948
+
949
+ // ==========================================================================
950
+ // 1. SEZIONE PROFONDITÀ (DEDICATA A 2 MINUTI DI SICUREZZA, MINIMO FISSO A 0)
951
+ // ==========================================================================
952
+ if (type === 'depth') {
953
+ const shallowThreshold = Math.max(s.stdMax, 10); // Es. 20m
954
+ if (store.depthProtectedActive === undefined) store.depthProtectedActive = false;
955
+
956
+ const now = Date.now();
957
+ const depthSafetyWindowMs = 120000; // 2 minuti fissi per la sicurezza
958
+
959
+ // Estrazione dati reali degli ultimi 2 minuti con timestamp
960
+ const recentPoints = store.histories.depth.filter(p => (now - p.time) <= depthSafetyWindowMs);
961
+ const recentVals = recentPoints.map(p => p.val);
962
+
963
+ const localMax = recentVals.length > 0 ? Math.max(...recentVals) : currentVal;
964
+ const localMin = recentVals.length > 0 ? Math.min(...recentVals) : currentVal;
965
+
966
+ // --- 1.1 NORMALE PROFONDITÀ (CON SOGLIA STANDARD E FILTRO 2 MINUTI) ---
967
+ if (mode !== 'hercules') {
968
+ if (!store.depthProtectedActive && currentVal <= shallowThreshold) {
969
+ store.depthProtectedActive = true;
970
+ }
971
+ if (store.depthProtectedActive) {
972
+ if (localMin > shallowThreshold) {
973
+ store.depthProtectedActive = false;
974
+ }
975
+ }
976
+ if (store.depthProtectedActive) {
977
+ return { min: 0, max: shallowThreshold };
978
+ }
979
+ const maxHistorico = Math.max(...data);
980
+ return { min: 0, max: Math.max(s.stdMax, Math.ceil(maxHistorico / s.step) * s.step) };
981
+ }
982
+
983
+ // --- 1.2 HERCULES PROFONDITÀ (0 IN BASSO, SNAP SUL MAX SENZA PADDING) ---
984
+ if (mode === 'hercules') {
985
+ const roundStep = s.hercSpan; // Passo di griglia selezionato dall'utente
986
+
987
+ let targetMin = 0;
988
+ let targetMax = Math.ceil(localMax / roundStep) * roundStep;
989
+
990
+ // Impediamo una scala inferiore a 4 metri per sicurezza visiva
991
+ const absoluteMinSpan = 4;
992
+ if (targetMax < absoluteMinSpan) {
993
+ targetMax = absoluteMinSpan;
994
+ }
995
+
996
+ // Regola asimmetrica per il MAX (Espansione istantanea, contrazione a 2 minuti)
997
+ if (currentVal > currentScale.max) {
998
+ currentScale.max = targetMax;
999
+ } else {
1000
+ const allStableInTarget = recentVals.every(val => val <= targetMax);
1001
+ if (allStableInTarget) {
1002
+ currentScale.max = targetMax;
1003
+ }
1004
+ }
1005
+
1006
+ currentScale.min = 0;
1007
+ return { min: currentScale.min, max: currentScale.max };
940
1008
  }
941
1009
  }
1010
+
1011
+ // ==========================================================================
1012
+ // 2. ALTRI GRAFICI (STW, SOG, TWS): COMPORTAMENTO ADATTIVO SU TERZO DEL GRAFICO
1013
+ // ==========================================================================
942
1014
 
943
- // --- MODALITÀ HERCULES AGGIORNATA (INVOLUCRO DINAMICO ELASTICO) ---
1015
+ // --- 2.1 MODALITÀ NORMALE ---
1016
+ if (mode !== 'hercules') {
1017
+ const maxHistorico = Math.max(...data);
1018
+ return { min: 0, max: Math.max(s.stdMax, Math.ceil(maxHistorico / s.step) * s.step) };
1019
+ }
1020
+
1021
+ // --- 2.2 MODALITÀ HERCULES AD ALTO CONTRASTO (SNAP SENZA PADDING) ---
944
1022
  if (mode === 'hercules') {
945
- const padding = (type === 'stw' || type === 'sog') ? 0.5 : 1.0;
1023
+ const roundStep = s.hercSpan; // Passo di griglia (ex hercSpan)
1024
+
1025
+ const oneThirdCount = Math.max(1, Math.floor(data.length / 3));
1026
+ const recentThirdData = data.slice(-oneThirdCount);
946
1027
 
947
- let min = Math.max(0, Math.floor(aMin - padding));
948
- let max = Math.ceil(aMax + padding);
1028
+ const localMin = Math.min(...recentThirdData);
1029
+ const localMax = Math.max(...recentThirdData);
949
1030
 
950
- // Garantisce uno span minimo configurato per evitare zoom eccessivi sul rumore di fondo
951
- const currentSpan = max - min;
952
- if (currentSpan < s.hercSpan) {
953
- const diff = s.hercSpan - currentSpan;
954
- min = Math.max(0, min - Math.floor(diff / 2));
955
- max = min + s.hercSpan;
1031
+ // Applichiamo lo snap diretto ai multipli della griglia
1032
+ let targetMin = Math.max(0, Math.floor(localMin / roundStep) * roundStep);
1033
+ let targetMax = Math.ceil(localMax / roundStep) * roundStep;
1034
+
1035
+ // Se lo span calcolato è nullo (es. velocità costante), forziamo lo span minimo
1036
+ if (targetMax - targetMin === 0) {
1037
+ targetMax = targetMin + roundStep;
956
1038
  }
957
1039
 
958
- // Arrotonda la griglia numerica a step prefissati per evitare sfarfallamento visivo
959
- const roundStep = (type === 'stw' || type === 'sog') ? 0.5 : 1.0;
960
- min = Math.floor(min / roundStep) * roundStep;
961
- max = Math.ceil(max / roundStep) * roundStep;
1040
+ // APPLICAZIONE REGOLE ASIMMETRICHE ANTI-CLIPPING
1041
+ // A. Espansione ISTANTANEA in alto o in basso (sicurezza)
1042
+ if (currentVal < currentScale.min || currentVal > currentScale.max) {
1043
+ currentScale.min = Math.min(currentScale.min, targetMin);
1044
+ currentScale.max = Math.max(currentScale.max, targetMax);
1045
+ }
1046
+ // B. Contrazione RITARDATA (solo se tutto il terzo recente si è assestato nel target)
1047
+ else {
1048
+ const allStableInTarget = recentThirdData.every(val => val >= targetMin && val <= targetMax);
1049
+ if (allStableInTarget) {
1050
+ currentScale.min = targetMin;
1051
+ currentScale.max = targetMax;
1052
+ }
1053
+ }
962
1054
 
963
- return { min, max };
1055
+ return { min: currentScale.min, max: currentScale.max };
964
1056
  }
965
-
966
- // Scala Standard (Autocompressione a scatti)
967
- return { min: 0, max: Math.max(s.stdMax, Math.ceil(aMax / s.step) * s.step) };
968
1057
  }
969
1058
 
970
1059
  function updateScaleLabels(t, min, max) {
package/index.js CHANGED
@@ -127,10 +127,10 @@ module.exports = function (app) {
127
127
  default: 0.5
128
128
  },
129
129
  stabilityThreshold: {
130
- type: 'number',
131
- title: 'Steering Precision (Sensitivity)',
132
- description: "How strictly the system judges data coherence (0.0 to 1.0). Due to internal smoothing, 0.97-0.98 requires racing precision; 0.93-0.95 is ideal for cruising in waves. Below this, the display rarely alerts for instability.",
133
- default: 0.95
130
+ type: 'number',
131
+ title: 'Steering Precision (Sensitivity)',
132
+ description: "How strictly the system judges data coherence (0.0 to 1.0). Due to internal smoothing, 0.97-0.98 requires racing precision; 0.93-0.95 is ideal for cruising in waves. Below this, the display rarely alerts for instability.",
133
+ default: 0.95
134
134
  },
135
135
  stabilityBreakout: {
136
136
  type: 'number',
@@ -152,7 +152,13 @@ module.exports = function (app) {
152
152
  properties: {
153
153
  stdMax: { type: 'number', title: 'Standard Max', description: "Default top limit of the graph.", default: 12 },
154
154
  step: { type: 'number', title: 'Scale Jump', description: "Amount the scale increases when you exceed the limit.", default: 2 },
155
- hercSpan: { type: 'number', title: 'Hercules Zoom Span', description: "Width of the zoom window around your current speed.", default: 4 }
155
+ hercSpan: {
156
+ type: 'number',
157
+ title: 'Hercules Grid Step (Resolution)',
158
+ description: "Select the multiplier step for the Hercules zoom. The scale boundaries will always snap to multiples of this value.",
159
+ enum: [0.5, 1.0, 2.0, 3.0],
160
+ default: 1.0
161
+ }
156
162
  }
157
163
  },
158
164
  sog: {
@@ -161,7 +167,13 @@ module.exports = function (app) {
161
167
  properties: {
162
168
  stdMax: { type: 'number', title: 'Standard Max', default: 12 },
163
169
  step: { type: 'number', title: 'Scale Jump', default: 2 },
164
- hercSpan: { type: 'number', title: 'Hercules Zoom Span', default: 4 }
170
+ hercSpan: {
171
+ type: 'number',
172
+ title: 'Hercules Grid Step (Resolution)',
173
+ description: "Select the multiplier step for the Hercules zoom. The scale boundaries will always snap to multiples of this value.",
174
+ enum: [0.5, 1.0, 2.0, 3.0],
175
+ default: 1.0
176
+ }
165
177
  }
166
178
  },
167
179
  tws: {
@@ -170,7 +182,13 @@ module.exports = function (app) {
170
182
  properties: {
171
183
  stdMax: { type: 'number', title: 'Standard Max', default: 25 },
172
184
  step: { type: 'number', title: 'Scale Jump', default: 5 },
173
- hercSpan: { type: 'number', title: 'Hercules Zoom Span', default: 10 }
185
+ hercSpan: {
186
+ type: 'number',
187
+ title: 'Hercules Grid Step (Resolution)',
188
+ description: "Select the multiplier step for the Hercules zoom. The scale boundaries will always snap to multiples of this value.",
189
+ enum: [1, 2, 3, 5, 10],
190
+ default: 2
191
+ }
174
192
  }
175
193
  },
176
194
  depth: {
@@ -179,7 +197,13 @@ module.exports = function (app) {
179
197
  properties: {
180
198
  stdMax: { type: 'number', title: 'Standard Max', default: 20 },
181
199
  step: { type: 'number', title: 'Scale Jump', default: 10 },
182
- hercSpan: { type: 'number', title: 'Hercules Zoom Span', default: 10 }
200
+ hercSpan: {
201
+ type: 'number',
202
+ title: 'Hercules Grid Step (Resolution)',
203
+ description: "Select the multiplier step for the Hercules zoom. The scale boundaries will always snap to multiples of this value.",
204
+ enum: [1, 2, 3, 5, 10],
205
+ default: 2
206
+ }
183
207
  }
184
208
  }
185
209
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sailingrotevista/rotevista-dash",
3
- "version": "5.0.6",
3
+ "version": "5.0.8",
4
4
  "description": "Wind Dashboard with navigation and course aids",
5
5
  "main": "index.js",
6
6
  "publishConfig": {