From 7cc4bba41763770103fcc38d040ad76862300b0a Mon Sep 17 00:00:00 2001 From: Robson Oliveira dos Santos Date: Tue, 24 Dec 2024 11:36:15 +0930 Subject: [PATCH] feat(spie): create data delimited event --- .../src/app/services/serial-port.service.ts | 14 ++++ .../spie/src/app/events/serial-port.events.ts | 64 +++++++++++-------- libs/types/src/lib/electron.d.ts | 6 +- 3 files changed, 56 insertions(+), 28 deletions(-) diff --git a/apps/spie-ui/src/app/services/serial-port.service.ts b/apps/spie-ui/src/app/services/serial-port.service.ts index d323a15..6253860 100644 --- a/apps/spie-ui/src/app/services/serial-port.service.ts +++ b/apps/spie-ui/src/app/services/serial-port.service.ts @@ -89,4 +89,18 @@ export class SerialPortService { serialPortEvent.type === 'data' || serialPortEvent.type === 'clear' ) ); + + dataDelimitedEvent$: Observable = toObservable(this.isOpen).pipe( + switchMap(() => + merge( + this.electronService.serialPort.onEvent(), + this.clearDataSubject.pipe(map(() => ({ type: 'clear' } as DataEvent))) + ) + ), + filter( + (serialPortEvent) => + serialPortEvent.type === 'data-delimited' || + serialPortEvent.type === 'clear' + ) + ); } diff --git a/apps/spie/src/app/events/serial-port.events.ts b/apps/spie/src/app/events/serial-port.events.ts index 86650d3..76dd44c 100644 --- a/apps/spie/src/app/events/serial-port.events.ts +++ b/apps/spie/src/app/events/serial-port.events.ts @@ -5,11 +5,9 @@ import type { SerialPortEventType, } from '@spie/types'; import { ipcMain } from 'electron'; -import { InterByteTimeoutParser, SerialPort } from 'serialport'; +import { ReadlineParser, SerialPort } from 'serialport'; export default class SerialPortEvents { - private static serialPort: SerialPort | null = null; - private static parser: InterByteTimeoutParser | null = null; private static eventListeners = new Map< SerialPortEventType, (...args: any[]) => void @@ -18,6 +16,9 @@ export default class SerialPortEvents { SerialPortEventType, (...args: any[]) => void >(); + private static serialPort: SerialPort | null = null; + private static parser: ReadlineParser | null = null; + private static parserCallback: (data: string) => void | null = null; private static encoding: Encoding = 'ascii'; private static areListenersRegistered = false; private static openOptions: OpenOptions | null = null; @@ -33,11 +34,7 @@ export default class SerialPortEvents { serialPortEventType: SerialPortEventType, callback: (...args: any[]) => void ) => { - if ( - !SerialPortEvents.serialPort || - !SerialPortEvents.parser || - !SerialPortEvents.serialPort.isOpen - ) { + if (!SerialPortEvents.serialPort || !SerialPortEvents.serialPort.isOpen) { if (!SerialPortEvents.listenerQueue.has(serialPortEventType)) { // console.log('SerialPortEvents.addEventListener queue', serialPortEventType, callback); // Port is not open, queue the callback @@ -49,13 +46,17 @@ export default class SerialPortEvents { if (!SerialPortEvents.eventListeners.has(serialPortEventType)) { // console.log('SerialPortEvents.addEventListener attach', serialPortEventType, callback); // Port is open, attach callback immediately - if (serialPortEventType === 'data') { - SerialPortEvents.parser.on(serialPortEventType, callback); - } else { - SerialPortEvents.serialPort.on(serialPortEventType, callback); - } + SerialPortEvents.serialPort.on(serialPortEventType, callback); SerialPortEvents.eventListeners.set(serialPortEventType, callback); } + + if ( + SerialPortEvents.parser && + SerialPortEvents.parser.listenerCount('data') === 0 && + SerialPortEvents.parserCallback + ) { + SerialPortEvents.parser.on('data', SerialPortEvents.parserCallback); + } }; addEventListener('error', (error: Error) => { @@ -73,7 +74,7 @@ export default class SerialPortEvents { event.sender.send('serial-port-event', notification); }); - addEventListener('data', (chunk: any) => { + addEventListener('data', (chunk: Buffer) => { const data: string = SerialPortEvents.encoding === 'hex' ? chunk.toString('hex').toUpperCase().match(/.{2}/g).join(' ') @@ -88,6 +89,11 @@ export default class SerialPortEvents { event.sender.send('serial-port-event', notification); }); + SerialPortEvents.parserCallback = (data: string) => { + const notification: SerialPortEvent = { type: 'data-delimited', data }; + event.sender.send('serial-port-event', notification); + }; + SerialPortEvents.areListenersRegistered = true; return Promise.resolve(); @@ -99,14 +105,15 @@ export default class SerialPortEvents { } SerialPortEvents.eventListeners.forEach((callback, serialPortEventType) => { - // console.log('SerialPortEvents.removeEventListener', serialPortEventType, callback); - if (serialPortEventType === 'data') { - SerialPortEvents.parser.off(serialPortEventType, callback); - } else { - SerialPortEvents.serialPort.off(serialPortEventType, callback); - } + // console.log('SerialPortEvents.serialPort.off', serialPortEventType, callback); + SerialPortEvents.serialPort.off(serialPortEventType, callback); }); + if (SerialPortEvents.parser) { + // console.log('SerialPortEvents.parser.off', serialPortEventType, callback); + SerialPortEvents.parser.off('data', SerialPortEvents.parserCallback); + } + SerialPortEvents.eventListeners.clear(); SerialPortEvents.areListenersRegistered = false; @@ -139,25 +146,28 @@ export default class SerialPortEvents { } ); - // INFO: InterByteTimeoutParser should become a toggleable parameter if the added "delay" becomes a problem. SerialPortEvents.parser = SerialPortEvents.serialPort.pipe( - new InterByteTimeoutParser({ interval: 5 }) + new ReadlineParser({ includeDelimiter: true }) // TODO: remove includeDelimiter ); // Process queued listeners after opening SerialPortEvents.listenerQueue.forEach( (callback, serialPortEventType) => { // console.log(`Processing queued listener for serialPortEventType: ${serialPortEventType}`); - if (serialPortEventType === 'data') { - SerialPortEvents.parser.on(serialPortEventType, callback); - } else { - SerialPortEvents.serialPort.on(serialPortEventType, callback); - } + SerialPortEvents.serialPort.on(serialPortEventType, callback); SerialPortEvents.eventListeners.set(serialPortEventType, callback); } ); SerialPortEvents.listenerQueue.clear(); + if ( + SerialPortEvents.parser && + SerialPortEvents.parser.listenerCount('data') === 0 && + SerialPortEvents.parserCallback + ) { + SerialPortEvents.parser.on('data', SerialPortEvents.parserCallback); + } + SerialPortEvents.serialPort.open((error) => { if (error) { return reject(error); diff --git a/libs/types/src/lib/electron.d.ts b/libs/types/src/lib/electron.d.ts index 05d09f0..7e6d6bc 100644 --- a/libs/types/src/lib/electron.d.ts +++ b/libs/types/src/lib/electron.d.ts @@ -28,9 +28,13 @@ export type SerialPortEvent = | { type: 'open' } | { type: 'close' } | { type: 'data'; data: string } + | { type: 'data-delimited'; data: string } | { type: 'drain' }; -export type DataEvent = { type: 'data'; data: string } | { type: 'clear' }; +export type DataEvent = + | { type: 'data'; data: string } + | { type: 'data-delimited'; data: string } + | { type: 'clear' }; export interface SerialPortAPI { list: () => Promise;