Skip to content

Fix issues in network adapter and core getStatus(), getStatuses() #90

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/ninety-needles-hug.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@node-escpos/core": minor
"@node-escpos/network-adapter": minor
---

Improvement for core and network adapter
78 changes: 51 additions & 27 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -905,15 +905,26 @@ export class Printer<AdapterCloseArgs extends []> extends EventEmitter {
* @return {Promise} promise returning given status
*/
getStatus<T extends DeviceStatus>(StatusClass: StatusClassConstructor<T>): Promise<T> {
return new Promise((resolve) => {
this.adapter.read((data) => {
const byte = data.readInt8(0);
resolve(new StatusClass(byte));
return new Promise<T>((resolve, reject) => {
this.adapter.read((data: Buffer) => {
try {
if(data.length === 0) {
return reject(new Error("Get status timeout"));
}
const byte = data.readInt8(0);
resolve(new StatusClass(byte));
} catch (err) {
if (typeof err === "string") {
console.error(err);
reject(new Error(err));
} else {
console.error(err);
reject(err);
}
}
});

StatusClass.commands().forEach((c) => {
this.buffer.write(c);
});
this.adapter.write(StatusClass.commands().join(''));
});
}

Expand All @@ -922,26 +933,25 @@ export class Printer<AdapterCloseArgs extends []> extends EventEmitter {
* @return {Promise}
*/
getStatuses(): Promise<DeviceStatus[]> {
return new Promise((resolve, reject) => {
this.adapter.read((data) => {
const buffer: number[] = [];
for (let i = 0; i < data.byteLength; i++) buffer.push(data.readInt8(i));
if (buffer.length < 4) return reject();

const statuses = [
new PrinterStatus(buffer[0]),
new RollPaperSensorStatus(buffer[1]),
new OfflineCauseStatus(buffer[2]),
new ErrorCauseStatus(buffer[3]),
];
resolve(statuses);
});

[PrinterStatus, RollPaperSensorStatus, OfflineCauseStatus, ErrorCauseStatus].forEach((statusClass) => {
statusClass.commands().forEach((command) => {
this.adapter.write(command);
});
});
return new Promise<DeviceStatus[]> (async (resolve, reject) => {
const results:DeviceStatus[] = [];

try {
results.push(await this.getStatus(PrinterStatus));
results.push(await this.getStatus(RollPaperSensorStatus));
results.push(await this.getStatus(OfflineCauseStatus));
results.push(await this.getStatus(ErrorCauseStatus));

resolve(results);
} catch (err) {
if (typeof err === "string") {
console.error(err);
reject(new Error(err));
} else {
console.error(err);
reject(err);
}
}
});
}

Expand Down Expand Up @@ -1091,3 +1101,17 @@ export class Printer<AdapterCloseArgs extends []> extends EventEmitter {
export default Printer;
export { default as Image } from "./image";
export const command = _;
export {
ErrorCauseStatus,
OfflineCauseStatus,
PrinterStatus,
RollPaperSensorStatus,
} from "./statuses";
export type {
DeviceStatus,
StatusClassConstructor,
StatusJSON,
StatusJSONElement,
StatusJSONElementSingle,
StatusJSONElementMultiple,
} from "./statuses";
8 changes: 4 additions & 4 deletions packages/core/src/statuses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,23 @@ enum Status {
Error = "error",
}

interface StatusJSONElementSingle {
export interface StatusJSONElementSingle {
bit: number
value: 0 | 1
label: string
status: Status
}

interface StatusJSONElementMultiple {
export interface StatusJSONElementMultiple {
bit: string
value: string
label: string
status: Status
}

type StatusJSONElement = StatusJSONElementSingle | StatusJSONElementMultiple;
export type StatusJSONElement = StatusJSONElementSingle | StatusJSONElementMultiple;

interface StatusJSON<T extends string> {
export interface StatusJSON<T extends string> {
className: T
byte: number
bits: string
Expand Down
5 changes: 3 additions & 2 deletions packages/core/test/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { describe, expect, it } from 'vitest'
import { splitForCode128, genCode128forXprinter } from '../src/utils';
import { describe, expect, it, vi } from 'vitest'
import { splitForCode128, genCode128forXprinter } from '../src/utils';

describe('should', () => {

it('exported', () => {
expect(1).toEqual(1)
});
Expand Down
154 changes: 154 additions & 0 deletions packages/core/test/status.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { Adapter } from '@node-escpos/adapter';
import { ErrorCauseStatus, OfflineCauseStatus, Printer, PrinterStatus, RollPaperSensorStatus } from '..//src';

class MockAdapter extends Adapter<[]> {
open = vi.fn()
write = vi.fn()
close = vi.fn()
read = vi.fn()
}

describe('should', () => {
const adapter = new MockAdapter();
const dataWrote = vi.fn();

// flag to return empty data on getting OnOfflineCauseStatus
let returnEmptyDataOnOfflineCauseStatus = false;

beforeEach(() => {
let readResolver: (value: string|PromiseLike<string>) => void;
let readRejecter: (reason?: any) => void;
returnEmptyDataOnOfflineCauseStatus = false;

adapter.read.mockImplementation(async (callback?: (data: Buffer) => void) => {
// promise to wait for data writing
const promise = new Promise<string>((resolve, reject) => {
// save resolve and reject
readResolver = resolve;
readRejecter = reject;
});
const result = await promise;
if (callback) callback(Buffer.from(result));
});

adapter.write.mockImplementation((data: string | Buffer, callback?: (error: Error | null) => void) => {
const normalizedData = data.toString();
dataWrote(normalizedData)

// return different data depending on the command received
switch(normalizedData) {
case PrinterStatus.commands().join(''):
readResolver('\x16');
break;
case RollPaperSensorStatus.commands().join(''):
readResolver('\x17');
break;
case OfflineCauseStatus.commands().join(''):
if (returnEmptyDataOnOfflineCauseStatus) {
readResolver("");
} else {
readResolver('\x18');
}
break;
case ErrorCauseStatus.commands().join(''):
readResolver('\x19');
break;
default:
readRejecter(new Error("Unknown data wrote"));
break;
}
});
});

afterEach(() => {
vi.clearAllMocks();
});

it('adapter receive correct data from getStatus', async () => {
const printer = new Printer(adapter, {})
await printer.getStatus(PrinterStatus);

expect(adapter.write).toHaveBeenCalledOnce();

expect(dataWrote).toBeCalledWith(PrinterStatus.commands().join(''));
})

it('data return from adapter can create correct status with correct byte', async () => {
const printer = new Printer(adapter, {})
const printerStatus = await printer.getStatus(PrinterStatus);

expect(adapter.read).toHaveBeenCalledOnce();

expect(printerStatus.byte).toEqual(22);
})

it('getStatuses return all statues with correct byte', async () => {
const printer = new Printer(adapter, {})
const printerStatuses = await printer.getStatuses();

expect(adapter.write).toHaveBeenCalledTimes(4);
expect(adapter.read).toHaveBeenCalledTimes(4);
expect(printerStatuses.length).toEqual(4);

printerStatuses
.map((status) => status.toJSON())
.forEach((json) => {
switch(json.className) {
case PrinterStatus.name:
expect(json.byte).toEqual(22);
break;
case RollPaperSensorStatus.name:
expect(json.byte).toEqual(23);
break;
case OfflineCauseStatus.name:
expect(json.byte).toEqual(24);
break;
case ErrorCauseStatus.name:
expect(json.byte).toEqual(25);
break;
default:
expect(false, "unexpected DeviceStatus class:" + json.className).toBeTruthy();
break;
}
})
})

it('getStatus throw error when receive empty buffer from adapter', async () => {
returnEmptyDataOnOfflineCauseStatus = true;
let receivedError: Error | null = null;

const printer = new Printer(adapter, {})
try {
await printer.getStatus(OfflineCauseStatus);
} catch (err) {
if (err instanceof Error) {
receivedError = err;
}
}

expect(adapter.write).toHaveBeenCalledOnce();
expect(adapter.read).toHaveBeenCalledOnce();
expect(receivedError).not.toBeNull();
expect(receivedError?.message).toEqual("Get status timeout");
})

it('getStatuses throw error when receive empty buffer from adapter', async () => {
returnEmptyDataOnOfflineCauseStatus = true;
let receivedError: Error | null = null;

const printer = new Printer(adapter, {})
try {
await printer.getStatuses();
} catch (err) {
if (err instanceof Error) {
receivedError = err;
}
}

expect(adapter.write).toHaveBeenCalledTimes(3);
expect(adapter.read).toHaveBeenCalledTimes(3);
expect(receivedError).not.toBeNull();
expect(receivedError?.message).toEqual("Get status timeout");
})
});
36 changes: 26 additions & 10 deletions packages/network-adapter/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,22 @@ import { Adapter } from "@node-escpos/adapter";
export default class Network extends Adapter<[device: net.Socket]> {
private readonly address: string;
private readonly port: number;
private readonly timeout: number;
private readonly connectTimeout: number;
private readonly readTimeout: number;
private readonly device: net.Socket;

/**
* @param {[type]} address
* @param {[type]} port
* @param {[type]} connectTimeout
* @param {[type]} readTimeout
*/
constructor(address: string, port = 9100, timeout = 30000) {
constructor(address: string, port = 9100, connectTimeout = 30000, readTimeout = 1000) {
super();
this.address = address;
this.port = port;
this.timeout = timeout;
this.connectTimeout = connectTimeout;
this.readTimeout = readTimeout;
this.device = new net.Socket();
}

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

// connect to net printer by socket (port, ip)
this.device.on("error", (err) => {
callback && callback(err, this.device);
}).on("data", (buf) => {
// eslint-disable-next-line no-console
console.log("printer say:", buf);
}).connect(this.port, this.address, (err?: Error | null) => {
clearInterval(connection_timeout);
}).connect(this.port, this.address, () => {
clearTimeout(connection_timeout);
this.emit("connect", this.device);
callback && callback(err ?? null, this.device);
callback && callback(null, this.device);
});
return this;
}
Expand All @@ -67,9 +71,21 @@ export default class Network extends Adapter<[device: net.Socket]> {
}

read(callback?: (data: Buffer) => void) {
this.device.on("data", (buf) => {
let timeoutId:NodeJS.Timeout|undefined = undefined;

// listener to pass to socket.once and socket.off
const eventListener = (buf: Buffer) => {
if(timeoutId !== undefined) clearTimeout(timeoutId);
if (callback) callback(buf);
});
}

// pass empty buffer to callback function when timeout
timeoutId = setTimeout(() => {
this.device.off("data", eventListener);
if (callback) callback(Buffer.from(""));
}, this.readTimeout);

this.device.once("data", eventListener);
return this;
}

Expand Down
Loading