Skip to content

Commit 4885699

Browse files
committed
fix(parsing): more reliable read data parsing with 2-bytes characters
1 parent b65bf1d commit 4885699

File tree

8 files changed

+61
-40
lines changed

8 files changed

+61
-40
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## 2.1.0 - UNRELEASED
9+
10+
- fix: more reliable read data parsing with 2-bytes characters ([#20](https://github.com/eove/serial-console-com/issues/20))
11+
812
## 2.1.1 - 2025-07-24
913

1014
- fix: actually write to transport

cli.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,8 @@ program
108108
debugEnabled,
109109
baudRate: Number(baudrate),
110110
});
111-
const received: string[] = [];
112-
serial.data$.subscribe((d: string) => received.push(d));
111+
const received: Buffer[] = [];
112+
serial.data$.subscribe((d) => received.push(d));
113113
debug(`connecting to ${portName} at ${baudrate}`);
114114
await serial.connect(portName);
115115

@@ -127,7 +127,7 @@ program
127127
await serial.write('\n');
128128
}
129129
await delayMS(1000);
130-
console.log('received:', received.join());
130+
console.log('received:', Buffer.concat(received).toString());
131131
process.exit(0);
132132
});
133133

lib/createCommandRunner.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { createCommandRunner, CommandRunner } from './createCommandRunner';
1+
import { CommandRunner, createCommandRunner } from './createCommandRunner';
22
import { makeParseConsoleOutput } from './makeParseConsoleOutput';
33
import { createTransportMock } from './test';
44
import { from, Subject } from 'rxjs';
55

66
describe('command runner', () => {
77
let runner: CommandRunner;
8-
let subject: Subject<string>;
8+
let subject: Subject<Buffer>;
99

1010
beforeEach(() => {
1111
subject = new Subject();
@@ -63,8 +63,8 @@ describe('command runner', () => {
6363
});
6464

6565
function emitReceivedData(data: string) {
66-
for (const c of data.split('')) {
67-
subject.next(c);
66+
for (let i = 0; i < Buffer.from(data).length; i++) {
67+
subject.next(Buffer.from(data[i]));
6868
}
6969
}
7070
});

lib/createCommandRunner.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export interface CommandRunner {
3434
interface CommandRunnerDependencies {
3535
parseData: ParseConsoleOutputFunction;
3636
transport: Transport;
37-
data$: Observable<string>;
37+
data$: Observable<Buffer>;
3838
debugEnabled?: boolean;
3939
}
4040

@@ -55,14 +55,14 @@ export function createCommandRunner(
5555

5656
const answer$ = data$.pipe(
5757
scan(
58-
(acc: ParseConsoleOutputResult, byte: any) => {
58+
(acc: ParseConsoleOutputResult, current: any) => {
5959
const { remaining: remainingBytes } = acc;
60-
const received = remainingBytes.concat(...byte);
60+
const received = Buffer.concat([remainingBytes, current]);
6161
const { remaining, lines } = parseData(received);
6262
return { remaining, lines };
6363
},
6464
{
65-
remaining: '',
65+
remaining: Buffer.alloc(0),
6666
lines: [],
6767
}
6868
),

lib/createTransport.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as _ from 'lodash';
33
import { Subject } from 'rxjs';
44
import { SerialPort } from 'serialport';
55

6-
import { Device, Transport, IOCTLOptions } from './types';
6+
import { Device, IOCTLOptions, Transport } from './types';
77

88
type UninstallHandler = () => void;
99

@@ -15,7 +15,7 @@ interface TransportCreationOptions {
1515
export function createTransport(options?: TransportCreationOptions): Transport {
1616
const { debugEnabled = false } = options || {};
1717
const debug = Object.assign(debugLib('transport'), { enabled: debugEnabled });
18-
const dataSource = new Subject<string>();
18+
const dataSource = new Subject<Buffer>();
1919
const eventSource = new Subject();
2020
let port: SerialPort;
2121
let uninstallPortListeners: UninstallHandler;
@@ -66,10 +66,10 @@ export function createTransport(options?: TransportCreationOptions): Transport {
6666
_sendEvent({ type: 'TRANSPORT_CONNECTED', payload: undefined });
6767
};
6868

69-
const onDataHandler = (data: any) => {
69+
const onDataHandler = (data: Buffer) => {
7070
const received = data.toString();
7171
debug('received:', received.replace('\r', '\\r'));
72-
_sendData(received);
72+
_sendData(data);
7373
};
7474

7575
const onCloseHandler = () => {
@@ -173,7 +173,7 @@ export function createTransport(options?: TransportCreationOptions): Transport {
173173
eventSource.next(event);
174174
}
175175

176-
function _sendData(data: any) {
176+
function _sendData(data: Buffer) {
177177
dataSource.next(data);
178178
}
179179
}

lib/makeParseConsoleOutput.spec.ts

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { makeParseConsoleOutput } from './makeParseConsoleOutput';
22
import { ParseConsoleOutputResult } from './types';
33

44
describe('parse console output', () => {
5-
let parseConsoleOutput: (data: string) => ParseConsoleOutputResult;
5+
let parseConsoleOutput: (data: Buffer) => ParseConsoleOutputResult;
66

77
beforeEach(() => {
88
parseConsoleOutput = makeParseConsoleOutput({
@@ -13,91 +13,108 @@ describe('parse console output', () => {
1313

1414
it('should return a single line when output contains a line separator and prompt', () => {
1515
expect(
16-
parseConsoleOutput('drwxr-xr-x 18 root root 0 Sep 11 15:48 . \n/ #')
16+
parseConsoleOutput(
17+
Buffer.from('drwxr-xr-x 18 root root 0 Sep 11 15:48 . \n/ #')
18+
)
1719
).toEqual({
1820
lines: ['drwxr-xr-x 18 root root 0 Sep 11 15:48 .'],
19-
remaining: '',
21+
remaining: Buffer.alloc(0),
2022
});
2123
});
2224

2325
it('should return many lines when output contains line separator and prompt', () => {
2426
expect(
2527
parseConsoleOutput(
26-
'drwxr-xr-x 18 root root 0 Sep 11 15:48 . \ndrwxr-xr-x 18 root root 0 Sep 11 15:48 .. \n/ #'
28+
Buffer.from(
29+
'drwxr-xr-x 18 root root 0 Sep 11 15:48 . \ndrwxr-xr-x 18 root root 0 Sep 11 15:48 .. \n/ #'
30+
)
2731
)
2832
).toEqual({
2933
lines: [
3034
'drwxr-xr-x 18 root root 0 Sep 11 15:48 .',
3135
'drwxr-xr-x 18 root root 0 Sep 11 15:48 ..',
3236
],
33-
remaining: '',
37+
remaining: Buffer.alloc(0),
3438
});
3539
});
3640

3741
it('should return lines when output contains expected prompt with pre escape chars', () => {
3842
expect(
3943
parseConsoleOutput(
40-
'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/ #'
44+
Buffer.from(
45+
'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/ #'
46+
)
4147
)
4248
).toEqual({
4349
lines: [
4450
'drwxr-xr-x 18 root root 0 Sep 11 15:48 .',
4551
'drwxr-xr-x 18 root root 0 Sep 11 15:48 ..',
4652
],
47-
remaining: '',
53+
remaining: Buffer.alloc(0),
4854
});
4955
});
5056

5157
it('should return lines when output contains expected prompt with post escape chars', () => {
5258
expect(
5359
parseConsoleOutput(
54-
'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'
60+
Buffer.from(
61+
'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'
62+
)
5563
)
5664
).toEqual({
5765
lines: [
5866
'drwxr-xr-x 18 root root 0 Sep 11 15:48 .',
5967
'drwxr-xr-x 18 root root 0 Sep 11 15:48 ..',
6068
],
61-
remaining: '',
69+
remaining: Buffer.alloc(0),
6270
});
6371
});
6472

6573
it('should return remaining data when no prompt in output', () => {
6674
expect(
6775
parseConsoleOutput(
68-
'drwxr-xr-x 18 root root 0 Sep 11 15:48 . \ndrwxr-xr-x 18 root root 0 Sep 11 15:48 .. \n'
76+
Buffer.from(
77+
'drwxr-xr-x 18 root root 0 Sep 11 15:48 . \ndrwxr-xr-x 18 root root 0 Sep 11 15:48 .. \n'
78+
)
6979
)
7080
).toEqual({
7181
lines: [],
72-
remaining:
73-
'drwxr-xr-x 18 root root 0 Sep 11 15:48 . \ndrwxr-xr-x 18 root root 0 Sep 11 15:48 .. \n',
82+
remaining: Buffer.from(
83+
'drwxr-xr-x 18 root root 0 Sep 11 15:48 . \ndrwxr-xr-x 18 root root 0 Sep 11 15:48 .. \n'
84+
),
7485
});
7586
});
7687

7788
it('should return remaining data when no line separator in output', () => {
7889
expect(
79-
parseConsoleOutput('drwxr-xr-x 18 root root 0 Sep 11 15:48 . / #')
90+
parseConsoleOutput(
91+
Buffer.from('drwxr-xr-x 18 root root 0 Sep 11 15:48 . / #')
92+
)
8093
).toEqual({
8194
lines: [],
82-
remaining: 'drwxr-xr-x 18 root root 0 Sep 11 15:48 . / #',
95+
remaining: Buffer.from('drwxr-xr-x 18 root root 0 Sep 11 15:48 . / #'),
8396
});
8497
});
8598

8699
it('should return remaining data when prompt is not the expected one', () => {
87100
expect(
88-
parseConsoleOutput('drwxr-xr-x 18 root root 0 Sep 11 15:48 . \n/ $')
101+
parseConsoleOutput(
102+
Buffer.from('drwxr-xr-x 18 root root 0 Sep 11 15:48 . \n/ $')
103+
)
89104
).toEqual({
90105
lines: [],
91-
remaining: 'drwxr-xr-x 18 root root 0 Sep 11 15:48 . \n/ $',
106+
remaining: Buffer.from('drwxr-xr-x 18 root root 0 Sep 11 15:48 . \n/ $'),
92107
});
93108
});
94109

95110
it('should return remaining data when line separator is not the expected one', () => {
96111
expect(
97-
parseConsoleOutput('drwxr-xr-x 18 root root 0 Sep 11 15:48 . \r/ #')
112+
parseConsoleOutput(
113+
Buffer.from('drwxr-xr-x 18 root root 0 Sep 11 15:48 . \r/ #')
114+
)
98115
).toEqual({
99116
lines: [],
100-
remaining: 'drwxr-xr-x 18 root root 0 Sep 11 15:48 . \r/ #',
117+
remaining: Buffer.from('drwxr-xr-x 18 root root 0 Sep 11 15:48 . \r/ #'),
101118
});
102119
});
103120
});

lib/makeParseConsoleOutput.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,16 @@ export function makeParseConsoleOutput(
1515
lineSeparator: '\n',
1616
});
1717

18-
return (data: string) => {
18+
return (data: Buffer) => {
1919
const regex = new RegExp(`(.*)${lineSeparator}(.*${prompt}.*)`, 'sm');
20-
const found = data.match(regex);
20+
const found = data.toString().match(regex);
2121
if (found && found.length) {
2222
return {
2323
lines: found[1]
2424
.split(/\r\n|\r|\n/)
2525
.map((l) => l.trim())
2626
.filter((x) => x),
27-
remaining: '',
27+
remaining: Buffer.alloc(0),
2828
};
2929
}
3030
return {

lib/types.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import { Observable } from 'rxjs';
22

33
export interface ParseConsoleOutputResult {
44
lines: string[];
5-
remaining: string;
5+
remaining: Buffer;
66
}
77

88
export type ParseConsoleOutputFunction = (
9-
data: string
9+
data: Buffer
1010
) => ParseConsoleOutputResult;
1111

1212
export interface Device {
@@ -26,7 +26,7 @@ export interface Transport {
2626
write: (bytes: string) => Promise<any>;
2727
discover: () => Promise<Device[]>;
2828
ioctl: (options: IOCTLOptions) => Promise<void>;
29-
data$: Observable<string>;
29+
data$: Observable<Buffer>;
3030
event$: Observable<unknown>;
3131
connected: boolean;
3232
}

0 commit comments

Comments
 (0)