@sailingrotevista/rotevista-dash 1.0.21 → 1.0.22

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 +33 -43
  2. package/index.js +31 -21
  3. package/package.json +1 -1
package/app.js CHANGED
@@ -1,10 +1,10 @@
1
1
  // ==========================================================================
2
- // 1. CONFIGURAZIONE E COSTANTI
2
+ // 1. CONFIGURAZIONE E DEFAULT
3
3
  // ==========================================================================
4
4
  let CONFIG = {
5
5
  alarms: { depthDanger: 2.5, depthWarning: 5.0 },
6
6
  averages: { smoothWindow: 2000, longWindow: 60000, stabilityTolerance: 2000, stabilityThreshold: 0.90, minSpeed: 0.5 },
7
- graphs: { reef1: 15.0, reef2: 20.0, realInterval: 5000, samples: 60 },
7
+ graphs: { reef1: 15.0, reef2: 20.0, historyMinutes: 5, samples: 60 },
8
8
  scales: {
9
9
  stw: { stdMax: 12, hercSpan: 4, step: 2 },
10
10
  sog: { stdMax: 12, hercSpan: 4, step: 2 },
@@ -33,14 +33,7 @@ const graphModes = {
33
33
  depth: localStorage.getItem('mode_depth') || 'standard'
34
34
  };
35
35
 
36
- const store = {
37
- raw: {},
38
- timestamps: {},
39
- smoothBuf: { hdg: [], cog: [], awa: [], twa: [], twd: [] },
40
- longBuf: { hdg: [], cog: [], awa: [], twa: [], twd: [] },
41
- histories: { stw: [], sog: [], depth: [], tws: [] },
42
- lastUpdates: { stw: 0, sog: 0, depth: 0, tws: 0 }
43
- };
36
+ const store = { raw: {}, timestamps: {}, smoothBuf: { hdg: [], cog: [], awa: [], twa: [], twd: [] }, longBuf: { hdg: [], cog: [], awa: [], twa: [], twd: [] }, histories: { stw: [], sog: [], depth: [], tws: [] }, lastUpdates: { stw: 0, sog: 0, depth: 0, tws: 0 } };
44
37
 
45
38
  const ui = {
46
39
  stw: document.getElementById('stw'), sog: document.getElementById('sog'),
@@ -56,7 +49,7 @@ const ui = {
56
49
  };
57
50
 
58
51
  // ==========================================================================
59
- // 3. CARICAMENTO CONFIGURAZIONE (SK-SERVER CONFIG)
52
+ // 3. CARICAMENTO CONFIGURAZIONE
60
53
  // ==========================================================================
61
54
  async function fetchServerConfig() {
62
55
  if (!window.location.protocol.includes("http")) return;
@@ -70,8 +63,6 @@ async function fetchServerConfig() {
70
63
  const data = await response.json();
71
64
  const actual = data.configuration || data;
72
65
  if (actual && typeof actual === 'object') {
73
-
74
- // Funzione per pulire e convertire i numeri
75
66
  const parseNumbers = (obj) => {
76
67
  for (let k in obj) {
77
68
  if (typeof obj[k] === 'object') parseNumbers(obj[k]);
@@ -79,18 +70,11 @@ async function fetchServerConfig() {
79
70
  }
80
71
  };
81
72
  parseNumbers(actual);
82
-
83
- // Merge intelligente dei blocchi
84
73
  if (actual.alarms) CONFIG.alarms = { ...CONFIG.alarms, ...actual.alarms };
85
74
  if (actual.graphs) CONFIG.graphs = { ...CONFIG.graphs, ...actual.graphs };
86
- if (actual.averaging) CONFIG.averages = { ...CONFIG.averages, ...actual.averaging }; // Nota: mappiamo averaging -> averages
87
- if (actual.scales) {
88
- for (let key in actual.scales) {
89
- CONFIG.scales[key] = { ...CONFIG.scales[key], ...actual.scales[key] };
90
- }
91
- }
92
-
93
- console.log("SUCCESS: Dashboard Config updated from Server:", CONFIG);
75
+ if (actual.averaging) CONFIG.averages = { ...CONFIG.averages, ...actual.averaging }; // Mapping averaging -> averages
76
+ if (actual.scales) { for (let key in actual.scales) { CONFIG.scales[key] = { ...CONFIG.scales[key], ...actual.scales[key] }; } }
77
+ console.log("SUCCESS: Config loaded from Server:", CONFIG);
94
78
  return;
95
79
  }
96
80
  }
@@ -99,7 +83,7 @@ async function fetchServerConfig() {
99
83
  }
100
84
 
101
85
  // ==========================================================================
102
- // 4. MATEMATICA VETTORIALE (CIRCULAR AVERAGING)
86
+ // 4. MATEMATICA VETTORIALE
103
87
  // ==========================================================================
104
88
  function radToDeg(rad) { return rad * (180 / Math.PI); }
105
89
  function degToRad(deg) { return deg * (Math.PI / 180); }
@@ -107,7 +91,6 @@ function msToKts(ms) { return ms * 1.94384; }
107
91
  function ktsToMs(kts) { return kts / 1.94384; }
108
92
  function getShortestRotation(curr, target) { let diff = (target - curr) % 360; if (diff > 180) diff -= 360; else if (diff < -180) diff += 360; return curr + diff; }
109
93
 
110
- // Calcolo media basato su componenti Seno/Coseno (Vettoriale)
111
94
  function getCircularAverageFromBuffer(bufferArray, windowMs, signed = false) {
112
95
  const now = Date.now();
113
96
  const validData = bufferArray.filter(item => (now - item.time) <= windowMs);
@@ -131,7 +114,6 @@ function processIncomingData(path, val) {
131
114
  if (path === "environment.wind.angleApparent") { store.smoothBuf.awa.push({ val: val, time: now }); store.longBuf.awa.push({ val: val, time: now }); }
132
115
  if (path === "environment.wind.angleTrueWater") { store.smoothBuf.twa.push({ val: val, time: now }); store.longBuf.twa.push({ val: val, time: now }); }
133
116
 
134
- // Calcolo o ricezione TWD (True Wind Direction)
135
117
  if (path === "navigation.headingTrue" || path === "environment.wind.angleTrueWater" || path === "environment.wind.directionTrue") {
136
118
  let twdRad = 0;
137
119
  if (path === "environment.wind.directionTrue") twdRad = val;
@@ -139,14 +121,13 @@ function processIncomingData(path, val) {
139
121
  twdRad = (store.raw["navigation.headingTrue"] + store.raw["environment.wind.angleTrueWater"]) % (2 * Math.PI);
140
122
  if (twdRad < 0) twdRad += (2 * Math.PI);
141
123
  } else return;
142
-
143
124
  store.smoothBuf.twd.push({ val: twdRad, time: now });
144
125
  store.longBuf.twd.push({ val: twdRad, time: now });
145
126
  }
146
127
  }
147
128
 
148
129
  // ==========================================================================
149
- // 6. MOTORE DI RENDERING
130
+ // 6. RENDER LOOP
150
131
  // ==========================================================================
151
132
  function startDisplayLoop() {
152
133
  renderInterval = setInterval(() => {
@@ -192,7 +173,9 @@ function startDisplayLoop() {
192
173
  let tA = twObj.val * 2;
193
174
  ui.tackHdg.innerHTML = `${Math.round((hObj.val - tA + 360) % 360).toString().padStart(3, '0')}&deg;`;
194
175
  if (cObj) ui.tackCog.innerHTML = `${Math.round((cObj.val - tA + 360) % 360).toString().padStart(3, '0')}&deg;`;
195
- 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'); }
176
+ let tStable = (hObj.stable && twObj.stable) || (curSog < CONFIG.averages.minSpeed);
177
+ if (tStable) { ui.tackHdg.classList.remove('unstable-data'); ui.tackCog.classList.remove('unstable-data'); }
178
+ else { ui.tackHdg.classList.add('unstable-data'); ui.tackCog.classList.add('unstable-data'); }
196
179
  }
197
180
  if (twdObj) { curTwdRoseRot = getShortestRotation(curTwdRoseRot, twdObj.val); ui.twdArrow.setAttribute('transform', `rotate(${curTwdRoseRot}, 20, 20)`); }
198
181
  lastAvgUIUpdate = now;
@@ -217,13 +200,20 @@ function connect() {
217
200
  }
218
201
 
219
202
  // ==========================================================================
220
- // 8. FUNZIONI GRAFICHE (Hercules Scalable)
203
+ // 8. FUNZIONI GRAFICHE (Hercules Mode)
221
204
  // ==========================================================================
222
205
  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)}°`; }
223
206
 
224
207
  function manageHistory(t, v) {
225
- const n = Date.now(), i = simulationMode ? SIM_SAMPLE_INTERVAL : CONFIG.graphs.realInterval;
226
- if (n - store.lastUpdates[t] > i || store.histories[t].length === 0) { store.histories[t].push(v); if (store.histories[t].length > CONFIG.graphs.samples) store.histories[t].shift(); store.lastUpdates[t] = n; }
208
+ const n = Date.now();
209
+ const dynamicInterval = (CONFIG.graphs.historyMinutes * 60000) / CONFIG.graphs.samples;
210
+ const interval = simulationMode ? SIM_SAMPLE_INTERVAL : dynamicInterval;
211
+
212
+ if (n - store.lastUpdates[t] > interval || store.histories[t].length === 0) {
213
+ store.histories[t].push(v);
214
+ if (store.histories[t].length > CONFIG.graphs.samples) store.histories[t].shift();
215
+ store.lastUpdates[t] = n;
216
+ }
227
217
  const mode = graphModes[t];
228
218
  const cfg = calculateScale(t, store.histories[t], mode);
229
219
  const box = document.getElementById(t + '-graph').closest('.data-box');
@@ -254,7 +244,14 @@ function updateScaleLabels(t, min, max) {
254
244
  function drawGraph(d, id, min, max, isTws, isHercules) {
255
245
  const svg = document.getElementById(id); if (!svg || d.length < 2) return;
256
246
  const w = 200, h = 40, range = max - min || 1;
257
- 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" />`; });
247
+ let grids = "";
248
+ [0.25, 0.5, 0.75].forEach(p => { grids += `<line x1="0" y1="${h-(p*h)}" x2="${w}" y2="${h-(p*h)}" stroke="rgba(255,255,255,0.08)" stroke-width="0.5" />`; });
249
+
250
+ const minutes = CONFIG.graphs.historyMinutes;
251
+ for (let m = 1; m < minutes; m++) {
252
+ const x = w - (m / minutes) * w;
253
+ grids += `<line x1="${x}" y1="0" x2="${x}" y2="${h}" stroke="rgba(255,255,255,0.05)" stroke-width="0.5" />`;
254
+ }
258
255
 
259
256
  let pD = "", cS = "";
260
257
  d.forEach((v, i) => {
@@ -266,15 +263,9 @@ function drawGraph(d, id, min, max, isTws, isHercules) {
266
263
  }
267
264
  });
268
265
 
269
- const areaPath = pD + ` L ${((d.length-1)/(CONFIG.graphs.samples-1))*w} ${h} L 0 ${h} Z`;
270
266
  const clrs = { 'stw-graph': '#2ecc71', 'sog-graph': '#f39c12', 'depth-graph': '#3498db', 'tws-graph': '#f1c40f' };
271
- const lClass = isHercules ? 'line-hercules' : '';
272
-
273
- if (isTws) {
274
- svg.innerHTML = `${gH}<path d="${areaPath}" fill="rgba(241,196,15,0.1)" stroke="none" />${cS}`;
275
- } else {
276
- 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" />`;
277
- }
267
+ const aP = pD + ` L ${((d.length-1)/(CONFIG.graphs.samples-1))*w} ${h} L 0 ${h} Z`;
268
+ svg.innerHTML = isTws ? `${grids}<path d="${aP}" fill="rgba(241,196,15,0.12)" stroke="none" />${cS}` : `${grids}<path d="${aP}" fill="${clrs[id]}22" stroke="none" /><path d="${pD}" class="${isHercules?'line-hercules':''}" fill="none" stroke="${clrs[id]}" stroke-width="1.5" />`;
278
269
  }
279
270
 
280
271
  // ==========================================================================
@@ -302,10 +293,9 @@ if (ui.hotspot) {
302
293
  async function init() { await fetchServerConfig(); startDisplayLoop(); connect(); }
303
294
  window.addEventListener('load', init);
304
295
 
305
- function checkDepthAlarm(d) { ui.depth.classList.remove('alarm-warning', 'alarm-danger'); if (d < CONFIG.alarms.depthDanger) { ui.depth.classList.add('alarm-danger'); playBingBing(); } else if (d < CONFIG.alarms.depthWarning) ui.depth.classList.add('alarm-warning'); }
296
+ 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'); }
306
297
  function playBingBing() { if (!audioCtx) return; const n = Date.now(); if (n - lastAlarmTime < 3000) return; lastAlarmTime = n; function b(f, s) { const o = audioCtx.createOscillator(); const g = audioCtx.createGain(); o.connect(g); g.connect(audioCtx.destination); o.frequency.value = f; g.gain.setValueAtTime(0.1, s); g.gain.exponentialRampToValueAtTime(0.01, s + 0.4); o.start(s); o.stop(s + 0.5); } b(880, audioCtx.currentTime); b(880, audioCtx.currentTime + 0.6); }
307
298
 
308
- // Simulatore (Triple click su Depth)
309
299
  ui.depth.closest('.data-box').addEventListener('click', (function() {
310
300
  let dC = 0, lC = 0;
311
301
  return function() {
package/index.js CHANGED
@@ -2,6 +2,7 @@ module.exports = function (app) {
2
2
  const plugin = {};
3
3
  plugin.id = 'rotevista-dash';
4
4
  plugin.name = 'Rotevista Dash Configuration';
5
+ plugin.description = 'Configure boat-specific parameters for the Dashboard';
5
6
 
6
7
  plugin.start = function (options) { };
7
8
  plugin.stop = function () { };
@@ -13,62 +14,71 @@ module.exports = function (app) {
13
14
  alarms: {
14
15
  type: 'object',
15
16
  title: 'Depth Alarms (meters)',
17
+ description: "qui un commento",
16
18
  properties: {
17
- depthDanger: { type: 'number', title: 'Danger Threshold (Red + Sound)', default: 2.5 },
18
- depthWarning: { type: 'number', title: 'Warning Threshold (Yellow)', default: 5.0 }
19
+ depthDanger: { type: 'number', title: 'Danger Threshold (Red + Sound)', description: "qui un commento", default: 2.5 },
20
+ depthWarning: { type: 'number', title: 'Warning Threshold (Yellow)', description: "qui un commento", default: 5.0 }
19
21
  }
20
22
  },
21
23
  graphs: {
22
24
  type: 'object',
23
- title: 'Wind Reef Alerts (TWS Knots)',
25
+ title: 'Graph & Reef Alerts',
26
+ description: "qui un commento",
24
27
  properties: {
25
- reef1: { type: 'number', title: '1st Reef Threshold (Orange Line)', default: 15.0 },
26
- reef2: { type: 'number', title: '2nd Reef Threshold (Red Line)', default: 20.0 }
28
+ reef1: { type: 'number', title: '1st Reef Threshold (Orange)', description: "qui un commento", default: 15.0 },
29
+ reef2: { type: 'number', title: '2nd Reef Threshold (Red)', description: "qui un commento", default: 20.0 },
30
+ historyMinutes: { type: 'number', title: 'Graph History Duration (minutes)', description: "qui un commento", default: 5 }
27
31
  }
28
32
  },
29
33
  averaging: {
30
34
  type: 'object',
31
35
  title: 'Averaging & Stability',
36
+ description: "qui un commento",
32
37
  properties: {
33
- longWindow: { type: 'number', title: 'Long Average Window (ms)', default: 60000 },
34
- smoothWindow: { type: 'number', title: 'Pointer Smoothing Window (ms)', default: 2000 },
35
- minSpeed: { type: 'number', title: 'Min Speed for Stability (knots)', default: 0.5 }
38
+ longWindow: { type: 'number', title: 'Long Average Window (ms)', description: "qui un commento", default: 60000 },
39
+ smoothWindow: { type: 'number', title: 'Pointer Smoothing Window (ms)', description: "qui un commento", default: 2000 },
40
+ minSpeed: { type: 'number', title: 'Min Speed for Stability (knots)', description: "qui un commento", default: 0.5 }
36
41
  }
37
42
  },
38
43
  scales: {
39
44
  type: 'object',
40
- title: 'Graph Scale Configurations',
45
+ title: 'Graph Scale Configurations (Standard Max, Hercules Span, Step)',
46
+ description: "qui un commento",
41
47
  properties: {
42
48
  stw: {
43
49
  type: 'object', title: 'STW (Speed Through Water)',
50
+ description: "qui un commento",
44
51
  properties: {
45
- stdMax: { type: 'number', title: 'Standard Mode Max Value', default: 12 },
46
- hercSpan: { type: 'number', title: 'Hercules Mode Zoom Span', default: 4 },
47
- step: { type: 'number', title: 'Rounding Step', default: 2 }
52
+ stdMax: { type: 'number', title: 'Standard Max', description: "qui un commento", default: 12 },
53
+ hercSpan: { type: 'number', title: 'Hercules Span', description: "qui un commento", default: 4 },
54
+ step: { type: 'number', title: 'Rounding Step', description: "qui un commento", default: 2 }
48
55
  }
49
56
  },
50
57
  sog: {
51
58
  type: 'object', title: 'SOG (Speed Over Ground)',
59
+ description: "qui un commento",
52
60
  properties: {
53
- stdMax: { type: 'number', title: 'Standard Mode Max Value', default: 12 },
54
- hercSpan: { type: 'number', title: 'Hercules Mode Zoom Span', default: 4 },
55
- step: { type: 'number', title: 'Rounding Step', default: 2 }
61
+ stdMax: { type: 'number', title: 'Standard Max', description: "qui un commento", default: 12 },
62
+ hercSpan: { type: 'number', title: 'Hercules Span', description: "qui un commento", default: 4 },
63
+ step: { type: 'number', title: 'Rounding Step', description: "qui un commento", default: 2 }
56
64
  }
57
65
  },
58
66
  tws: {
59
67
  type: 'object', title: 'TWS (True Wind Speed)',
68
+ description: "qui un commento",
60
69
  properties: {
61
- stdMax: { type: 'number', title: 'Standard Mode Max Value', default: 25 },
62
- hercSpan: { type: 'number', title: 'Hercules Mode Zoom Span', default: 10 },
63
- step: { type: 'number', title: 'Rounding Step', default: 5 }
70
+ stdMax: { type: 'number', title: 'Standard Max', description: "qui un commento", default: 25 },
71
+ hercSpan: { type: 'number', title: 'Hercules Span', description: "qui un commento", default: 10 },
72
+ step: { type: 'number', title: 'Rounding Step', description: "qui un commento", default: 5 }
64
73
  }
65
74
  },
66
75
  depth: {
67
76
  type: 'object', title: 'Depth',
77
+ description: "qui un commento",
68
78
  properties: {
69
- stdMax: { type: 'number', title: 'Standard Mode Max Value', default: 20 },
70
- hercSpan: { type: 'number', title: 'Hercules Mode Zoom Span', default: 10 },
71
- step: { type: 'number', title: 'Rounding Step', default: 10 }
79
+ stdMax: { type: 'number', title: 'Standard Max', description: "qui un commento", default: 20 },
80
+ hercSpan: { type: 'number', title: 'Hercules Span', description: "qui un commento", default: 10 },
81
+ step: { type: 'number', title: 'Rounding Step', description: "qui un commento", default: 10 }
72
82
  }
73
83
  }
74
84
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sailingrotevista/rotevista-dash",
3
- "version": "1.0.21",
3
+ "version": "1.0.22",
4
4
  "description": "Public Wind Dashboard with navigation and course aids",
5
5
  "main": "index.js",
6
6
  "publishConfig": {