Skip to content

Commit 1f430c8

Browse files
author
Xelio Cheong
committed
Improvement for core and network adapter.
core: - core getStatus(): change buffer.write() to adapter.write(), becuase buffer.write() does not send data to print immediately. - core getStatuses(): change to retrieve status one by one, because network printer does not always return all 4 status in a single read() operation. - Export class StatusJSON, StatusJSONElement, StatusJSONElementSingle, StatusJSONElementMultiple - Add test for core getStatus() and getStatuses() network adapter: - network adapter open(): remove err from connectListener of .connect(), it's defination does not have err param. - network adapter read(): change .on() to .once(), using .on() will run previous callback again in next read() operation, which is not the expected behavior. - Add read timeout for network adapter. When timeout occur, it will pass empty buffer to callback function. - Add error handling in getStatus() and getStatuses(). - Add test of getStatus(), getStatuses() for network adapter.
1 parent 3861181 commit 1f430c8

File tree

6 files changed

+401
-47
lines changed

6 files changed

+401
-47
lines changed

packages/core/src/index.ts

Lines changed: 51 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -905,15 +905,26 @@ export class Printer<AdapterCloseArgs extends []> extends EventEmitter {
905905
* @return {Promise} promise returning given status
906906
*/
907907
getStatus<T extends DeviceStatus>(StatusClass: StatusClassConstructor<T>): Promise<T> {
908-
return new Promise((resolve) => {
909-
this.adapter.read((data) => {
910-
const byte = data.readInt8(0);
911-
resolve(new StatusClass(byte));
908+
return new Promise<T>((resolve, reject) => {
909+
this.adapter.read((data: Buffer) => {
910+
try {
911+
if(data.length === 0) {
912+
return reject(new Error("Get status timeout"));
913+
}
914+
const byte = data.readInt8(0);
915+
resolve(new StatusClass(byte));
916+
} catch (err) {
917+
if (typeof err === "string") {
918+
console.error(err);
919+
reject(new Error(err));
920+
} else {
921+
console.error(err);
922+
reject(err);
923+
}
924+
}
912925
});
913926

914-
StatusClass.commands().forEach((c) => {
915-
this.buffer.write(c);
916-
});
927+
this.adapter.write(StatusClass.commands().join(''));
917928
});
918929
}
919930

@@ -922,26 +933,25 @@ export class Printer<AdapterCloseArgs extends []> extends EventEmitter {
922933
* @return {Promise}
923934
*/
924935
getStatuses(): Promise<DeviceStatus[]> {
925-
return new Promise((resolve, reject) => {
926-
this.adapter.read((data) => {
927-
const buffer: number[] = [];
928-
for (let i = 0; i < data.byteLength; i++) buffer.push(data.readInt8(i));
929-
if (buffer.length < 4) return reject();
930-
931-
const statuses = [
932-
new PrinterStatus(buffer[0]),
933-
new RollPaperSensorStatus(buffer[1]),
934-
new OfflineCauseStatus(buffer[2]),
935-
new ErrorCauseStatus(buffer[3]),
936-
];
937-
resolve(statuses);
938-
});
939-
940-
[PrinterStatus, RollPaperSensorStatus, OfflineCauseStatus, ErrorCauseStatus].forEach((statusClass) => {
941-
statusClass.commands().forEach((command) => {
942-
this.adapter.write(command);
943-
});
944-
});
936+
return new Promise<DeviceStatus[]> (async (resolve, reject) => {
937+
const results:DeviceStatus[] = [];
938+
939+
try {
940+
results.push(await this.getStatus(PrinterStatus));
941+
results.push(await this.getStatus(RollPaperSensorStatus));
942+
results.push(await this.getStatus(OfflineCauseStatus));
943+
results.push(await this.getStatus(ErrorCauseStatus));
944+
945+
resolve(results);
946+
} catch (err) {
947+
if (typeof err === "string") {
948+
console.error(err);
949+
reject(new Error(err));
950+
} else {
951+
console.error(err);
952+
reject(err);
953+
}
954+
}
945955
});
946956
}
947957

@@ -1091,3 +1101,17 @@ export class Printer<AdapterCloseArgs extends []> extends EventEmitter {
10911101
export default Printer;
10921102
export { default as Image } from "./image";
10931103
export const command = _;
1104+
export {
1105+
ErrorCauseStatus,
1106+
OfflineCauseStatus,
1107+
PrinterStatus,
1108+
RollPaperSensorStatus,
1109+
} from "./statuses";
1110+
export type {
1111+
DeviceStatus,
1112+
StatusClassConstructor,
1113+
StatusJSON,
1114+
StatusJSONElement,
1115+
StatusJSONElementSingle,
1116+
StatusJSONElementMultiple,
1117+
} from "./statuses";

packages/core/src/statuses.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,23 @@ enum Status {
66
Error = "error",
77
}
88

9-
interface StatusJSONElementSingle {
9+
export interface StatusJSONElementSingle {
1010
bit: number
1111
value: 0 | 1
1212
label: string
1313
status: Status
1414
}
1515

16-
interface StatusJSONElementMultiple {
16+
export interface StatusJSONElementMultiple {
1717
bit: string
1818
value: string
1919
label: string
2020
status: Status
2121
}
2222

23-
type StatusJSONElement = StatusJSONElementSingle | StatusJSONElementMultiple;
23+
export type StatusJSONElement = StatusJSONElementSingle | StatusJSONElementMultiple;
2424

25-
interface StatusJSON<T extends string> {
25+
export interface StatusJSON<T extends string> {
2626
className: T
2727
byte: number
2828
bits: string

packages/core/test/index.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { describe, expect, it } from 'vitest'
2-
import { splitForCode128, genCode128forXprinter } from '../src/utils';
1+
import { describe, expect, it, vi } from 'vitest'
2+
import { splitForCode128, genCode128forXprinter } from '../src/utils';
33

44
describe('should', () => {
5+
56
it('exported', () => {
67
expect(1).toEqual(1)
78
});

packages/core/test/status.test.ts

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
2+
import { Adapter } from '@node-escpos/adapter';
3+
import { ErrorCauseStatus, OfflineCauseStatus, Printer, PrinterStatus, RollPaperSensorStatus } from '..//src';
4+
5+
class MockAdapter extends Adapter<[]> {
6+
open = vi.fn()
7+
write = vi.fn()
8+
close = vi.fn()
9+
read = vi.fn()
10+
}
11+
12+
describe('should', () => {
13+
const adapter = new MockAdapter();
14+
const dataWrote = vi.fn();
15+
16+
// flag to return empty data on getting OnOfflineCauseStatus
17+
let returnEmptyDataOnOfflineCauseStatus = false;
18+
19+
beforeEach(() => {
20+
let readResolver: (value: string|PromiseLike<string>) => void;
21+
let readRejecter: (reason?: any) => void;
22+
returnEmptyDataOnOfflineCauseStatus = false;
23+
24+
adapter.read.mockImplementation(async (callback?: (data: Buffer) => void) => {
25+
// promise to wait for data writing
26+
const promise = new Promise<string>((resolve, reject) => {
27+
// save resolve and reject
28+
readResolver = resolve;
29+
readRejecter = reject;
30+
});
31+
const result = await promise;
32+
if (callback) callback(Buffer.from(result));
33+
});
34+
35+
adapter.write.mockImplementation((data: string | Buffer, callback?: (error: Error | null) => void) => {
36+
const normalizedData = data.toString();
37+
dataWrote(normalizedData)
38+
39+
// return different data depending on the command received
40+
switch(normalizedData) {
41+
case PrinterStatus.commands().join(''):
42+
readResolver('\x16');
43+
break;
44+
case RollPaperSensorStatus.commands().join(''):
45+
readResolver('\x17');
46+
break;
47+
case OfflineCauseStatus.commands().join(''):
48+
if (returnEmptyDataOnOfflineCauseStatus) {
49+
readResolver("");
50+
} else {
51+
readResolver('\x18');
52+
}
53+
break;
54+
case ErrorCauseStatus.commands().join(''):
55+
readResolver('\x19');
56+
break;
57+
default:
58+
readRejecter(new Error("Unknown data wrote"));
59+
break;
60+
}
61+
});
62+
});
63+
64+
afterEach(() => {
65+
vi.clearAllMocks();
66+
});
67+
68+
it('adapter receive correct data from getStatus', async () => {
69+
const printer = new Printer(adapter, {})
70+
await printer.getStatus(PrinterStatus);
71+
72+
expect(adapter.write).toHaveBeenCalledOnce();
73+
74+
expect(dataWrote).toBeCalledWith(PrinterStatus.commands().join(''));
75+
})
76+
77+
it('data return from adapter can create correct status with correct byte', async () => {
78+
const printer = new Printer(adapter, {})
79+
const printerStatus = await printer.getStatus(PrinterStatus);
80+
81+
expect(adapter.read).toHaveBeenCalledOnce();
82+
83+
expect(printerStatus.byte).toEqual(22);
84+
})
85+
86+
it('getStatuses return all statues with correct byte', async () => {
87+
const printer = new Printer(adapter, {})
88+
const printerStatuses = await printer.getStatuses();
89+
90+
expect(adapter.write).toHaveBeenCalledTimes(4);
91+
expect(adapter.read).toHaveBeenCalledTimes(4);
92+
expect(printerStatuses.length).toEqual(4);
93+
94+
printerStatuses
95+
.map((status) => status.toJSON())
96+
.forEach((json) => {
97+
switch(json.className) {
98+
case PrinterStatus.name:
99+
expect(json.byte).toEqual(22);
100+
break;
101+
case RollPaperSensorStatus.name:
102+
expect(json.byte).toEqual(23);
103+
break;
104+
case OfflineCauseStatus.name:
105+
expect(json.byte).toEqual(24);
106+
break;
107+
case ErrorCauseStatus.name:
108+
expect(json.byte).toEqual(25);
109+
break;
110+
default:
111+
expect(false, "unexpected DeviceStatus class:" + json.className).toBeTruthy();
112+
break;
113+
}
114+
})
115+
})
116+
117+
it('getStatus throw error when receive empty buffer from adapter', async () => {
118+
returnEmptyDataOnOfflineCauseStatus = true;
119+
let receivedError: Error | null = null;
120+
121+
const printer = new Printer(adapter, {})
122+
try {
123+
await printer.getStatus(OfflineCauseStatus);
124+
} catch (err) {
125+
if (err instanceof Error) {
126+
receivedError = err;
127+
}
128+
}
129+
130+
expect(adapter.write).toHaveBeenCalledOnce();
131+
expect(adapter.read).toHaveBeenCalledOnce();
132+
expect(receivedError).not.toBeNull();
133+
expect(receivedError?.message).toEqual("Get status timeout");
134+
})
135+
136+
it('getStatuses throw error when receive empty buffer from adapter', async () => {
137+
returnEmptyDataOnOfflineCauseStatus = true;
138+
let receivedError: Error | null = null;
139+
140+
const printer = new Printer(adapter, {})
141+
try {
142+
await printer.getStatuses();
143+
} catch (err) {
144+
if (err instanceof Error) {
145+
receivedError = err;
146+
}
147+
}
148+
149+
expect(adapter.write).toHaveBeenCalledTimes(3);
150+
expect(adapter.read).toHaveBeenCalledTimes(3);
151+
expect(receivedError).not.toBeNull();
152+
expect(receivedError?.message).toEqual("Get status timeout");
153+
})
154+
});

packages/network-adapter/src/index.ts

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,22 @@ import { Adapter } from "@node-escpos/adapter";
88
export default class Network extends Adapter<[device: net.Socket]> {
99
private readonly address: string;
1010
private readonly port: number;
11-
private readonly timeout: number;
11+
private readonly connectTimeout: number;
12+
private readonly readTimeout: number;
1213
private readonly device: net.Socket;
1314

1415
/**
1516
* @param {[type]} address
1617
* @param {[type]} port
18+
* @param {[type]} connectTimeout
19+
* @param {[type]} readTimeout
1720
*/
18-
constructor(address: string, port = 9100, timeout = 30000) {
21+
constructor(address: string, port = 9100, connectTimeout = 30000, readTimeout = 1000) {
1922
super();
2023
this.address = address;
2124
this.port = port;
22-
this.timeout = timeout;
25+
this.connectTimeout = connectTimeout;
26+
this.readTimeout = readTimeout;
2327
this.device = new net.Socket();
2428
}
2529

@@ -33,20 +37,20 @@ export default class Network extends Adapter<[device: net.Socket]> {
3337
const connection_timeout = setTimeout(() => {
3438
this.device.destroy();
3539
callback && callback(
36-
new Error(`printer connection timeout after ${this.timeout}ms`), this.device,
40+
new Error(`printer connection timeout after ${this.connectTimeout}ms`), this.device,
3741
);
38-
}, this.timeout);
42+
}, this.connectTimeout);
3943

4044
// connect to net printer by socket (port, ip)
4145
this.device.on("error", (err) => {
4246
callback && callback(err, this.device);
4347
}).on("data", (buf) => {
4448
// eslint-disable-next-line no-console
4549
console.log("printer say:", buf);
46-
}).connect(this.port, this.address, (err?: Error | null) => {
47-
clearInterval(connection_timeout);
50+
}).connect(this.port, this.address, () => {
51+
clearTimeout(connection_timeout);
4852
this.emit("connect", this.device);
49-
callback && callback(err ?? null, this.device);
53+
callback && callback(null, this.device);
5054
});
5155
return this;
5256
}
@@ -67,9 +71,21 @@ export default class Network extends Adapter<[device: net.Socket]> {
6771
}
6872

6973
read(callback?: (data: Buffer) => void) {
70-
this.device.on("data", (buf) => {
74+
let timeoutId:NodeJS.Timeout|undefined = undefined;
75+
76+
// listener to pass to socket.once and socket.off
77+
const eventListener = (buf: Buffer) => {
78+
if(timeoutId !== undefined) clearTimeout(timeoutId);
7179
if (callback) callback(buf);
72-
});
80+
}
81+
82+
// pass empty buffer to callback function when timeout
83+
timeoutId = setTimeout(() => {
84+
this.device.off("data", eventListener);
85+
if (callback) callback(Buffer.from(""));
86+
}, this.readTimeout);
87+
88+
this.device.once("data", eventListener);
7389
return this;
7490
}
7591

0 commit comments

Comments
 (0)