Aggiunta stili CSS per Kiosk, struttura HTML per la Mappa e Riferimenti ai Sensori

• Creato un nuovo file CSS per gli stili del chiosco (kiosk) con variabili, stili per le schede (card) e animazioni.
• Aggiunto un file HTML per l'interfaccia della mappa utilizzando Mapbox, inclusi gli stili e il JavaScript per le funzionalità della mappa.
• Introdotto un file JSON per i riferimenti ai sensori, definendo percorsi ed elementi per i dati di temperatura, vento, onde, posizione, batteria, motore e sistema.

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Giuseppe Raffa
2026-04-23 16:19:11 +02:00
parent 41f33ce181
commit bb8d267cd4
85 changed files with 4293 additions and 5083 deletions

View File

@@ -0,0 +1,50 @@
const configManager = require('../../config/configManager.js');
const REALTIME_URL = process.env.REALTIME_URL;
/**
* Autentica il sensore per connetterlo al server.
* Il token viene inviato al server, che restituisce un token temporaneo per la connessione websocket.
* @returns {Promise<{socketToken: string, sensorId: string, expiresIn: number}|null>}
*/
async function authenticate() {
const SENSOR_CODE = configManager.getSensorCode();
const SENSOR_NAME = configManager.getSensorName();
if (!REALTIME_URL || !SENSOR_CODE || !SENSOR_NAME) {
console.error('[REALTIME|AUTH] REALTIME_URL, SENSOR_CODE o SENSOR_NAME non configurati');
return null;
}
try {
const response = await fetch(`${REALTIME_URL}/connect`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: SENSOR_NAME,
code: SENSOR_CODE
})
});
if (!response.ok) {
const err = await response.json().catch(() => ({}));
console.error(`[REALTIME|AUTH] auth error (${response.status}):`, err.error || 'unknown');
return null;
}
const data = await response.json();
// Server risponde { s: 'ok', t: token }
if (data.s !== 'ok' || !data.t) {
console.error('[REALTIME|AUTH] Risposta inattesa:', data);
return null;
}
console.log(`[REALTIME|AUTH] Autenticato: ${SENSOR_NAME}, token valido 5s`);
return { socketToken: data.t };
} catch (error) {
console.error(`[REALTIME|AUTH] error: ${error.message}`);
return null;
}
}
module.exports = { authenticate };

View File

@@ -0,0 +1,106 @@
const os = require('os');
const auth = require('./auth');
const socket = require('./socket');
const configManager = require('../../config/configManager.js');
let reconnectTimer = null;
let isShuttingDown = false;
/**
* Inizializza la connessione al server realtime.
* Autentica il sensore e apre la connessione WebSocket.
* In caso di disconnessione, tenta di riconnettersi.
*/
async function init() {
isShuttingDown = false;
await connectToServer();
}
/**
* Esegue il flusso di connessione: auth → websocket
*/
async function connectToServer() {
if (isShuttingDown) return;
console.log('CONNECTING......')
const result = await auth.authenticate();
console.log('AUTH RESULT:', result);
if (!result) {
scheduleReconnect();
return;
}
const connected = await socket.connect(result.socketToken, () => {
if (!isShuttingDown) {
scheduleReconnect();
}
});
if (!connected) {
scheduleReconnect();
}
}
/**
* Pianifica un tentativo di riconnessione dopo il ritardo configurato.
*/
function scheduleReconnect() {
if (reconnectTimer || isShuttingDown) return;
const RECONNECT_DELAY = configManager.getReconnectDelay();
console.log(`[REALTIME] riconnessione in ${RECONNECT_DELAY / 1000}s...`);
reconnectTimer = setTimeout(async () => {
reconnectTimer = null;
await connectToServer();
}, RECONNECT_DELAY);
}
/**
* Invia dati al server se la connessione è attiva.
* @param {Array} data - Array nel formato [timestamp, measurement, fields]
*/
function send(data) {
if (socket.isConnected()) {
socket.send(data);
}
}
/**
* Invia un oggetto raw al server (senza trasformazione [ts, _m, fields]).
* Usato per forecast_batch e altri messaggi con struttura custom.
*/
function sendRaw(obj) {
if (socket.isConnected()) {
socket.sendRaw(obj);
}
}
/**
* @returns {boolean} true se la connessione WebSocket è attiva
*/
function isConnected() {
return socket.isConnected();
}
/**
* Ferma la connessione e i tentativi di riconnessione.
*/
function stop() {
isShuttingDown = true;
if (reconnectTimer) {
clearTimeout(reconnectTimer);
reconnectTimer = null;
}
socket.close();
}
module.exports = {
init,
send,
sendIfConnected: send,
sendRaw,
isConnected,
stop
};

View File

@@ -0,0 +1,114 @@
const WebSocket = require('ws');
const os = require('os');
const { encode } = require('@msgpack/msgpack');
const SOCKET_URL = process.env.REALTIME_SOCKET_URL;
let ws = null;
let onDisconnect = null;
/**
* Apre una connessione WebSocket al server realtime usando il token temporaneo.
* @param {string} socketToken - Token temporaneo ottenuto da auth.authenticate()
* @param {Function} onClose - Callback chiamata quando la connessione si chiude
* @returns {Promise<boolean>} true se la connessione è riuscita
*/
function connect(socketToken, onClose) {
return new Promise((resolve) => {
if (!SOCKET_URL) {
console.error('[REALTIME|WS] REALTIME_SOCKET_URL non configurato nel .env');
return resolve(false);
}
onDisconnect = onClose;
try {
const wsUrl = `${SOCKET_URL}/?token=${encodeURIComponent(socketToken)}`;
ws = new WebSocket(wsUrl);
} catch (err) {
console.error('[REALTIME|WS] Errore creazione:', err.message);
return resolve(false);
}
ws.on('open', () => {
console.log('[REALTIME|WS] Connesso');
// Invia init con system uptime
const initPayload = {
_t: 'init',
uptime: Math.floor(os.uptime())
};
ws.send(encode(initPayload));
console.log('[REALTIME|WS] Init inviato:', initPayload);
resolve(true);
});
ws.on('message', () => {
// Il server non invia messaggi ai sensori per ora
});
ws.on('ping', () => {
// ws risponde automaticamente con pong
});
ws.on('error', (err) => {
console.error(`[REALTIME|WS] Errore: ${err.message}`);
resolve(false);
});
ws.on('close', (code) => {
console.log(`[REALTIME|WS] Disconnesso (code: ${code})`);
ws = null;
if (onDisconnect) onDisconnect();
});
});
}
/**
* Invia dati al server tramite WebSocket, codificati in msgpack.
* @param {Array} data - Array nel formato [timestamp, measurement, fields]
*/
function send(data) {
if (!ws || ws.readyState !== WebSocket.OPEN) return;
try {
const [timestamp, measurement, fields] = data;
const packet = { ts: timestamp, _m: measurement, ...fields };
ws.send(encode(packet));
} catch (err) {
console.error('[REALTIME|WS] Errore invio:', err.message);
}
}
/**
* Invia un oggetto raw al server, codificato in msgpack.
* A differenza di send(), non fa transform [ts, measurement, fields].
* @param {Object} obj - Oggetto da inviare direttamente
*/
function sendRaw(obj) {
if (!ws || ws.readyState !== WebSocket.OPEN) return;
try {
ws.send(encode(obj));
} catch (err) {
console.error('[REALTIME|WS] Errore invio raw:', err.message);
}
}
/**
* @returns {boolean} true se la connessione è attiva
*/
function isConnected() {
return ws !== null && ws.readyState === WebSocket.OPEN;
}
/**
* Chiude la connessione WebSocket.
*/
function close() {
onDisconnect = null;
if (ws) {
ws.close();
ws = null;
}
}
module.exports = { connect, send, sendRaw, isConnected, close };