dynamic graph
This commit is contained in:
parent
a606796d9e
commit
5e029ff99c
17 changed files with 1707 additions and 569 deletions
131
services/SensorDataStorage.ts
Normal file
131
services/SensorDataStorage.ts
Normal file
|
@ -0,0 +1,131 @@
|
|||
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<SensorDataPoint[]> {
|
||||
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<SensorDataPoint[]> {
|
||||
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;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue