diff --git a/CHANGELOG.md b/CHANGELOG.md index 263cbdb..fc8b702 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 2.1.2 - UNRELEASED + +- fix: more reliable read data parsing with 2-bytes characters ([#20](https://github.com/eove/serial-console-com/issues/20)) + ## 2.1.1 - 2025-07-24 - fix: actually write to transport diff --git a/cli.ts b/cli.ts index 04e3b85..b681e9d 100755 --- a/cli.ts +++ b/cli.ts @@ -108,8 +108,8 @@ program debugEnabled, baudRate: Number(baudrate), }); - const received: string[] = []; - serial.data$.subscribe((d: string) => received.push(d)); + const received: Buffer[] = []; + serial.data$.subscribe((d) => received.push(d)); debug(`connecting to ${portName} at ${baudrate}`); await serial.connect(portName); @@ -127,7 +127,7 @@ program await serial.write('\n'); } await delayMS(1000); - console.log('received:', received.join()); + console.log('received:', Buffer.concat(received).toString()); process.exit(0); }); diff --git a/lib/createCommandRunner.spec.ts b/lib/createCommandRunner.spec.ts index e6d98eb..460e457 100644 --- a/lib/createCommandRunner.spec.ts +++ b/lib/createCommandRunner.spec.ts @@ -1,11 +1,11 @@ -import { createCommandRunner, CommandRunner } from './createCommandRunner'; +import { CommandRunner, createCommandRunner } from './createCommandRunner'; import { makeParseConsoleOutput } from './makeParseConsoleOutput'; import { createTransportMock } from './test'; import { from, Subject } from 'rxjs'; describe('command runner', () => { let runner: CommandRunner; - let subject: Subject; + let subject: Subject; beforeEach(() => { subject = new Subject(); @@ -63,8 +63,8 @@ describe('command runner', () => { }); function emitReceivedData(data: string) { - for (const c of data.split('')) { - subject.next(c); + for (let i = 0; i < Buffer.from(data).length; i++) { + subject.next(Buffer.from(data[i])); } } }); diff --git a/lib/createCommandRunner.ts b/lib/createCommandRunner.ts index d1a16ba..b435b9a 100644 --- a/lib/createCommandRunner.ts +++ b/lib/createCommandRunner.ts @@ -34,7 +34,7 @@ export interface CommandRunner { interface CommandRunnerDependencies { parseData: ParseConsoleOutputFunction; transport: Transport; - data$: Observable; + data$: Observable; debugEnabled?: boolean; } @@ -55,14 +55,14 @@ export function createCommandRunner( const answer$ = data$.pipe( scan( - (acc: ParseConsoleOutputResult, byte: any) => { + (acc: ParseConsoleOutputResult, current: any) => { const { remaining: remainingBytes } = acc; - const received = remainingBytes.concat(...byte); + const received = Buffer.concat([remainingBytes, current]); const { remaining, lines } = parseData(received); return { remaining, lines }; }, { - remaining: '', + remaining: Buffer.alloc(0), lines: [], } ), diff --git a/lib/createTransport.ts b/lib/createTransport.ts index fbb64c2..22595dc 100644 --- a/lib/createTransport.ts +++ b/lib/createTransport.ts @@ -3,7 +3,7 @@ import * as _ from 'lodash'; import { Subject } from 'rxjs'; import { SerialPort } from 'serialport'; -import { Device, Transport, IOCTLOptions } from './types'; +import { Device, IOCTLOptions, Transport } from './types'; type UninstallHandler = () => void; @@ -15,7 +15,7 @@ interface TransportCreationOptions { export function createTransport(options?: TransportCreationOptions): Transport { const { debugEnabled = false } = options || {}; const debug = Object.assign(debugLib('transport'), { enabled: debugEnabled }); - const dataSource = new Subject(); + const dataSource = new Subject(); const eventSource = new Subject(); let port: SerialPort; let uninstallPortListeners: UninstallHandler; @@ -66,10 +66,10 @@ export function createTransport(options?: TransportCreationOptions): Transport { _sendEvent({ type: 'TRANSPORT_CONNECTED', payload: undefined }); }; - const onDataHandler = (data: any) => { + const onDataHandler = (data: Buffer) => { const received = data.toString(); debug('received:', received.replace('\r', '\\r')); - _sendData(received); + _sendData(data); }; const onCloseHandler = () => { @@ -173,7 +173,7 @@ export function createTransport(options?: TransportCreationOptions): Transport { eventSource.next(event); } - function _sendData(data: any) { + function _sendData(data: Buffer) { dataSource.next(data); } } diff --git a/lib/makeParseConsoleOutput.spec.ts b/lib/makeParseConsoleOutput.spec.ts index 5b95a24..2e0e0bf 100644 --- a/lib/makeParseConsoleOutput.spec.ts +++ b/lib/makeParseConsoleOutput.spec.ts @@ -2,7 +2,7 @@ import { makeParseConsoleOutput } from './makeParseConsoleOutput'; import { ParseConsoleOutputResult } from './types'; describe('parse console output', () => { - let parseConsoleOutput: (data: string) => ParseConsoleOutputResult; + let parseConsoleOutput: (data: Buffer) => ParseConsoleOutputResult; beforeEach(() => { parseConsoleOutput = makeParseConsoleOutput({ @@ -13,91 +13,108 @@ describe('parse console output', () => { it('should return a single line when output contains a line separator and prompt', () => { expect( - parseConsoleOutput('drwxr-xr-x 18 root root 0 Sep 11 15:48 . \n/ #') + parseConsoleOutput( + Buffer.from('drwxr-xr-x 18 root root 0 Sep 11 15:48 . \n/ #') + ) ).toEqual({ lines: ['drwxr-xr-x 18 root root 0 Sep 11 15:48 .'], - remaining: '', + remaining: Buffer.alloc(0), }); }); it('should return many lines when output contains line separator and prompt', () => { expect( parseConsoleOutput( - 'drwxr-xr-x 18 root root 0 Sep 11 15:48 . \ndrwxr-xr-x 18 root root 0 Sep 11 15:48 .. \n/ #' + Buffer.from( + 'drwxr-xr-x 18 root root 0 Sep 11 15:48 . \ndrwxr-xr-x 18 root root 0 Sep 11 15:48 .. \n/ #' + ) ) ).toEqual({ lines: [ 'drwxr-xr-x 18 root root 0 Sep 11 15:48 .', 'drwxr-xr-x 18 root root 0 Sep 11 15:48 ..', ], - remaining: '', + remaining: Buffer.alloc(0), }); }); it('should return lines when output contains expected prompt with pre escape chars', () => { expect( parseConsoleOutput( - 'drwxr-xr-x 18 root root 0 Sep 11 15:48 . \ndrwxr-xr-x 18 root root 0 Sep 11 15:48 .. \n\u001b[1;32m/ #' + Buffer.from( + 'drwxr-xr-x 18 root root 0 Sep 11 15:48 . \ndrwxr-xr-x 18 root root 0 Sep 11 15:48 .. \n\u001b[1;32m/ #' + ) ) ).toEqual({ lines: [ 'drwxr-xr-x 18 root root 0 Sep 11 15:48 .', 'drwxr-xr-x 18 root root 0 Sep 11 15:48 ..', ], - remaining: '', + remaining: Buffer.alloc(0), }); }); it('should return lines when output contains expected prompt with post escape chars', () => { expect( parseConsoleOutput( - 'drwxr-xr-x 18 root root 0 Sep 11 15:48 . \ndrwxr-xr-x 18 root root 0 Sep 11 15:48 .. \n/ #\u001b[1;32m' + Buffer.from( + 'drwxr-xr-x 18 root root 0 Sep 11 15:48 . \ndrwxr-xr-x 18 root root 0 Sep 11 15:48 .. \n/ #\u001b[1;32m' + ) ) ).toEqual({ lines: [ 'drwxr-xr-x 18 root root 0 Sep 11 15:48 .', 'drwxr-xr-x 18 root root 0 Sep 11 15:48 ..', ], - remaining: '', + remaining: Buffer.alloc(0), }); }); it('should return remaining data when no prompt in output', () => { expect( parseConsoleOutput( - 'drwxr-xr-x 18 root root 0 Sep 11 15:48 . \ndrwxr-xr-x 18 root root 0 Sep 11 15:48 .. \n' + Buffer.from( + 'drwxr-xr-x 18 root root 0 Sep 11 15:48 . \ndrwxr-xr-x 18 root root 0 Sep 11 15:48 .. \n' + ) ) ).toEqual({ lines: [], - remaining: - 'drwxr-xr-x 18 root root 0 Sep 11 15:48 . \ndrwxr-xr-x 18 root root 0 Sep 11 15:48 .. \n', + remaining: Buffer.from( + 'drwxr-xr-x 18 root root 0 Sep 11 15:48 . \ndrwxr-xr-x 18 root root 0 Sep 11 15:48 .. \n' + ), }); }); it('should return remaining data when no line separator in output', () => { expect( - parseConsoleOutput('drwxr-xr-x 18 root root 0 Sep 11 15:48 . / #') + parseConsoleOutput( + Buffer.from('drwxr-xr-x 18 root root 0 Sep 11 15:48 . / #') + ) ).toEqual({ lines: [], - remaining: 'drwxr-xr-x 18 root root 0 Sep 11 15:48 . / #', + remaining: Buffer.from('drwxr-xr-x 18 root root 0 Sep 11 15:48 . / #'), }); }); it('should return remaining data when prompt is not the expected one', () => { expect( - parseConsoleOutput('drwxr-xr-x 18 root root 0 Sep 11 15:48 . \n/ $') + parseConsoleOutput( + Buffer.from('drwxr-xr-x 18 root root 0 Sep 11 15:48 . \n/ $') + ) ).toEqual({ lines: [], - remaining: 'drwxr-xr-x 18 root root 0 Sep 11 15:48 . \n/ $', + remaining: Buffer.from('drwxr-xr-x 18 root root 0 Sep 11 15:48 . \n/ $'), }); }); it('should return remaining data when line separator is not the expected one', () => { expect( - parseConsoleOutput('drwxr-xr-x 18 root root 0 Sep 11 15:48 . \r/ #') + parseConsoleOutput( + Buffer.from('drwxr-xr-x 18 root root 0 Sep 11 15:48 . \r/ #') + ) ).toEqual({ lines: [], - remaining: 'drwxr-xr-x 18 root root 0 Sep 11 15:48 . \r/ #', + remaining: Buffer.from('drwxr-xr-x 18 root root 0 Sep 11 15:48 . \r/ #'), }); }); }); diff --git a/lib/makeParseConsoleOutput.ts b/lib/makeParseConsoleOutput.ts index 47df025..5d34234 100644 --- a/lib/makeParseConsoleOutput.ts +++ b/lib/makeParseConsoleOutput.ts @@ -15,16 +15,16 @@ export function makeParseConsoleOutput( lineSeparator: '\n', }); - return (data: string) => { + return (data: Buffer) => { const regex = new RegExp(`(.*)${lineSeparator}(.*${prompt}.*)`, 'sm'); - const found = data.match(regex); + const found = data.toString().match(regex); if (found && found.length) { return { lines: found[1] .split(/\r\n|\r|\n/) .map((l) => l.trim()) .filter((x) => x), - remaining: '', + remaining: Buffer.alloc(0), }; } return { diff --git a/lib/types.ts b/lib/types.ts index 81f0843..03d0e66 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -2,11 +2,11 @@ import { Observable } from 'rxjs'; export interface ParseConsoleOutputResult { lines: string[]; - remaining: string; + remaining: Buffer; } export type ParseConsoleOutputFunction = ( - data: string + data: Buffer ) => ParseConsoleOutputResult; export interface Device { @@ -26,7 +26,7 @@ export interface Transport { write: (bytes: string) => Promise; discover: () => Promise; ioctl: (options: IOCTLOptions) => Promise; - data$: Observable; + data$: Observable; event$: Observable; connected: boolean; } diff --git a/package-lock.json b/package-lock.json index 89d8a82..222f237 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@eove/serial-console-com", - "version": "2.1.1", + "version": "2.1.2-i20.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@eove/serial-console-com", - "version": "2.1.1", + "version": "2.1.2-i20.1", "license": "MIT", "dependencies": { "@arpinum/promising": "3.1.0", diff --git a/package.json b/package.json index 8b017e5..7c6b9f2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@eove/serial-console-com", - "version": "2.1.1", + "version": "2.1.2-i20.1", "description": "library to communicate with a (unix) console over a serial line", "bin": "./cli.js", "main": "build/index.js", diff --git a/tsconfig.json b/tsconfig.json index cbc6726..5cfd28f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,8 @@ "forceConsistentCasingInFileNames": true, "noImplicitReturns": true, "noUnusedLocals": true, - "noUnusedParameters": true + "noUnusedParameters": true, + "skipLibCheck": true }, "include": ["./lib/**/*"], "exclude": ["node_modules", "build", "**/*.spec.ts", "**/*.spec.js"]