@sailingrotevista/rotevista-dash 1.0.21 → 1.0.23
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 +78 -20
- 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,119 @@ module.exports = function (app) {
|
|
|
13
14
|
alarms: {
|
|
14
15
|
type: 'object',
|
|
15
16
|
title: 'Depth Alarms (meters)',
|
|
17
|
+
description: "Configure safety thresholds for depth monitoring.",
|
|
16
18
|
properties: {
|
|
17
|
-
depthDanger: {
|
|
18
|
-
|
|
19
|
+
depthDanger: {
|
|
20
|
+
type: 'number',
|
|
21
|
+
title: 'Danger Threshold (Red + Sound)',
|
|
22
|
+
description: "Critical depth level. When depth is below this value, the display turns red and an audible alarm is triggered.",
|
|
23
|
+
default: 2.5
|
|
24
|
+
},
|
|
25
|
+
depthWarning: {
|
|
26
|
+
type: 'number',
|
|
27
|
+
title: 'Warning Threshold (Yellow)',
|
|
28
|
+
description: "Shallow water margin. When depth is below this value, the display turns yellow as a safety warning.",
|
|
29
|
+
default: 5.0
|
|
30
|
+
}
|
|
19
31
|
}
|
|
20
32
|
},
|
|
21
33
|
graphs: {
|
|
22
34
|
type: 'object',
|
|
23
|
-
title: '
|
|
35
|
+
title: 'Graph & Reef Alerts',
|
|
36
|
+
description: "General settings for graph sampling and tactical wind alerts.",
|
|
24
37
|
properties: {
|
|
25
|
-
reef1: {
|
|
26
|
-
|
|
38
|
+
reef1: {
|
|
39
|
+
type: 'number',
|
|
40
|
+
title: '1st Reef Threshold (Orange)',
|
|
41
|
+
description: "TWS knots at which the wind graph turns orange, indicating it's time to prepare for the first reef.",
|
|
42
|
+
default: 15.0
|
|
43
|
+
},
|
|
44
|
+
reef2: {
|
|
45
|
+
type: 'number',
|
|
46
|
+
title: '2nd Reef Threshold (Red)',
|
|
47
|
+
description: "TWS knots at which the wind graph turns red, indicating urgent reefing is required.",
|
|
48
|
+
default: 20.0
|
|
49
|
+
},
|
|
50
|
+
historyMinutes: {
|
|
51
|
+
type: 'number',
|
|
52
|
+
title: 'Graph History Duration (minutes)',
|
|
53
|
+
description: "Total timeframe shown in the sparklines. Vertical grid lines will automatically mark each elapsed minute.",
|
|
54
|
+
default: 5
|
|
55
|
+
}
|
|
27
56
|
}
|
|
28
57
|
},
|
|
29
58
|
averaging: {
|
|
30
59
|
type: 'object',
|
|
31
60
|
title: 'Averaging & Stability',
|
|
61
|
+
description: "Fine-tune data smoothing and maneuver detection.",
|
|
32
62
|
properties: {
|
|
33
|
-
longWindow: {
|
|
34
|
-
|
|
35
|
-
|
|
63
|
+
longWindow: {
|
|
64
|
+
type: 'number',
|
|
65
|
+
title: 'Long Average Window (ms)',
|
|
66
|
+
description: "Time buffer used for 'MEAN' values (e.g., 60000ms = 1 min). Higher values provide more stability but slower reaction.",
|
|
67
|
+
default: 60000
|
|
68
|
+
},
|
|
69
|
+
smoothWindow: {
|
|
70
|
+
type: 'number',
|
|
71
|
+
title: 'Pointer Smoothing Window (ms)',
|
|
72
|
+
description: "Buffer for gauge needles and pointers. Removes sensor jitter while maintaining real-time responsiveness.",
|
|
73
|
+
default: 2000
|
|
74
|
+
},
|
|
75
|
+
minSpeed: {
|
|
76
|
+
type: 'number',
|
|
77
|
+
title: 'Min Speed for Stability (knots)',
|
|
78
|
+
description: "SOG threshold below which stability alerts (blinking orange) are suppressed to avoid GPS noise while docked.",
|
|
79
|
+
default: 0.5
|
|
80
|
+
}
|
|
36
81
|
}
|
|
37
82
|
},
|
|
38
83
|
scales: {
|
|
39
84
|
type: 'object',
|
|
40
85
|
title: 'Graph Scale Configurations',
|
|
86
|
+
description: "Customize how scales adapt to your boat's performance in both Standard and Hercules modes.",
|
|
41
87
|
properties: {
|
|
42
88
|
stw: {
|
|
43
89
|
type: 'object', title: 'STW (Speed Through Water)',
|
|
44
90
|
properties: {
|
|
45
|
-
stdMax: {
|
|
46
|
-
|
|
47
|
-
|
|
91
|
+
stdMax: {
|
|
92
|
+
type: 'number', title: 'Standard Mode Max',
|
|
93
|
+
description: "The initial top limit of the graph (base 0) during normal navigation.",
|
|
94
|
+
default: 12
|
|
95
|
+
},
|
|
96
|
+
step: {
|
|
97
|
+
type: 'number', title: 'Rounding Step',
|
|
98
|
+
description: "The fixed increment used when speed exceeds the Max (e.g., scale jumps from 0-12 to 0-14, 0-16).",
|
|
99
|
+
default: 2
|
|
100
|
+
},
|
|
101
|
+
hercSpan: {
|
|
102
|
+
type: 'number', title: 'Hercules Zoom Span',
|
|
103
|
+
description: "The minimum knots window centered on current speed. Smaller values increase 'zoom' on small variations.",
|
|
104
|
+
default: 4
|
|
105
|
+
}
|
|
48
106
|
}
|
|
49
107
|
},
|
|
50
108
|
sog: {
|
|
51
109
|
type: 'object', title: 'SOG (Speed Over Ground)',
|
|
52
110
|
properties: {
|
|
53
|
-
stdMax: { type: 'number', title: 'Standard Mode Max
|
|
54
|
-
|
|
55
|
-
|
|
111
|
+
stdMax: { type: 'number', title: 'Standard Mode Max', description: "Initial top limit for the base-0 scale.", default: 12 },
|
|
112
|
+
step: { type: 'number', title: 'Rounding Step', description: "Scale jump interval to keep labels tidy.", default: 2 },
|
|
113
|
+
hercSpan: { type: 'number', title: 'Hercules Zoom Span', description: "Minimum window amplitude during active zoom.", default: 4 }
|
|
56
114
|
}
|
|
57
115
|
},
|
|
58
116
|
tws: {
|
|
59
117
|
type: 'object', title: 'TWS (True Wind Speed)',
|
|
60
118
|
properties: {
|
|
61
|
-
stdMax: { type: 'number', title: 'Standard Mode Max
|
|
62
|
-
|
|
63
|
-
|
|
119
|
+
stdMax: { type: 'number', title: 'Standard Mode Max', description: "Maximum wind speed shown in standard view (base 0).", default: 25 },
|
|
120
|
+
step: { type: 'number', title: 'Rounding Step', description: "Incremental jump for wind scales (usually 5 or 10 knots).", default: 5 },
|
|
121
|
+
hercSpan: { type: 'number', title: 'Hercules Zoom Span', description: "Minimum knots window for high-detail gust monitoring.", default: 10 }
|
|
64
122
|
}
|
|
65
123
|
},
|
|
66
124
|
depth: {
|
|
67
125
|
type: 'object', title: 'Depth',
|
|
68
126
|
properties: {
|
|
69
|
-
stdMax: { type: 'number', title: 'Standard Mode Max
|
|
70
|
-
|
|
71
|
-
|
|
127
|
+
stdMax: { type: 'number', title: 'Standard Mode Max', description: "Default maximum depth for the graph scale.", default: 20 },
|
|
128
|
+
step: { type: 'number', title: 'Rounding Step', description: "Gradual increment steps for deep water navigation.", default: 10 },
|
|
129
|
+
hercSpan: { type: 'number', title: 'Hercules Zoom Span', description: "Minimum meters window to highlight bottom profile changes.", default: 10 }
|
|
72
130
|
}
|
|
73
131
|
}
|
|
74
132
|
}
|