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,73 @@
/**
* Gestore centralizzato della configurazione del plugin.
* Questa soluzione permette di leggere i valori di configurazione dinamicamente,
* quindi cambiano in tempo reale quando l'utente li modifica dalle impostazioni del plugin.
*/
let pluginOptions = {};
/**
* Inizializza il ConfigManager con le opzioni del plugin.
* Deve essere chiamato all'avvio del plugin.
* @param {Object} options - Le opzioni passate da Signal K al plugin
*/
function init(options) {
pluginOptions = options || {};
console.log('[CONFIG] ConfigManager inizializzato');
}
/**
* Ottiene il Telegram Bot Token
*/
function getTelegramToken() {
return process.env.TELEGRAM_BOT_TOKEN || '';
}
/**
* Ottiene il codice sensore
*/
function getSensorCode() {
return pluginOptions.sensor_code || '';
}
/**
* Ottiene il nome sensore
*/
function getSensorName() {
return pluginOptions.sensor_name || '';
}
/**
* Ottiene l'intervallo di invio dati (in millisecondi, converte da secondi)
*/
function getSendInterval() {
const seconds = pluginOptions.sensor_interval || process.env.SEND_INTERVAL;
return (typeof seconds === 'number' ? seconds : parseInt(seconds)) * 1000 || 60000;
}
/**
* Ottiene il ritardo di riconnessione (in millisecondi, converte da secondi)
*/
function getReconnectDelay() {
const seconds = pluginOptions.reconnect_delay || process.env.RECONNECT_DELAY;
return (typeof seconds === 'number' ? seconds : parseInt(seconds)) * 1000 || 5000;
}
/**
* Ottiene un valore di configurazione generico
* @param {string} key - La chiave della configurazione
* @param {*} defaultValue - Il valore di default
*/
function get(key, defaultValue = null) {
return pluginOptions[key] !== undefined ? pluginOptions[key] : defaultValue;
}
module.exports = {
init,
getTelegramToken,
getSensorCode,
getSensorName,
getSendInterval,
getReconnectDelay,
get
};

171
plugin/config/skFlow.js Normal file
View File

@@ -0,0 +1,171 @@
let skApp = null;
/**
* Inizializza il modulo con l'istanza dell'app Signal K.
* Da chiamare una sola volta nel plugin.start()
* @param {Object} app - l'istanza dell'applicazione Signal K
*/
function init(app) {
skApp = app;
}
/**
* Pubblica un set di dati nel data browser di Signal K tramite i delta.
* @param {Object} data - Oggetto JSON dove le chiavi sono i percorsi e i valori sono i dati da pubblicare
*/
function publish(data) {
//TODO: Controlla se serve aggiungere typeof skApp.handleMessage !== 'function' (controlla che esista la funzione handleMessage, ma in teoria esiste sempre)
if (!skApp) {
console.error('[SKFLOW] skApp non inizializzato')
return;
}
if (!data || typeof data !== 'object') {
console.error('[SKFLOW] Dati non validi')
return;
}
const values = Object.entries(data).map(([path, value]) => {
return {
path: path,
value: value
};
});
//La funzione non continua se non ci sono dati
//TODO: Controllare se serve davvero, non dovrebbe interrompersi già al check di data?
if (values.length === 0) return;
// Viene creato un "Delta Update" con l'ID del plugin 'meb.plugin' e l'array di valori.
skApp.handleMessage('meb.plugin', {
updates: [
{
values: values
}
]
});
}
/**
* Ottieni i dati dal Data-Browser di Signal K
* @param {String} path - Il path Signal K
* @returns {*} Il dato
*/
function get(path) {
if (!skApp) {
return null;
}
const valObj = skApp.getSelfPath(path);
return valObj ? valObj.value : null;
}
/**
* Ottieni tutti i dati nel databrowser di Signal K che corrispondono ad un ID o ad una sorgente specifica.
* @param {String} source - Il parametro da confrontare con il sorgente ($source o source) o ID del dato.
* @returns {Object} Un oggetto contenente path e valori trovati.
*/
function getBySource(source) {
if (!skApp) return {};
const results = {};
const self = skApp.signalk?.self || skApp.signalk?.retrieve()?.vessels?.[skApp.selfId] || {};
if (!self || Object.keys(self).length === 0) {
console.log('[SKFLOW] Nessun dato trovato nel databrowser');
return results;
}
const traverse = (obj, path = '') => {
if (!obj || typeof obj !== 'object') return;
// Se l'oggetto ha una proprietà 'value', verifichiamo la sorgente
if (Object.prototype.hasOwnProperty.call(obj, 'value')) {
const hasSource = obj.$source === source || obj.source === source || obj.id === source;
if (hasSource) {
results[path] = obj.value;
}
}
// Esplora i sotto-oggetti escludendo chiavi di sistema che non sono percorsi SK
const skip = ['value', 'timestamp', '$source', 'source', 'meta', 'sentence', 'talker'];
for (const key in obj) {
if (skip.includes(key)) continue;
const subPath = path ? `${path}.${key}` : key;
traverse(obj[key], subPath);
}
};
traverse(self);
return results;
}
/**
* Ottieni tutti i dati nel databrowser di Signal K il cui path inizia con la stringa specificata.
* @param {String} filterPath - La stringa con cui deve iniziare il path (es. "custom.plugin").
* @returns {Object} Un oggetto contenente path e valori trovati.
*/
function getWithFilter(filterPath) {
if (!skApp) return {};
const results = {};
const self = skApp.signalk?.self || skApp.signalk?.retrieve()?.vessels?.[skApp.selfId] || {};
if (!self || Object.keys(self).length === 0) {
return results;
}
const traverse = (obj, path = '') => {
if (!obj || typeof obj !== 'object') return;
// Se l'oggetto ha una proprietà 'value', verifichiamo se il path corrisponde al filtro
if (Object.prototype.hasOwnProperty.call(obj, 'value')) {
if (path.startsWith(filterPath)) {
results[path] = obj.value;
}
}
// Esplora i sotto-oggetti escludendo chiavi di sistema
const skip = ['value', 'timestamp', '$source', 'source', 'meta', 'sentence', 'talker'];
for (const key in obj) {
if (skip.includes(key)) continue;
const subPath = path ? `${path}.${key}` : key;
// Ottimizzazione: se `subPath` non inizia con `filterPath` E `filterPath` non inizia con `subPath`,
// possiamo evitare di scendere in rami completamente irrilevanti.
// (es. filter = "environment." e subPath = "navigation." -> skippa)
if (!subPath.startsWith(filterPath) && !filterPath.startsWith(subPath)) continue;
traverse(obj[key], subPath);
}
};
traverse(self);
return results;
}
/**
* Ottieni un oggetto con path e valori per una lista specifica di path.
* @param {Array<String>} data - Un array contenente i path di Signal K da recuperare.
* @returns {Object} Un oggetto JSON con elementi path: value.
*/
function getFrom(data) {
if (!Array.isArray(data)) return {};
const results = {};
for (const path of data) {
results[path] = get(path);
}
return results;
}
module.exports = {
init,
publish,
get,
getBySource,
getWithFilter,
getFrom
};

View File

@@ -0,0 +1,53 @@
module.exports = {
type: 'object',
properties: { // impostazioni
wthr_update_interval: {
type: 'number',
title: 'Aggiornamento meteo (in secondi)',
description: 'Imposta ogni quanto verranno aggiornati i dati meteo attuali',
default: 60, //1m
},
wthr_longterm_interval: {
type: 'number',
title: 'Aggiornamenti Previsioni (in minuti)',
description: 'Imposta ogni quanti minuti verranno aggiornate le previsioni meteo fino a 7 ore',
default: 60, //1h
},
telemetry_log_interval: {
type: 'number',
title: 'Registrazione dei dati (in secondi)',
description: 'Imposta ogni quanto la telemetria della barca verrà registrata',
default: 10, //10sec
},
telegam_token: {
type: 'string',
title: 'Telegram Bot Token',
description: 'Inserisci il token del tuo bot Telegram per ricevere notifiche e interagire con la tua barca da remoto',
default: '',
},
sensor_code: {
type: 'string',
title: 'Sensore',
description: 'Inserisci un codice identificativo per inviare i dati al server',
default: '',
},
sensor_name: {
type: 'string',
title: 'Nome Sensore',
description: 'Inserisci un nome per il tuo sensore, che verrà visualizzato nel server',
default: '',
},
sensor_interval: {
type: 'number',
title: 'Aggiornamento dati (in secondi)',
description: 'Imposta ogni quanto i dati del sensore verranno inviati al server',
default: 60, //1m
},
reconnect_delay: {
type: 'number',
title: 'Ritardo di riconnessione (in secondi)',
description: 'Imposta il ritardo prima di tentare una nuova connessione al server',
default: 10, //10sec
}
}
}