import fs from 'fs/promises'; import path from 'path'; const DATA_DIR = path.join(process.cwd(), 'data'); const SENSOR_DATA_FILE = path.join(DATA_DIR, 'sensor-data.json'); export interface SensorDataPoint { sensorId: string; value: number; // Changed from pressure to value (0-4095) timestamp: number; time: string; source: 'hardware'; pin?: number; digitalState?: number; } export class SensorDataStorage { private static instance: SensorDataStorage; private dataCache: SensorDataPoint[] = []; private constructor() { this.initializeStorage(); } static getInstance(): SensorDataStorage { if (!SensorDataStorage.instance) { SensorDataStorage.instance = new SensorDataStorage(); } return SensorDataStorage.instance; } private async initializeStorage() { try { // Ensure data directory exists await fs.mkdir(DATA_DIR, { recursive: true }); // Load existing data await this.loadData(); } catch (error) { console.warn('Failed to initialize sensor data storage:', error); } } private async loadData() { try { const data = await fs.readFile(SENSOR_DATA_FILE, 'utf8'); this.dataCache = JSON.parse(data); console.log(`Loaded ${this.dataCache.length} sensor data points from storage`); } catch { // File doesn't exist or is corrupted, start with empty cache this.dataCache = []; console.log('Starting with empty sensor data cache'); } } private async saveData() { try { await fs.writeFile(SENSOR_DATA_FILE, JSON.stringify(this.dataCache, null, 2)); } catch (error) { console.error('Failed to save sensor data:', error); } } async addDataPoint(dataPoint: SensorDataPoint) { this.dataCache.push(dataPoint); // Keep only last 7 days of data to prevent unlimited storage growth const sevenDaysAgo = Date.now() - (7 * 24 * 60 * 60 * 1000); this.dataCache = this.dataCache.filter(point => point.timestamp > sevenDaysAgo); // Save to disk every 10 data points to reduce I/O if (this.dataCache.length % 10 === 0) { await this.saveData(); } } async getDataForSensor( sensorId: string, timespan: number = 24 * 60 * 60 * 1000 // Default 24 hours in milliseconds ): Promise { const cutoffTime = Date.now() - timespan; return this.dataCache .filter(point => point.sensorId === sensorId && point.timestamp > cutoffTime) .sort((a, b) => a.timestamp - b.timestamp); } async getAllRecentData(timespan: number = 24 * 60 * 60 * 1000): Promise { const cutoffTime = Date.now() - timespan; return this.dataCache .filter(point => point.timestamp > cutoffTime) .sort((a, b) => a.timestamp - b.timestamp); } async forceSave() { await this.saveData(); } // Generate time series data for a specific timespan generateTimeSeriesData( sensorData: SensorDataPoint[], timespan: number = 24 * 60 * 60 * 1000 ): Array<{ time: string; timestamp: number; value: number }> { if (sensorData.length === 0) { // Return empty array if no real data exists return []; } // Filter data by timespan const cutoffTime = Date.now() - timespan; const filteredData = sensorData.filter(point => point.timestamp > cutoffTime); return filteredData.map(point => ({ time: point.time, timestamp: point.timestamp, value: point.value })); } }