Migra dal codice salvato in locale al codice condiviso
This commit is contained in:
105
plugin/datasetModels/datasetCore.js
Normal file
105
plugin/datasetModels/datasetCore.js
Normal file
@@ -0,0 +1,105 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
// Coda di scrittura per gestire backpressure
|
||||
let writeQueue = [];
|
||||
let isDraining = false;
|
||||
|
||||
/**
|
||||
* Inizializza il dataset e lo prepara per essere salvato.
|
||||
*
|
||||
* @param {String[]} headers Un array di stringhe che rappresentano i tipi di dati.
|
||||
* @param {WriteStream} streamer Lo stream di scrittura del file.
|
||||
* @returns {boolean} True se l'inizializzazione ha successo
|
||||
*/
|
||||
function datasetInit(headers, streamer) {
|
||||
if (!streamer || streamer.destroyed) {
|
||||
console.error('[DatasetCore] Stream non valido per inizializzazione');
|
||||
return false;
|
||||
}
|
||||
if (!Array.isArray(headers) || headers.length === 0) {
|
||||
console.error('[DatasetCore] Headers non validi');
|
||||
return false;
|
||||
}
|
||||
|
||||
writeQueue = [];
|
||||
isDraining = false;
|
||||
|
||||
streamer.write(headers.join(',') + '\n');
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggiunge una riga di dati al dataset con gestione backpressure.
|
||||
*
|
||||
* @param {Object} data I dati da scrivere
|
||||
* @param {String[]} headers Gli header delle colonne
|
||||
* @param {WriteStream} streamer Lo stream di scrittura
|
||||
* @returns {boolean} True se la scrittura è andata a buon fine
|
||||
*/
|
||||
function appendData(data, headers, streamer) {
|
||||
if (!streamer || streamer.destroyed) {
|
||||
console.error('[DatasetCore] Stream non disponibile o distrutto');
|
||||
return false;
|
||||
}
|
||||
if (!data || typeof data !== 'object') {
|
||||
console.warn('[DatasetCore] Dati non validi, skip scrittura');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Escape valori che contengono virgole o newline per CSV valido
|
||||
const escapeCSV = (val) => {
|
||||
if (val === undefined || val === null) return '';
|
||||
const str = String(val);
|
||||
if (str.includes(',') || str.includes('\n') || str.includes('"')) {
|
||||
return `"${str.replace(/"/g, '""')}"`;
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
||||
const row = headers.map(header => escapeCSV(data[header])).join(',');
|
||||
|
||||
// Gestione backpressure con coda
|
||||
const canWrite = streamer.write(row + '\n');
|
||||
|
||||
if (!canWrite) {
|
||||
if (!isDraining) {
|
||||
isDraining = true;
|
||||
console.warn('[DatasetCore] Buffer saturo, attendo drain...');
|
||||
streamer.once('drain', () => {
|
||||
isDraining = false;
|
||||
// Processa coda pendente
|
||||
while (writeQueue.length > 0 && !streamer.destroyed) {
|
||||
const pendingRow = writeQueue.shift();
|
||||
if (!streamer.write(pendingRow + '\n')) {
|
||||
writeQueue.unshift(pendingRow);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// Aggiungi alla coda solo se non troppo piena (max 1000 entries)
|
||||
if (writeQueue.length < 1000) {
|
||||
writeQueue.push(row);
|
||||
} else {
|
||||
console.error('[DatasetCore] Coda piena, scarto dati');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ottiene la dimensione della coda di scrittura pendente
|
||||
* @returns {number} Numero di righe in attesa
|
||||
*/
|
||||
function getPendingWrites() {
|
||||
return writeQueue.length;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
datasetInit,
|
||||
appendData,
|
||||
getPendingWrites
|
||||
};
|
||||
274
plugin/datasetModels/datasetUtils.js
Normal file
274
plugin/datasetModels/datasetUtils.js
Normal file
@@ -0,0 +1,274 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* Searches for a directory. If not found, creates it.
|
||||
* @param {string} dirPath - The absolute or relative path to the directory.
|
||||
* @returns {string} - The absolute path to the directory.
|
||||
*/
|
||||
function getDirectory(dirPath) {
|
||||
const absolutePath = path.resolve(dirPath);
|
||||
if (!fs.existsSync(absolutePath)) {
|
||||
fs.mkdirSync(absolutePath, { recursive: true });
|
||||
}
|
||||
return absolutePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for a file. If not found, creates it with initialData.
|
||||
* @param {string} filePath - The absolute or relative path to the file.
|
||||
* @param {object} [initialData={}] - The initial data to write if the file is created.
|
||||
* @returns {object} - The content of the file as an object.
|
||||
*/
|
||||
function write(filePath, initialData = {}) {
|
||||
const absolutePath = path.resolve(filePath);
|
||||
const dir = path.dirname(absolutePath);
|
||||
|
||||
getDirectory(dir);
|
||||
|
||||
if (!fs.existsSync(absolutePath)) {
|
||||
fs.writeFileSync(absolutePath, JSON.stringify(initialData, null, 2), 'utf-8');
|
||||
return initialData;
|
||||
}
|
||||
|
||||
try {
|
||||
const content = fs.readFileSync(absolutePath, 'utf-8');
|
||||
return JSON.parse(content);
|
||||
} catch (error) {
|
||||
console.error(`Error reading or parsing JSON file at ${absolutePath}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrive dati in un file JSON.
|
||||
* @param {string} filePath - Il path assoluto o relativo al file JSON.
|
||||
* @param {object} data - Gli elementi da aggiungere nel file JSON.
|
||||
*/
|
||||
function update(filePath, data) {
|
||||
const absolutePath = path.resolve(filePath);
|
||||
const dir = path.dirname(absolutePath);
|
||||
|
||||
getDirectory(dir);
|
||||
|
||||
fs.writeFileSync(absolutePath, JSON.stringify(data, null, 2), 'utf-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggiunge un elemento all'array del file specificato
|
||||
* Se il file non esiste, lo crea con un array contenente l'elemento.
|
||||
* Se il file esiste ma non è un array, genera un errore.
|
||||
* @param {string} filePath - Il path del file JSON.
|
||||
* @param {any} element - L'elemento da aggiungere all'array.
|
||||
* @returns {array} - L'array aggiornato.
|
||||
*/
|
||||
function appendTo(filePath, element) {
|
||||
const absolutePath = path.resolve(filePath);
|
||||
let data = [];
|
||||
|
||||
if (fs.existsSync(absolutePath)) {
|
||||
try {
|
||||
const content = fs.readFileSync(absolutePath, 'utf-8');
|
||||
data = JSON.parse(content);
|
||||
} catch (error) {
|
||||
console.error(`Error reading or parsing JSON file at ${absolutePath}:`, error);
|
||||
throw error;
|
||||
}
|
||||
} else {
|
||||
// Ensure directory exists if we are creating the file
|
||||
const dir = path.dirname(absolutePath);
|
||||
getDirectory(dir);
|
||||
}
|
||||
|
||||
if (!Array.isArray(data)) {
|
||||
throw new Error(`File at ${absolutePath} exists but is not a JSON array.`);
|
||||
}
|
||||
|
||||
data.push(element);
|
||||
fs.writeFileSync(absolutePath, JSON.stringify(data, null, 2), 'utf-8');
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggiunge un elemento a un array specifico all'interno di un oggetto JSON
|
||||
* Es: JSON = {date: "now", elements: [], security: false}
|
||||
* appendToElement(filePath, 'elements', {title: "", description: ""})
|
||||
*
|
||||
* @param {string} filePath - Il path del file JSON.
|
||||
* @param {string} arrayKey - La chiave dell'array nell'oggetto JSON (es: 'elements').
|
||||
* @param {any} element - L'elemento da aggiungere all'array specificato.
|
||||
* @returns {boolean} - Se l'operazione è andata a buon fine, restituisce true.
|
||||
*/
|
||||
function appendToElement(filePath, arrayKey, element) {
|
||||
const absolutePath = path.resolve(filePath);
|
||||
let data = {};
|
||||
|
||||
if (fs.existsSync(absolutePath)) {
|
||||
try {
|
||||
const content = fs.readFileSync(absolutePath, 'utf-8');
|
||||
data = JSON.parse(content);
|
||||
} catch (error) {
|
||||
console.error(`Error reading or parsing JSON file at ${absolutePath}:`, error);
|
||||
throw error;
|
||||
}
|
||||
} else {
|
||||
const dir = path.dirname(absolutePath);
|
||||
getDirectory(dir);
|
||||
data = {};
|
||||
}
|
||||
|
||||
if (!data.hasOwnProperty(arrayKey)) {
|
||||
data[arrayKey] = [];
|
||||
}
|
||||
if (!Array.isArray(data[arrayKey])) {
|
||||
throw new Error(`Property '${arrayKey}' in file at ${absolutePath} exists but is not an array.`);
|
||||
}
|
||||
|
||||
data[arrayKey].push(element);
|
||||
|
||||
fs.writeFileSync(absolutePath, JSON.stringify(data, null, 2), 'utf-8');
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Rimuove un elemento da un array specifico all'interno di un oggetto JSON
|
||||
* cercando per proprietà "name"
|
||||
* Es: JSON = {date: "now", elements: [{name: "item1"}, {name: "item2"}], security: false}
|
||||
* removeFromElement(filePath, 'elements', 'item1')
|
||||
*
|
||||
* @param {string} filePath - Il path del file JSON.
|
||||
* @param {string} arrayKey - La chiave dell'array nell'oggetto JSON (es: 'elements').
|
||||
* @param {string} nameToRemove - Il valore della proprietà "name" dell'elemento da rimuovere.
|
||||
* @returns {object} - Oggetto con {success: boolean, removed: object|null, remaining: number}
|
||||
*/
|
||||
function removeFromElement(filePath, arrayKey, nameToRemove) {
|
||||
const absolutePath = path.resolve(filePath);
|
||||
let data = {};
|
||||
|
||||
if (fs.existsSync(absolutePath)) {
|
||||
try {
|
||||
const content = fs.readFileSync(absolutePath, 'utf-8');
|
||||
data = JSON.parse(content);
|
||||
} catch (error) {
|
||||
console.error(`Error reading or parsing JSON file at ${absolutePath}:`, error);
|
||||
throw error;
|
||||
}
|
||||
} else {
|
||||
throw new Error(`File at ${absolutePath} does not exist.`);
|
||||
}
|
||||
|
||||
if (!data.hasOwnProperty(arrayKey)) {
|
||||
throw new Error(`Property '${arrayKey}' does not exist in file at ${absolutePath}.`);
|
||||
}
|
||||
if (!Array.isArray(data[arrayKey])) {
|
||||
throw new Error(`Property '${arrayKey}' in file at ${absolutePath} is not an array.`);
|
||||
}
|
||||
|
||||
const initialLength = data[arrayKey].length;
|
||||
const indexToRemove = data[arrayKey].findIndex(item => item.name === nameToRemove);
|
||||
|
||||
if (indexToRemove === -1) {
|
||||
return {
|
||||
success: false,
|
||||
removed: null,
|
||||
remaining: initialLength,
|
||||
message: `Element with name '${nameToRemove}' not found in array '${arrayKey}'.`
|
||||
};
|
||||
}
|
||||
|
||||
const removedElement = data[arrayKey].splice(indexToRemove, 1)[0];
|
||||
fs.writeFileSync(absolutePath, JSON.stringify(data, null, 2), 'utf-8');
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
function findInElement(filePath, arrayKey, name) {
|
||||
const absolutePath = path.resolve(filePath);
|
||||
let data = {};
|
||||
|
||||
if (fs.existsSync(absolutePath)) {
|
||||
try {
|
||||
const content = fs.readFileSync(absolutePath, 'utf-8');
|
||||
data = JSON.parse(content);
|
||||
} catch (error) {
|
||||
console.error(`Error reading or parsing JSON file at ${absolutePath}:`, error);
|
||||
throw error;
|
||||
}
|
||||
} else {
|
||||
throw new Error(`File at ${absolutePath} does not exist.`);
|
||||
}
|
||||
|
||||
if (!data.hasOwnProperty(arrayKey)) {
|
||||
throw new Error(`Property '${arrayKey}' does not exist in file at ${absolutePath}.`);
|
||||
}
|
||||
if (!Array.isArray(data[arrayKey])) {
|
||||
throw new Error(`Property '${arrayKey}' in file at ${absolutePath} is not an array.`);
|
||||
}
|
||||
|
||||
const index = data[arrayKey].findIndex(item => item.name === name);
|
||||
|
||||
return data[arrayKey][index]
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Aggiorna un elemento in un array specifico all'interno di un oggetto JSON
|
||||
* cercando per proprietà "name" e sostituendolo con un nuovo elemento
|
||||
* Es: JSON = {date: "now", elements: [{name: "item1", value: 10}, {name: "item2", value: 20}]}
|
||||
* updateInElement(filePath, 'elements', 'item1', {name: "item1", value: 99})
|
||||
*
|
||||
* @param {string} filePath - Il path del file JSON.
|
||||
* @param {string} arrayKey - La chiave dell'array nell'oggetto JSON (es: 'elements').
|
||||
* @param {string} nameToUpdate - Il valore della proprietà "name" dell'elemento da aggiornare.
|
||||
* @param {any} newElement - Il nuovo elemento che sostituirà quello trovato.
|
||||
* @returns {boolean} - True se l'operazione ha successo, false se l'elemento non è stato trovato.
|
||||
*/
|
||||
function updateInElement(filePath, arrayKey, nameToUpdate, newElement) {
|
||||
const absolutePath = path.resolve(filePath);
|
||||
let data = {};
|
||||
|
||||
if (fs.existsSync(absolutePath)) {
|
||||
try {
|
||||
const content = fs.readFileSync(absolutePath, 'utf-8');
|
||||
data = JSON.parse(content);
|
||||
} catch (error) {
|
||||
console.error(`Error reading or parsing JSON file at ${absolutePath}:`, error);
|
||||
throw error;
|
||||
}
|
||||
} else {
|
||||
throw new Error(`File at ${absolutePath} does not exist.`);
|
||||
}
|
||||
|
||||
if (!data.hasOwnProperty(arrayKey)) {
|
||||
throw new Error(`Property '${arrayKey}' does not exist in file at ${absolutePath}.`);
|
||||
}
|
||||
if (!Array.isArray(data[arrayKey])) {
|
||||
throw new Error(`Property '${arrayKey}' in file at ${absolutePath} is not an array.`);
|
||||
}
|
||||
|
||||
const index = data[arrayKey].findIndex(item => item.name === nameToUpdate);
|
||||
|
||||
if (index === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
data[arrayKey][index] = newElement;
|
||||
fs.writeFileSync(absolutePath, JSON.stringify(data, null, 2), 'utf-8');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
module.exports = {
|
||||
getDirectory,
|
||||
write,
|
||||
update,
|
||||
appendToElement,
|
||||
findInElement,
|
||||
removeFromElement,
|
||||
updateInElement
|
||||
};
|
||||
350
plugin/datasetModels/graphsCore.js
Normal file
350
plugin/datasetModels/graphsCore.js
Normal file
@@ -0,0 +1,350 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const ARCHIVE_FILE = path.join(__dirname, 'hourly_archive.json');
|
||||
|
||||
// Cache dati OpenMeteo condivisi (evita chiamate duplicate)
|
||||
let sharedWeatherData = {
|
||||
forecast: null,
|
||||
waves: null,
|
||||
units: null, // Unità di misura globali
|
||||
lastUpdate: null,
|
||||
updateInterval: 2 * 60 * 1000 // 2 minuti
|
||||
};
|
||||
|
||||
// Archivio dati orari
|
||||
let hourlyArchive = {
|
||||
temperature: [],
|
||||
windSpeed: [],
|
||||
windDirection: [],
|
||||
waveHeight: [],
|
||||
wavePeriod: [],
|
||||
waveDirection: [],
|
||||
humidity: [],
|
||||
pressure: []
|
||||
};
|
||||
|
||||
/**
|
||||
* Carica l'archivio da file
|
||||
*/
|
||||
function loadArchive() {
|
||||
try {
|
||||
if (fs.existsSync(ARCHIVE_FILE)) {
|
||||
const data = fs.readFileSync(ARCHIVE_FILE, 'utf8');
|
||||
const parsed = JSON.parse(data);
|
||||
// Valida struttura archivio
|
||||
if (parsed && typeof parsed === 'object') {
|
||||
hourlyArchive = {
|
||||
temperature: Array.isArray(parsed.temperature) ? parsed.temperature : [],
|
||||
windSpeed: Array.isArray(parsed.windSpeed) ? parsed.windSpeed : [],
|
||||
windDirection: Array.isArray(parsed.windDirection) ? parsed.windDirection : [],
|
||||
waveHeight: Array.isArray(parsed.waveHeight) ? parsed.waveHeight : [],
|
||||
wavePeriod: Array.isArray(parsed.wavePeriod) ? parsed.wavePeriod : [],
|
||||
waveDirection: Array.isArray(parsed.waveDirection) ? parsed.waveDirection : [],
|
||||
humidity: Array.isArray(parsed.humidity) ? parsed.humidity : [],
|
||||
pressure: Array.isArray(parsed.pressure) ? parsed.pressure : []
|
||||
};
|
||||
console.log('[GraphsCore] Archivio caricato');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[GraphsCore] Errore caricamento archivio:', error.message);
|
||||
// Resetta archivio se corrotto
|
||||
hourlyArchive = {
|
||||
temperature: [], windSpeed: [], windDirection: [],
|
||||
waveHeight: [], wavePeriod: [], waveDirection: [],
|
||||
humidity: [], pressure: []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Salva l'archivio su file
|
||||
*/
|
||||
function saveArchive() {
|
||||
try {
|
||||
fs.writeFileSync(ARCHIVE_FILE, JSON.stringify(hourlyArchive, null, 2));
|
||||
} catch (error) {
|
||||
console.error('[GraphsCore] Errore salvataggio archivio:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggiorna i dati meteo condivisi
|
||||
* @param {object} forecastData - Dati forecast da OpenMeteo
|
||||
* @param {object} wavesData - Dati onde da OpenMeteo
|
||||
*/
|
||||
function updateSharedWeatherData(forecastData, wavesData) {
|
||||
if (forecastData) {
|
||||
sharedWeatherData.forecast = forecastData;
|
||||
}
|
||||
if (wavesData) {
|
||||
sharedWeatherData.waves = wavesData;
|
||||
}
|
||||
|
||||
// Aggiorna unità se disponibili
|
||||
if (forecastData?.units || wavesData?.units) {
|
||||
sharedWeatherData.units = {
|
||||
forecast: forecastData?.units || sharedWeatherData.units?.forecast || {},
|
||||
waves: wavesData?.units || sharedWeatherData.units?.waves || {}
|
||||
};
|
||||
}
|
||||
|
||||
sharedWeatherData.lastUpdate = Date.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ottiene i dati meteo condivisi
|
||||
* @returns {object} Dati meteo attuali
|
||||
*/
|
||||
function getSharedWeatherData() {
|
||||
return {
|
||||
forecast: sharedWeatherData.forecast,
|
||||
waves: sharedWeatherData.waves,
|
||||
units: sharedWeatherData.units,
|
||||
lastUpdate: sharedWeatherData.lastUpdate,
|
||||
isValid: sharedWeatherData.lastUpdate &&
|
||||
(Date.now() - sharedWeatherData.lastUpdate) < sharedWeatherData.updateInterval * 2
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Ottiene le unità di misura globali
|
||||
*/
|
||||
function getUnits() {
|
||||
return sharedWeatherData.units || {
|
||||
forecast: {
|
||||
temperature: '°C',
|
||||
humidity: '%',
|
||||
pressure: 'hPa',
|
||||
windSpeed: 'km/h',
|
||||
windDirection: '°'
|
||||
},
|
||||
waves: {
|
||||
waveHeight: 'm',
|
||||
wavePeriod: 's',
|
||||
waveDirection: '°'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatta un valore con la sua unità
|
||||
*/
|
||||
function formatValue(value, unitKey, category = 'forecast') {
|
||||
if (value === null || value === undefined) return 'n/d';
|
||||
const units = getUnits();
|
||||
const unit = units[category]?.[unitKey] || '';
|
||||
return `${value}${unit}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica se i dati condivisi sono ancora validi
|
||||
*/
|
||||
function isWeatherDataValid() {
|
||||
if (!sharedWeatherData.lastUpdate) return false;
|
||||
return (Date.now() - sharedWeatherData.lastUpdate) < sharedWeatherData.updateInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Archivia un punto dati orario
|
||||
*/
|
||||
function archiveHourlyData(data) {
|
||||
if (!data || typeof data !== 'object') {
|
||||
console.warn('[GraphsCore] archiveHourlyData: dati non validi');
|
||||
return;
|
||||
}
|
||||
|
||||
const timestamp = new Date().toISOString();
|
||||
const maxPoints = 168; // 7 giorni di dati orari
|
||||
|
||||
const addPoint = (arr, value) => {
|
||||
if (value === null || value === undefined || Number.isNaN(value)) return;
|
||||
arr.push({ timestamp, value });
|
||||
if (arr.length > maxPoints) arr.shift();
|
||||
};
|
||||
|
||||
addPoint(hourlyArchive.temperature, data.temperature);
|
||||
addPoint(hourlyArchive.windSpeed, data.windSpeed);
|
||||
addPoint(hourlyArchive.windDirection, data.windDirection);
|
||||
addPoint(hourlyArchive.waveHeight, data.waveHeight);
|
||||
addPoint(hourlyArchive.wavePeriod, data.wavePeriod);
|
||||
addPoint(hourlyArchive.waveDirection, data.waveDirection);
|
||||
addPoint(hourlyArchive.humidity, data.humidity);
|
||||
addPoint(hourlyArchive.pressure, data.pressure);
|
||||
|
||||
saveArchive();
|
||||
console.log('[GraphsCore] Dati orari archiviati');
|
||||
}
|
||||
|
||||
/**
|
||||
* Ottiene i dati per un grafico specifico
|
||||
* @param {string} parameter - temperatura, vento, onde, etc.
|
||||
* @param {number} hours - ultimi N ore (default 24)
|
||||
*/
|
||||
function getGraphData(parameter, hours = 24) {
|
||||
const paramMap = {
|
||||
'temperature': hourlyArchive.temperature,
|
||||
'windSpeed': hourlyArchive.windSpeed,
|
||||
'windDirection': hourlyArchive.windDirection,
|
||||
'waveHeight': hourlyArchive.waveHeight,
|
||||
'wavePeriod': hourlyArchive.wavePeriod,
|
||||
'waveDirection': hourlyArchive.waveDirection,
|
||||
'humidity': hourlyArchive.humidity,
|
||||
'pressure': hourlyArchive.pressure
|
||||
};
|
||||
|
||||
const data = paramMap[parameter] || [];
|
||||
const cutoff = Date.now() - (hours * 60 * 60 * 1000);
|
||||
|
||||
return data.filter(point => new Date(point.timestamp).getTime() > cutoff);
|
||||
}
|
||||
|
||||
/**
|
||||
* Genera dati formattati per Chart.js
|
||||
*/
|
||||
function formatForChart(parameter, hours = 24) {
|
||||
const data = getGraphData(parameter, hours);
|
||||
|
||||
return {
|
||||
labels: data.map(p => {
|
||||
const d = new Date(p.timestamp);
|
||||
return `${d.getHours()}:${String(d.getMinutes()).padStart(2, '0')}`;
|
||||
}),
|
||||
datasets: [{
|
||||
label: getParameterLabel(parameter),
|
||||
data: data.map(p => p.value),
|
||||
borderColor: getParameterColor(parameter),
|
||||
backgroundColor: getParameterColor(parameter, 0.2),
|
||||
tension: 0.3,
|
||||
fill: true
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Label leggibili per i parametri
|
||||
*/
|
||||
function getParameterLabel(param) {
|
||||
const labels = {
|
||||
'temperature': 'Temperatura (°C)',
|
||||
'windSpeed': 'Velocità Vento (km/h)',
|
||||
'windDirection': 'Direzione Vento (°)',
|
||||
'waveHeight': 'Altezza Onde (m)',
|
||||
'wavePeriod': 'Periodo Onde (s)',
|
||||
'waveDirection': 'Direzione Onde (°)',
|
||||
'humidity': 'Umidità (%)',
|
||||
'pressure': 'Pressione (hPa)'
|
||||
};
|
||||
return labels[param] || param;
|
||||
}
|
||||
|
||||
/**
|
||||
* Colori per i grafici
|
||||
*/
|
||||
function getParameterColor(param, alpha = 1) {
|
||||
const colors = {
|
||||
'temperature': `rgba(255, 99, 132, ${alpha})`,
|
||||
'windSpeed': `rgba(54, 162, 235, ${alpha})`,
|
||||
'windDirection': `rgba(75, 192, 192, ${alpha})`,
|
||||
'waveHeight': `rgba(153, 102, 255, ${alpha})`,
|
||||
'wavePeriod': `rgba(255, 159, 64, ${alpha})`,
|
||||
'waveDirection': `rgba(255, 205, 86, ${alpha})`,
|
||||
'humidity': `rgba(201, 203, 207, ${alpha})`,
|
||||
'pressure': `rgba(100, 149, 237, ${alpha})`
|
||||
};
|
||||
return colors[param] || `rgba(128, 128, 128, ${alpha})`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ottiene tutti i dati disponibili per dashboard
|
||||
*/
|
||||
function getAllGraphsData(hours = 24) {
|
||||
return {
|
||||
temperature: formatForChart('temperature', hours),
|
||||
windSpeed: formatForChart('windSpeed', hours),
|
||||
waveHeight: formatForChart('waveHeight', hours),
|
||||
humidity: formatForChart('humidity', hours)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Statistiche sull'archivio
|
||||
*/
|
||||
function getArchiveStats() {
|
||||
return {
|
||||
temperature: hourlyArchive.temperature.length,
|
||||
windSpeed: hourlyArchive.windSpeed.length,
|
||||
waveHeight: hourlyArchive.waveHeight.length,
|
||||
oldestData: getOldestTimestamp(),
|
||||
newestData: getNewestTimestamp()
|
||||
};
|
||||
}
|
||||
|
||||
function getOldestTimestamp() {
|
||||
const all = [
|
||||
...hourlyArchive.temperature,
|
||||
...hourlyArchive.windSpeed,
|
||||
...hourlyArchive.waveHeight
|
||||
];
|
||||
if (all.length === 0) return null;
|
||||
return all.reduce((oldest, p) =>
|
||||
new Date(p.timestamp) < new Date(oldest.timestamp) ? p : oldest
|
||||
).timestamp;
|
||||
}
|
||||
|
||||
function getNewestTimestamp() {
|
||||
const all = [
|
||||
...hourlyArchive.temperature,
|
||||
...hourlyArchive.windSpeed,
|
||||
...hourlyArchive.waveHeight
|
||||
];
|
||||
if (all.length === 0) return null;
|
||||
return all.reduce((newest, p) =>
|
||||
new Date(p.timestamp) > new Date(newest.timestamp) ? p : newest
|
||||
).timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pulisce l'archivio
|
||||
*/
|
||||
function clearArchive() {
|
||||
hourlyArchive = {
|
||||
temperature: [],
|
||||
windSpeed: [],
|
||||
windDirection: [],
|
||||
waveHeight: [],
|
||||
wavePeriod: [],
|
||||
waveDirection: [],
|
||||
humidity: [],
|
||||
pressure: []
|
||||
};
|
||||
saveArchive();
|
||||
console.log('[GraphsCore] Archivio pulito');
|
||||
}
|
||||
|
||||
// Carica archivio all'avvio
|
||||
loadArchive();
|
||||
|
||||
module.exports = {
|
||||
// Gestione dati condivisi
|
||||
updateSharedWeatherData,
|
||||
getSharedWeatherData,
|
||||
isWeatherDataValid,
|
||||
|
||||
// Unità di misura
|
||||
getUnits,
|
||||
formatValue,
|
||||
|
||||
// Archivio orario
|
||||
archiveHourlyData,
|
||||
getGraphData,
|
||||
formatForChart,
|
||||
getAllGraphsData,
|
||||
getArchiveStats,
|
||||
clearArchive,
|
||||
|
||||
// Utility
|
||||
getParameterLabel,
|
||||
getParameterColor
|
||||
};
|
||||
Reference in New Issue
Block a user