import { SerialPort } from 'serialport'; import { ReadlineParser } from '@serialport/parser-readline'; import { EventEmitter } from 'events'; import { IBedHardware, PinState, PinChange } from '../types/bedhardware'; export class BedHardwareSerial extends EventEmitter implements IBedHardware { private serialPort: SerialPort | null = null; private parser: ReadlineParser | null = null; private pinStates: Map = new Map(); private connectionState: boolean = false; constructor(private portPath: string, private baudRate: number = 9600) { super(); } async connect(): Promise { try { this.serialPort = new SerialPort({ path: this.portPath, baudRate: this.baudRate, autoOpen: false }); this.parser = new ReadlineParser({ delimiter: '\n' }); this.serialPort.pipe(this.parser); // Setup event handlers this.serialPort.on('open', () => { this.connectionState = true; this.emit('connected'); console.log('Serial port opened'); }); this.serialPort.on('error', (error) => { this.emit('error', error); console.error('Serial port error:', error); }); this.serialPort.on('close', () => { this.connectionState = false; this.emit('disconnected'); console.log('Serial port closed'); }); this.parser.on('data', (data: string) => { this.handleSerialData(data.trim()); }); // Open the port await new Promise((resolve, reject) => { this.serialPort!.open((error) => { if (error) { reject(error); } else { resolve(); } }); }); } catch (error) { throw new Error(`Failed to connect to ${this.portPath}: ${error}`); } } async disconnect(): Promise { if (this.serialPort && this.serialPort.isOpen) { await new Promise((resolve) => { this.serialPort!.close(() => { resolve(); }); }); } this.serialPort = null; this.parser = null; this.connectionState = false; } private handleSerialData(data: string): void { const parts = data.split(':'); if (parts[0] === 'INIT') { if (parts[1] === 'START') { this.emit('initialized'); console.log('Arduino initialization started'); } else if (parts.length >= 3) { // INIT:PIN:STATE format const pin = parseInt(parts[1]); const state = parseInt(parts[2]); if (!isNaN(pin) && !isNaN(state)) { const pinState: PinState = { pin, state, name: `PIN${pin}`, timestamp: new Date() }; this.pinStates.set(pin, pinState); this.emit('pinInitialized', pinState); } } } else if (parts[0] === 'CHANGE' && parts.length >= 4) { // CHANGE:PIN:PREVIOUS_STATE:CURRENT_STATE format const pin = parseInt(parts[1]); const previousState = parseInt(parts[2]); const currentState = parseInt(parts[3]); if (!isNaN(pin) && !isNaN(previousState) && !isNaN(currentState)) { const pinChange: PinChange = { pin, previousState, currentState, timestamp: new Date() }; // Update stored pin state const pinState: PinState = { pin, state: currentState, name: `PIN${pin}`, timestamp: new Date() }; this.pinStates.set(pin, pinState); this.emit('pinChanged', pinChange); this.emit(`pin${pin}Changed`, pinChange); } } } getPinState(pin: number): PinState | undefined { return this.pinStates.get(pin); } getAllPinStates(): PinState[] { return Array.from(this.pinStates.values()); } isConnected(): boolean { return this.connectionState && this.serialPort?.isOpen === true; } // Static method to list available serial ports static async listPorts(): Promise { const ports = await SerialPort.list(); return ports.map(port => port.path); } }