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' | 'mock'; 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) { // Generate mock data if no real data exists return this.generateMockTimeSeriesData(timespan); } return sensorData.map(point => ({ time: point.time, timestamp: point.timestamp, value: point.value })); } private generateMockTimeSeriesData(timespan: number): Array<{ time: string; timestamp: number; value: number }> { const data = []; const now = Date.now(); const interval = Math.max(1000, timespan / 288); // At least 1 second intervals, up to 288 points for (let i = timespan; i >= 0; i -= interval) { const timestamp = now - i; const time = new Date(timestamp); data.push({ time: time.toLocaleTimeString("en-US", { hour12: false }), timestamp: timestamp, value: Math.floor(Math.random() * 4096 + Math.sin(i / 60000) * 500 + 2000), // 0-4095 range }); } return data; } }