import EventEmitter from 'events'; import type { UpdateOptions, SetOptions } from 'serialport' type ErrorCallback = (error?: Error | null) => void; type BaudRate = 115200 | 57600 | 38400 | 19200 | 9600 | 4800 | 2400 | 1800 | 1200 | 600 | 300 | 200 | 150 | 134 | 110 | 75 | 50 | number; interface OpenOptions { baudRate?: BaudRate; dataBits?: number; stopBits?: number; parity?: string; } // navigator.serial.addEventListener('connect', (event) => { // console.log('connect:', event) // }) class SerialPortAdapter extends EventEmitter { private wsp: SerialPort; private openOptions: OpenOptions; private writer?: WritableStreamDefaultWriter; private reader?: ReadableStreamDefaultReader; private pollDataPromise?: Promise; public isOpen = false; public baudRate: BaudRate; constructor( webSerialPort: SerialPort, options?: OpenOptions ) { super(); this.wsp = webSerialPort; this.openOptions = { baudRate: 57600, ...options }; this.baudRate = this.openOptions.baudRate || 57600; } public open(callback?: ErrorCallback) { const opts = { baudRate: this.baudRate }; return this.wsp.open(opts).then(() => { const { writable, readable } = this.wsp; if (writable) { this.writer = writable.getWriter(); } if (readable) { this.reader = readable.getReader(); this.pollDataPromise = this.pollData(); } this.addDisconnectListener(); this.isOpen = true; this.emit('open', null) if (callback) callback(null) }).catch((error) => { this.emit('open', error) if (callback) callback(error) }) } private async pollData() { if (!this.reader) return; const { readable } = this.wsp; try { while (readable && readable.locked) { const { value, done } = await this.reader.read(); if (done) { break; } if (value) { const buffer = Buffer.from(value) this.emit('data', buffer); } } } catch (error) { console.error('pollData:', error) } console.log('pollData:', 'done') } public async close(callback?: ErrorCallback) { try { if (this.reader) { await Promise.allSettled([ this.reader.cancel(), this.reader.releaseLock(), this.pollDataPromise, ]) } if (this.writer) { await Promise.allSettled([ this.writer.close(), this.writer.releaseLock() ]) } await this.wsp.close() this.isOpen = false; this.emit('close', null) if (callback) callback(null); this.removeDisconnectListener(); } catch (error) { if (callback) callback(error as Error) } } public async update(options: UpdateOptions, callback?: ErrorCallback) { const { baudRate } = options; if (!baudRate || baudRate === this.baudRate) { if (callback) callback(null) return; } try { await this.close(); this.baudRate = baudRate; await this.open(); if (callback) callback(null) } catch (error) { if (callback) callback(error as Error) } } public set(props: SetOptions, callback?: ErrorCallback) { const signals: SerialOutputSignals = {}; if (Object.prototype.hasOwnProperty.call(props, 'dtr')) { signals.dataTerminalReady = props.dtr; } if (Object.prototype.hasOwnProperty.call(props, 'rts')) { signals.requestToSend = props.rts; } if (Object.prototype.hasOwnProperty.call(props, 'brk')) { signals.break = props.brk; } if (Object.keys(signals).length > 0) { this.wsp.setSignals(signals) .then(() => { if (callback) callback(null) }).catch((error) => { if (callback) callback(error) }) } } public write(data: string | number[] | Buffer, callback?: ErrorCallback) { if (!this.writer) { if (callback) callback(Error('writer is nil')) return } const buffer = Buffer.from(data) this.writer.write(buffer).then( () => { if (callback) callback(null); }, (error) => { if (callback) callback(error); } ) console.log('write:', buffer.toString('hex')) } public drain(callback?: ErrorCallback) { if (callback) callback(null); } private addDisconnectListener() { navigator.serial.addEventListener('disconnect', this.disconnectHandler) } private removeDisconnectListener() { navigator.serial.removeEventListener('disconnect', this.disconnectHandler) } private disconnectHandler = (event: Event) => { if (event.target === this.wsp) { this.close(); } } } export { SerialPortAdapter };