feat: Add Swagger documentation support and restructure routes
- Added @elysiajs/swagger dependency to package.json for API documentation. - Removed the old bed router and replaced it with a new history router. - Created a new state router to manage WebSocket connections and state updates. - Implemented a comprehensive state management system with the StateManager service. - Introduced AlarmManagement and BedService services for handling alarms and sensor readings. - Established a new MQTT service for managing MQTT connections and subscriptions. - Created an AlarmStateStore to manage volatile alerts and their states. - Defined FrontendState types for structured state management and WebSocket messaging.
This commit is contained in:
parent
a767dc3635
commit
4ae5196ef1
12 changed files with 1189 additions and 1 deletions
228
services/BedService.ts
Normal file
228
services/BedService.ts
Normal file
|
@ -0,0 +1,228 @@
|
|||
import { PrismaClient, type MeasurementPoint } from '../generated/prisma';
|
||||
import { EventEmitter } from 'events';
|
||||
import MQTT from '../adapter/mqtt';
|
||||
import { getMQTTClient, BASE_TOPIC } from './mqttService';
|
||||
import { AlarmManagement } from './AlarmManagement';
|
||||
import { VolatileAlert } from '../store/AlarmStateStore';
|
||||
|
||||
export interface SensorReading {
|
||||
sensorId: string;
|
||||
value: number;
|
||||
timestamp: Date;
|
||||
}
|
||||
|
||||
export interface AlertConfig {
|
||||
warningThreshold: number;
|
||||
alarmThreshold: number;
|
||||
warningDelayMs: number;
|
||||
}
|
||||
|
||||
export class BedService extends EventEmitter {
|
||||
private prisma: PrismaClient;
|
||||
private mqtt: MQTT | null = null;
|
||||
private alarmManagement: AlarmManagement;
|
||||
private baseTopic = `${BASE_TOPIC}pressure`;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.prisma = new PrismaClient();
|
||||
this.alarmManagement = new AlarmManagement();
|
||||
this.setupAlarmEventListeners();
|
||||
}
|
||||
|
||||
private setupAlarmEventListeners(): void {
|
||||
// Forward alarm events
|
||||
this.alarmManagement.on('alertCreated', (alert: VolatileAlert) => {
|
||||
this.emit('alert', alert);
|
||||
});
|
||||
|
||||
this.alarmManagement.on('alertUpdated', (alert: VolatileAlert) => {
|
||||
this.emit('alert', alert);
|
||||
});
|
||||
|
||||
this.alarmManagement.on('alertRemoved', (alert: VolatileAlert) => {
|
||||
this.emit('alertRemoved', alert);
|
||||
});
|
||||
}
|
||||
|
||||
async initialize(mqttConfig?: { host?: string; port?: number; username?: string; password?: string }): Promise<void> {
|
||||
try {
|
||||
// Use mqttService to get initialized MQTT client
|
||||
this.mqtt = await getMQTTClient(mqttConfig);
|
||||
|
||||
// Subscribe to sensor data topic
|
||||
await this.mqtt.subscribe(`${this.baseTopic}/+/data`, (topic, message) => {
|
||||
this.handleSensorData(topic, message);
|
||||
});
|
||||
|
||||
console.log('BedService initialized successfully');
|
||||
this.emit('initialized');
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize BedService:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private handleSensorData(topic: string, message: string): void {
|
||||
try {
|
||||
// Extract sensor ID from topic: bed/pressure/{sensorId}/data
|
||||
const sensorId = topic.split('/')[2];
|
||||
const data = JSON.parse(message);
|
||||
|
||||
const reading: SensorReading = {
|
||||
sensorId,
|
||||
value: data.value,
|
||||
timestamp: new Date(data.timestamp || Date.now())
|
||||
};
|
||||
|
||||
this.processSensorReading(reading);
|
||||
} catch (error) {
|
||||
console.error('Error processing MQTT sensor data:', error);
|
||||
this.emit('error', error);
|
||||
}
|
||||
}
|
||||
private async processSensorReading(reading: SensorReading): Promise<void> {
|
||||
try {
|
||||
// Find measurement point
|
||||
const measurementPoint = await this.prisma.measurementPoint.findUnique({
|
||||
where: { sensorId: reading.sensorId }
|
||||
});
|
||||
|
||||
if (!measurementPoint) {
|
||||
console.warn(`Unknown sensor ID: ${reading.sensorId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Store sensor data
|
||||
await this.prisma.measurementPointData.create({
|
||||
data: {
|
||||
measurementPointId: measurementPoint.id,
|
||||
value: reading.value,
|
||||
timestamp: reading.timestamp,
|
||||
time: reading.timestamp.toISOString()
|
||||
}
|
||||
});
|
||||
|
||||
// Let alarm management handle alerts
|
||||
this.alarmManagement.processSensorReading(reading.sensorId, reading.value, reading.timestamp);
|
||||
|
||||
this.emit('sensorReading', reading);
|
||||
} catch (error) {
|
||||
console.error('Error processing sensor reading:', error);
|
||||
this.emit('error', error);
|
||||
}
|
||||
}
|
||||
// Public API methods
|
||||
async createMeasurementPoint(data: {
|
||||
sensorId: string;
|
||||
label: string;
|
||||
zone: string;
|
||||
x: number;
|
||||
y: number;
|
||||
pin: number;
|
||||
warningThreshold: number;
|
||||
alarmThreshold: number;
|
||||
warningDelayMs: number;
|
||||
}): Promise<MeasurementPoint> {
|
||||
return this.prisma.measurementPoint.create({ data });
|
||||
}
|
||||
async getMeasurementPoints(): Promise<MeasurementPoint[]> {
|
||||
const points = await this.prisma.measurementPoint.findMany({
|
||||
orderBy: { zone: 'asc' }
|
||||
});
|
||||
|
||||
// Update alarm management with current measurement points
|
||||
const pointsRecord: Record<string, {
|
||||
id: string;
|
||||
sensorId: string;
|
||||
label: string;
|
||||
zone: string;
|
||||
x: number;
|
||||
y: number;
|
||||
pin: number;
|
||||
warningThreshold: number;
|
||||
alarmThreshold: number;
|
||||
warningDelayMs: number;
|
||||
currentValue: number;
|
||||
lastUpdateTime: Date;
|
||||
status: 'offline';
|
||||
}> = {};
|
||||
|
||||
points.forEach(point => {
|
||||
pointsRecord[point.id] = {
|
||||
id: point.id,
|
||||
sensorId: point.sensorId,
|
||||
label: point.label,
|
||||
zone: point.zone, x: point.x,
|
||||
y: point.y,
|
||||
pin: point.pin,
|
||||
warningThreshold: point.warningThreshold,
|
||||
alarmThreshold: point.alarmThreshold,
|
||||
warningDelayMs: point.warningDelayMs,
|
||||
currentValue: 0,
|
||||
lastUpdateTime: new Date(),
|
||||
status: 'offline' as const
|
||||
};
|
||||
});
|
||||
|
||||
this.alarmManagement.updateMeasurementPoints(pointsRecord);
|
||||
return points;
|
||||
}
|
||||
|
||||
async getMeasurementPointData(sensorId: string, limit = 100) {
|
||||
const measurementPoint = await this.prisma.measurementPoint.findUnique({
|
||||
where: { sensorId }
|
||||
});
|
||||
|
||||
if (!measurementPoint) {
|
||||
throw new Error(`Measurement point not found for sensor: ${sensorId}`);
|
||||
}
|
||||
|
||||
return this.prisma.measurementPointData.findMany({
|
||||
where: { measurementPointId: measurementPoint.id },
|
||||
orderBy: { timestamp: 'desc' },
|
||||
take: limit
|
||||
});
|
||||
}
|
||||
|
||||
// Get active alerts from alarm management (volatile)
|
||||
getActiveAlerts(): VolatileAlert[] {
|
||||
return this.alarmManagement.getActiveAlerts();
|
||||
}
|
||||
|
||||
// Acknowledge alert in volatile store
|
||||
acknowledgeAlert(alertId: string): boolean {
|
||||
return this.alarmManagement.acknowledgeAlert(alertId);
|
||||
}
|
||||
|
||||
// Silence alert in volatile store
|
||||
silenceAlert(alertId: string): boolean {
|
||||
return this.alarmManagement.silenceAlert(alertId);
|
||||
}
|
||||
|
||||
async updateAlertConfig(sensorId: string, config: AlertConfig): Promise<MeasurementPoint> {
|
||||
return this.prisma.measurementPoint.update({
|
||||
where: { sensorId },
|
||||
data: {
|
||||
warningThreshold: config.warningThreshold,
|
||||
alarmThreshold: config.alarmThreshold,
|
||||
warningDelayMs: config.warningDelayMs
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async disconnect(): Promise<void> {
|
||||
// Cleanup alarm management
|
||||
this.alarmManagement.cleanup();
|
||||
|
||||
// Disconnect MQTT
|
||||
if (this.mqtt) {
|
||||
await this.mqtt.disconnect();
|
||||
}
|
||||
|
||||
// Disconnect Prisma
|
||||
await this.prisma.$disconnect();
|
||||
|
||||
this.emit('disconnected');
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue