@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.
- package/app.js +33 -43
- package/index.js +31 -21
- package/package.json +1 -1
package/app.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// ==========================================================================
|
|
2
|
-
// 1. CONFIGURAZIONE E
|
|
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,
|
|
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
|
|
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 }; //
|
|
87
|
-
if (actual.scales) {
|
|
88
|
-
|
|
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
|
|
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.
|
|
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')}°`;
|
|
194
175
|
if (cObj) ui.tackCog.innerHTML = `${Math.round((cObj.val - tA + 360) % 360).toString().padStart(3, '0')}°`;
|
|
195
|
-
|
|
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
|
|
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()
|
|
226
|
-
|
|
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
|
|
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
|
|
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(
|
|
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: '
|
|
25
|
+
title: 'Graph & Reef Alerts',
|
|
26
|
+
description: "qui un commento",
|
|
24
27
|
properties: {
|
|
25
|
-
reef1: { type: 'number', title: '1st Reef Threshold (Orange
|
|
26
|
-
reef2: { type: 'number', title: '2nd Reef Threshold (Red
|
|
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
|
|
46
|
-
hercSpan: { type: 'number', title: 'Hercules
|
|
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
|
|
54
|
-
hercSpan: { type: 'number', title: 'Hercules
|
|
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
|
|
62
|
-
hercSpan: { type: 'number', title: 'Hercules
|
|
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
|
|
70
|
-
hercSpan: { type: 'number', title: 'Hercules
|
|
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
|
}
|