- 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.
172 lines
No EOL
4.4 KiB
TypeScript
172 lines
No EOL
4.4 KiB
TypeScript
import { Elysia, t } from "elysia";
|
|
import { StateManager } from '../services/StateManager';
|
|
import { BedService } from '../services/BedService';
|
|
import { WebSocketMessage, StateUpdateEvent } from '../types/FrontendState';
|
|
|
|
// Define WebSocket client type
|
|
interface WSClient {
|
|
id: string;
|
|
send: (message: string) => void;
|
|
readyState: number;
|
|
}
|
|
|
|
// Singleton instances
|
|
let stateManager: StateManager | null = null;
|
|
let bedService: BedService | null = null;
|
|
const clients = new Map<string, WSClient>();
|
|
|
|
// Initialize services
|
|
async function initializeServices() {
|
|
if (!bedService) {
|
|
bedService = new BedService();
|
|
await bedService.initialize();
|
|
}
|
|
|
|
if (!stateManager) {
|
|
stateManager = new StateManager(bedService);
|
|
await stateManager.initializeState();
|
|
|
|
// Listen for state updates and broadcast to clients
|
|
stateManager.on('stateUpdate', (event: StateUpdateEvent) => {
|
|
broadcastStateUpdate(event);
|
|
});
|
|
}
|
|
}
|
|
|
|
// Broadcast state updates to all connected clients
|
|
function broadcastStateUpdate(event: StateUpdateEvent) {
|
|
const message: WebSocketMessage = {
|
|
type: 'STATE_UPDATE',
|
|
payload: event,
|
|
timestamp: new Date()
|
|
};
|
|
|
|
const messageStr = JSON.stringify(message);
|
|
const deadClients: string[] = [];
|
|
|
|
clients.forEach((ws, id) => {
|
|
try {
|
|
if (ws.readyState === 1) { // WebSocket.OPEN
|
|
ws.send(messageStr);
|
|
} else {
|
|
deadClients.push(id);
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to send message to client:', error);
|
|
deadClients.push(id);
|
|
}
|
|
});
|
|
|
|
// Clean up dead clients
|
|
deadClients.forEach(id => {
|
|
clients.delete(id);
|
|
});
|
|
|
|
// Update connection count
|
|
if (stateManager) {
|
|
stateManager.updateConnectionCount(clients.size);
|
|
}
|
|
}
|
|
|
|
// Send current state to a specific client
|
|
async function sendCurrentState(ws: WSClient) {
|
|
try {
|
|
await initializeServices();
|
|
|
|
if (stateManager) {
|
|
const state = stateManager.getState();
|
|
const message: WebSocketMessage = {
|
|
type: 'STATE_UPDATE',
|
|
payload: {
|
|
type: 'FULL_STATE',
|
|
timestamp: new Date(),
|
|
data: state
|
|
},
|
|
timestamp: new Date()
|
|
};
|
|
|
|
ws.send(JSON.stringify(message));
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to send current state:', error);
|
|
}
|
|
}
|
|
|
|
const stateRouter = new Elysia()
|
|
.ws('/ws', {
|
|
body: t.Object({
|
|
type: t.Union([
|
|
t.Literal('ACKNOWLEDGE_ALERT'),
|
|
t.Literal('SILENCE_ALERT'),
|
|
t.Literal('HEARTBEAT')
|
|
]),
|
|
payload: t.Optional(t.Object({
|
|
alertId: t.Optional(t.String())
|
|
}))
|
|
}),
|
|
|
|
open: async (ws) => {
|
|
console.log('WebSocket client connected:', ws.id);
|
|
clients.set(ws.id, ws);
|
|
|
|
// Send current state to new client
|
|
await sendCurrentState(ws);
|
|
|
|
// Update connection count
|
|
await initializeServices();
|
|
if (stateManager) {
|
|
stateManager.updateConnectionCount(clients.size);
|
|
}
|
|
},
|
|
|
|
message: async (ws, message) => {
|
|
try {
|
|
await initializeServices();
|
|
|
|
switch (message.type) {
|
|
case 'ACKNOWLEDGE_ALERT':
|
|
if (message.payload?.alertId && stateManager) {
|
|
await stateManager.acknowledgeAlert(message.payload.alertId);
|
|
}
|
|
break;
|
|
|
|
case 'SILENCE_ALERT':
|
|
if (message.payload?.alertId && stateManager) {
|
|
await stateManager.silenceAlert(message.payload.alertId);
|
|
}
|
|
break;
|
|
|
|
case 'HEARTBEAT':
|
|
// Respond to heartbeat
|
|
ws.send(JSON.stringify({
|
|
type: 'HEARTBEAT',
|
|
payload: { message: 'pong' },
|
|
timestamp: new Date()
|
|
}));
|
|
break;
|
|
|
|
default:
|
|
console.warn('Unknown WebSocket message type:', message.type);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error handling client message:', error);
|
|
ws.send(JSON.stringify({
|
|
type: 'ERROR',
|
|
payload: { message: 'Invalid message format' },
|
|
timestamp: new Date()
|
|
}));
|
|
}
|
|
},
|
|
|
|
close: (ws) => {
|
|
console.log('WebSocket client disconnected:', ws.id);
|
|
clients.delete(ws.id);
|
|
|
|
// Update connection count
|
|
if (stateManager) {
|
|
stateManager.updateConnectionCount(clients.size);
|
|
}
|
|
}
|
|
});
|
|
|
|
export default stateRouter; |