Files
signalk-plugin/plugin/cores/logs.local.js
Giuseppe Raffa bb8d267cd4 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>
2026-04-23 16:19:11 +02:00

251 lines
6.5 KiB
JavaScript

const fs = require('fs').promises;
const fsSync = require('fs');
const pth = require('path');
const os = require('os');
const skFlow = require('../config/skFlow');
const realtime = require('./realtime/core');
const logsDirectory = pth.join(__dirname, '../../data/logs/');
// Intervallo di scrittura fisso: 1 secondo
const WRITE_INTERVAL = 1000;
// Stato della sessione attiva
let session = null;
let writeInterval = null;
// Paths da registrare (impostati da init)
let logPaths = [];
/**
* Risolutori speciali per path che non sono direttamente accessibili via skFlow.get().
* Per ogni path speciale, definisce una funzione che restituisce il valore.
*/
const SPECIAL_RESOLVERS = {
'navigation.position.latitude': () => {
const pos = skFlow.get('navigation.position');
return pos?.latitude ?? null;
},
'navigation.position.longitude': () => {
const pos = skFlow.get('navigation.position');
return pos?.longitude ?? null;
},
'system.uptime': () => Math.floor(process.uptime())
};
/**
* Risolve il valore di un path, gestendo i casi speciali.
* @param {String} path - il path da risolvere
* @returns {*} il valore
*/
function resolveValue(path) {
// Controlla se c'e un risolutore speciale
if (SPECIAL_RESOLVERS[path]) {
return SPECIAL_RESOLVERS[path]();
}
// Path standard: leggi dal databrowser Signal K
return skFlow.get(path);
}
/**
* Inizializza i path da registrare.
* @param {Array<String>} paths - array di path da rules.js LOG_PATHS
*/
function init(paths) {
logPaths = paths;
console.log(`[LOGS] Inizializzati ${paths.length} path`);
}
/**
* Assicura che la cartella logs esista
*/
async function ensureDir() {
try {
await fs.mkdir(logsDirectory, { recursive: true });
} catch (e) {}
}
/**
* Avvia la registrazione: crea un nuovo file CSV e inizia a scrivere ogni secondo.
* @param {String} name - nome del file (opzionale, default: data/ora corrente)
*/
async function startRecording(name) {
// Se c'e gia una sessione attiva, fermala prima
if (session) {
await stopRecording();
}
if (!name) {
const now = new Date();
name = now.toISOString().replace(/[:.]/g, '-');
}
if (logPaths.length === 0) {
console.warn('[LOGS] Nessun path configurato, la registrazione non catturera dati');
}
await ensureDir();
// Header CSV: timestamp + tutti i path
const header = ['timestamp', ...logPaths].join(',') + '\n';
const filePath = pth.join(logsDirectory, `${name}.csv`);
await fs.writeFile(filePath, header);
session = {
name: name,
paths: logPaths,
startTime: new Date(),
elements: 0,
filePath: filePath
};
// Scrivi ogni secondo
writeInterval = setInterval(() => {
writeLog();
}, WRITE_INTERVAL);
console.log(`[LOGS] Registrazione avviata: ${name} (${logPaths.length} colonne, intervallo ${WRITE_INTERVAL}ms)`);
return session;
}
/**
* Scrive una riga nel CSV e invia i dati al server via WebSocket.
*/
async function writeLog() {
if (!session) return;
try {
const timestamp = new Date().toISOString();
// Risolvi tutti i valori
const fields = {};
const csvValues = [];
for (const path of session.paths) {
const val = resolveValue(path);
fields[path] = val;
// Formatta per CSV
if (val === null || val === undefined) {
csvValues.push('');
} else if (typeof val === 'object') {
csvValues.push(JSON.stringify(val).replace(/,/g, ';'));
} else {
csvValues.push(val);
}
}
// Scrivi riga CSV nel file locale
const row = [timestamp, ...csvValues].join(',') + '\n';
await fs.appendFile(session.filePath, row);
session.elements++;
// Invia al server via WebSocket (se connesso)
if (realtime.isConnected()) {
realtime.send([Date.now(), 'logs', fields]);
}
} catch (error) {
console.error('[LOGS] Errore scrittura:', error.message);
}
}
/**
* Interrompe la registrazione e chiude il file.
*/
async function stopRecording() {
if (writeInterval) {
clearInterval(writeInterval);
writeInterval = null;
}
if (session) {
console.log(`[LOGS] Registrazione fermata: ${session.name} (${session.elements} righe)`);
session = null;
}
}
/**
* Ottieni i dati del file come stringa CSV.
* @param {String} name - il nome del file (senza estensione)
* @returns {String|null}
*/
async function getLog(name) {
try {
const filePath = pth.join(logsDirectory, `${name}.csv`);
const content = await fs.readFile(filePath, 'utf-8');
return content;
} catch (error) {
console.error('[LOGS] Errore lettura log:', error.message);
return null;
}
}
/**
* Ottieni il percorso del file CSV.
* @param {String} name - il nome del file (senza estensione)
* @returns {String|null}
*/
function getLogFile(name) {
const filePath = pth.join(logsDirectory, `${name}.csv`);
if (fsSync.existsSync(filePath)) {
return filePath;
}
return null;
}
/**
* Ottieni la lista di tutti i file di log disponibili.
* @returns {Array}
*/
async function listLogs() {
await ensureDir();
try {
const files = await fs.readdir(logsDirectory);
const csvFiles = files.filter(f => f.endsWith('.csv'));
const result = [];
for (const file of csvFiles) {
const filePath = pth.join(logsDirectory, file);
const stat = await fs.stat(filePath);
result.push({
name: file.replace('.csv', ''),
filename: file,
size: (stat.size / (1024 * 1024)).toFixed(2),
created: stat.birthtime,
modified: stat.mtime
});
}
return result.sort((a, b) => b.modified - a.modified);
} catch (error) {
console.error('[LOGS] Errore lista log:', error.message);
return [];
}
}
/**
* Ottieni informazioni sulla sessione di registrazione attiva.
* @returns {Object|null}
*/
function getSession() {
if (!session) return null;
return {
name: session.name,
paths: session.paths,
startTime: session.startTime,
elements: session.elements,
delay: WRITE_INTERVAL
};
}
module.exports = {
init,
startRecording,
stopRecording,
getLog,
getLogFile,
getSession,
listLogs
};