Files
signalk-plugin/plugin/tools/kiosk/data-binder.js
Giuseppe Raffa c2c1598226 feat: Implement rulesets and layout management for kiosk plugin
- Added rulesets manager to handle various data types and updates via HTTP and WebSocket.
- Introduced layout store for managing kiosk layouts with caching and server synchronization.
- Enhanced dashboard and data routes to support new layout and ruleset features.
- Updated kiosk HTML and JavaScript to utilize new layout rendering and data binding.
- Removed obsolete map route and integrated map functionality into the new tile renderer.
- Improved Telegram commands to reflect changes in data structure and logging.
- Refactored weather fetching intervals to prevent multiple instances.
- Added SSE stream for real-time layout updates in the kiosk.
2026-05-12 10:17:54 +02:00

73 lines
2.9 KiB
JavaScript

/**
* data-binder.js — apre una singola sottoscrizione SignalK delta locale
* e smista i valori ai tile per path.
*
* Espone window.dataBinder con:
* subscribe(path, fn) → unsubscribe()
* getLatest(path) → ultimo valore visto (o null)
* onConnState(fn) → notifica connesso/disconnesso
*/
(function () {
const subs = new Map(); // path -> Set<fn>
const latest = new Map(); // path -> { value, ts }
const stateListeners = new Set();
let ws = null;
let reconnectTimer = null;
function notify(path, value) {
latest.set(path, { value, ts: Date.now() });
const ss = subs.get(path);
if (!ss) return;
for (const fn of ss) { try { fn(value); } catch (e) { console.warn(e); } }
}
function notifyState(s) { for (const fn of stateListeners) { try { fn(s); } catch (_) {} } }
function connect() {
const url = `ws://${location.host}/signalk/v1/stream?subscribe=all`;
try { ws = new WebSocket(url); }
catch (err) { console.error('[BINDER] WS create:', err); scheduleReconnect(); return; }
ws.onopen = () => { notifyState({ connected: true }); console.log('[BINDER] WS connected'); };
ws.onerror = (e) => console.warn('[BINDER] WS error', e);
ws.onclose = () => { notifyState({ connected: false }); scheduleReconnect(); };
ws.onmessage = (ev) => {
let msg;
try { msg = JSON.parse(ev.data); } catch { return; }
if (!msg.updates) return;
for (const u of msg.updates) {
if (!u.values) continue;
for (const v of u.values) {
if (!v.path) continue;
notify(v.path, v.value);
// espandi position in lat/lon per chi si iscrive a quelli
if (v.path === 'navigation.position' && v.value && typeof v.value === 'object') {
if (v.value.latitude != null) notify('navigation.position.latitude', v.value.latitude);
if (v.value.longitude != null) notify('navigation.position.longitude', v.value.longitude);
}
}
}
};
}
function scheduleReconnect() {
if (reconnectTimer) return;
reconnectTimer = setTimeout(() => { reconnectTimer = null; connect(); }, 3000);
}
function subscribe(path, fn) {
if (!subs.has(path)) subs.set(path, new Set());
subs.get(path).add(fn);
const last = latest.get(path);
if (last) { try { fn(last.value); } catch (_) {} }
return () => subs.get(path)?.delete(fn);
}
function getLatest(path) { return latest.get(path)?.value ?? null; }
function onConnState(fn) { stateListeners.add(fn); return () => stateListeners.delete(fn); }
connect();
window.dataBinder = { subscribe, getLatest, onConnState };
})();