feat: Refactor BedHardware to use a singleton instance and remove serial implementation

This commit is contained in:
Siwat Sirichai 2025-06-21 15:21:32 +07:00
parent fb87e74ec9
commit fd8cacd62b
4 changed files with 25 additions and 54 deletions

View file

@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import { BedHardware, PinState, PinChange } from '@/services/BedHardware'; import { bedHardwareInstance, PinState, PinChange } from '@/services/BedHardware';
import { SensorDataStorage, SensorDataPoint } from '@/services/SensorDataStorage'; import { SensorDataStorage, SensorDataPoint } from '@/services/SensorDataStorage';
import { SensorConfig } from '@/types/sensor'; import { SensorConfig } from '@/types/sensor';
@ -44,7 +44,6 @@ SENSOR_CONFIG.forEach(sensor => {
} }
}); });
let bedHardware: BedHardware | null = null;
const sensorDataStorage = SensorDataStorage.getInstance(); const sensorDataStorage = SensorDataStorage.getInstance();
const sensorData: Record<string, { const sensorData: Record<string, {
id: string; id: string;
@ -108,41 +107,33 @@ function generateTimeSeriesData(hours = 1) {
// Initialize hardware connection // Initialize hardware connection
async function initializeHardware() { async function initializeHardware() {
if (bedHardware && isHardwareConnected) return; if (isHardwareConnected) return;
try { try {
// Try to find available serial ports bedHardwareInstance.on('connected', () => {
const availablePorts = await BedHardware.listPorts();
const portPath = availablePorts.find(port =>
port.includes('ttyUSB') || port.includes('ttyACM') || port.includes('cu.usbmodem')
) || '/dev/ttyUSB0'; // Default fallback
bedHardware = new BedHardware(portPath, 9600);
bedHardware.on('connected', () => {
console.log('BedHardware connected'); console.log('BedHardware connected');
isHardwareConnected = true; isHardwareConnected = true;
}); });
bedHardware.on('disconnected', () => { bedHardwareInstance.on('disconnected', () => {
console.log('BedHardware disconnected'); console.log('BedHardware disconnected');
isHardwareConnected = false; isHardwareConnected = false;
}); });
bedHardware.on('pinChanged', (change: PinChange) => { bedHardwareInstance.on('pinChanged', (change: PinChange) => {
updateSensorFromPin(change.pin, change.currentState); updateSensorFromPin(change.pin, change.currentState);
}); });
bedHardware.on('pinInitialized', (pinState: PinState) => { bedHardwareInstance.on('pinInitialized', (pinState: PinState) => {
updateSensorFromPin(pinState.pin, pinState.state); updateSensorFromPin(pinState.pin, pinState.state);
}); });
bedHardware.on('error', (error) => { bedHardwareInstance.on('error', (error) => {
console.error('BedHardware error:', error); console.error('BedHardware error:', error);
isHardwareConnected = false; isHardwareConnected = false;
}); });
await bedHardware.connect(); await bedHardwareInstance.connect();
} catch (error) { } catch (error) {
console.warn('Failed to connect to hardware, using mock data:', error); console.warn('Failed to connect to hardware, using mock data:', error);
isHardwareConnected = false; isHardwareConnected = false;
@ -283,7 +274,7 @@ export async function GET() {
} }
// Initialize hardware if not already done // Initialize hardware if not already done
if (!bedHardware) { if (!isHardwareConnected) {
await initializeHardware(); await initializeHardware();
} }
@ -291,8 +282,8 @@ export async function GET() {
updateMockSensorData(); updateMockSensorData();
// If hardware is connected, get current pin states // If hardware is connected, get current pin states
if (isHardwareConnected && bedHardware) { if (isHardwareConnected) {
const pinStates = bedHardware.getAllPinStates(); const pinStates = bedHardwareInstance.getAllPinStates();
for (const pinState of pinStates) { for (const pinState of pinStates) {
await updateSensorFromPin(pinState.pin, pinState.state); await updateSensorFromPin(pinState.pin, pinState.state);
} }
@ -332,9 +323,8 @@ export async function POST(request: NextRequest) {
} }
if (body.action === 'disconnect') { if (body.action === 'disconnect') {
if (bedHardware) { if (bedHardwareInstance) {
await bedHardware.disconnect(); await bedHardwareInstance.disconnect();
bedHardware = null;
isHardwareConnected = false; isHardwareConnected = false;
} }
return NextResponse.json({ return NextResponse.json({

View file

@ -1,19 +1,15 @@
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { IBedHardware, PinState, PinChange, BedHardwareConfig } from '../types/bedhardware'; import { IBedHardware, PinState, PinChange, BedHardwareConfig } from '../types/bedhardware';
import { BedHardwareSerial } from './BedHardwareSerial';
import { BedHardwareMQTT } from './BedHardwareMQTT'; import { BedHardwareMQTT } from './BedHardwareMQTT';
/** /**
* BedHardware - Factory class for creating bed hardware implementations * BedHardware - MQTT-based bed hardware implementation
* *
* Usage: * Usage:
* // MQTT (connects to broker.hivemq.com with base topic /Jtkcp2N/pressurebed/) * MQTT (connects to broker.hivemq.com with base topic /Jtkcp2N/pressurebed/)
* const hardware = BedHardware.createSimpleMQTT(); * const hardware = BedHardware.createSimpleMQTT();
* *
* // Serial * With custom topics
* const hardware = BedHardware.createSerial('COM3', 9600);
*
* // With custom topics
* const hardware = BedHardware.createMQTT({ * const hardware = BedHardware.createMQTT({
* topics: { * topics: {
* pinState: '/custom/pin/state', * pinState: '/custom/pin/state',
@ -24,18 +20,10 @@ import { BedHardwareMQTT } from './BedHardwareMQTT';
*/ */
export class BedHardware extends EventEmitter implements IBedHardware { export class BedHardware extends EventEmitter implements IBedHardware {
private implementation: IBedHardware; private implementation: IBedHardware;
constructor(config: BedHardwareConfig) { constructor(config: BedHardwareConfig) {
super(); super();
if (config.type === 'serial') { if (config.type === 'mqtt') {
if (!config.serial?.portPath) {
throw new Error('Serial port path is required for serial connection');
}
this.implementation = new BedHardwareSerial(
config.serial.portPath,
config.serial.baudRate || 9600 );
} else if (config.type === 'mqtt') {
this.implementation = new BedHardwareMQTT({ this.implementation = new BedHardwareMQTT({
topics: config.mqtt?.topics topics: config.mqtt?.topics
}); });
@ -88,14 +76,8 @@ export class BedHardware extends EventEmitter implements IBedHardware {
isConnected(): boolean { isConnected(): boolean {
return this.implementation.isConnected(); return this.implementation.isConnected();
} }
// Static factory methods for convenience // Static factory methods for convenience
static createSerial(portPath: string, baudRate: number = 9600): BedHardware { static createMQTT(config?: {
return new BedHardware({
type: 'serial',
serial: { portPath, baudRate }
});
} static createMQTT(config?: {
topics?: { topics?: {
pinState: string; pinState: string;
pinChange: string; pinChange: string;
@ -114,13 +96,12 @@ export class BedHardware extends EventEmitter implements IBedHardware {
mqtt: {} mqtt: {}
}); });
} }
// Static method to list available serial ports (for serial implementation)
static async listSerialPorts(): Promise<string[]> {
return BedHardwareSerial.listPorts();
}
} }
// Export all classes for direct access if needed // Create and export a default MQTT instance
export { BedHardwareSerial, BedHardwareMQTT }; export const bedHardwareInstance = new BedHardware({
type: 'mqtt',
mqtt: {}
});
export * from '../types/bedhardware'; export * from '../types/bedhardware';

View file

@ -23,6 +23,6 @@
"@/*": ["./*"] "@/*": ["./*"]
} }
}, },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "services/BedHardwareSerial.ts.disabled"],
"exclude": ["node_modules"] "exclude": ["node_modules"]
} }