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:
50
plugin/cores/realtime/auth.js
Normal file
50
plugin/cores/realtime/auth.js
Normal 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 };
|
||||
106
plugin/cores/realtime/core.js
Normal file
106
plugin/cores/realtime/core.js
Normal 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
|
||||
};
|
||||
114
plugin/cores/realtime/socket.js
Normal file
114
plugin/cores/realtime/socket.js
Normal 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 };
|
||||
Reference in New Issue
Block a user