export type SerialDataCallback = (data: Uint8Array) => void; // Define Web Serial API types interface SerialPortInfo { usbVendorId?: number; usbProductId?: number; } interface SerialPort { open(options: { baudRate: number }): Promise; close(): Promise; getInfo(): SerialPortInfo; readable: ReadableStream | null; writable: WritableStream | null; } declare global { interface Navigator { serial: { requestPort(options?: any): Promise; }; } } export class SerialPortHandler { private port: SerialPort | null = null; private reader: ReadableStreamDefaultReader | null = null; private writer: WritableStreamDefaultWriter | null = null; private isConnected: boolean = false; private onData: SerialDataCallback; constructor(onData: SerialDataCallback) { this.onData = onData; } async connect(baudRate: number = 9600) { if (!navigator.serial) { throw new Error('Web Serial API not supported in this browser.'); } try { this.port = await navigator.serial.requestPort(); await this.port.open({ baudRate }); if (this.port.readable) { this.reader = this.port.readable.getReader(); } if (this.port.writable) { this.writer = this.port.writable.getWriter(); } this.isConnected = true; this.readLoop(); } catch (error: any) { if (error.name === 'NotFoundError') { throw new Error('USER_CANCELLED'); } console.error('Serial connection failed:', error); this.disconnect(); throw error; } } async disconnect() { if (this.reader) { await this.reader.cancel(); this.reader.releaseLock(); } if (this.writer) { await this.writer.close(); this.writer.releaseLock(); } if (this.port) { await this.port.close(); } this.isConnected = false; this.port = null; } async send(data: string | Uint8Array) { if (this.writer && this.isConnected) { let payload: Uint8Array; if (typeof data === 'string') { payload = new TextEncoder().encode(data + '\n'); } else { payload = data; } try { await this.writer.write(payload); } catch (e) { console.error("Serial Send Error", e); throw e; // Re-throw to let caller know if needed, or handle gracefull } } } getPortInfo(): string | null { if (this.port) { const info = this.port.getInfo(); if (info.usbVendorId && info.usbProductId) { return `ID:${info.usbVendorId.toString(16).padStart(4, '0').toUpperCase()}:${info.usbProductId.toString(16).padStart(4, '0').toUpperCase()}`; } return "USB Device"; } return null; } private async readLoop() { while (true) { try { const { value, done } = await this.reader!.read(); if (done) { break; } if (value) { this.onData(value); } } catch (error) { console.error('Serial read error:', error); break; } } } get connected() { return this.isConnected; } }