Skip to content

Commit 79b0afc

Browse files
authored
Add device connected state (#7)
I suspect systems would like to know if the printer they're going to talk to is still there or not. This adds a `connected` getter to both the ReceiptPrinter object and the DeviceChannel object, which is what gets rolled into the printer status.
1 parent 9d05506 commit 79b0afc

4 files changed

Lines changed: 78 additions & 65 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "web-receiptline-printer",
3-
"version": "0.1.0-alpha.2",
3+
"version": "0.1.0-rc1",
44
"description": "A small Library for printing ReceiptLine documents to various receipt printers from a browser.",
55
"type": "module",
66
"repository": {

src/Printers/Communication/DeviceCommunication.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ export interface IDeviceChannel<TOutput, TInput> {
4848
/** A promise indicating this communication channel is ready for use. */
4949
get ready(): Promise<boolean>;
5050

51+
/** Whether the device is connected. */
52+
get connected(): boolean;
53+
5154
/** Close the channel, disallowing future communication. */
5255
dispose(): Promise<void>;
5356

src/Printers/Communication/UsbDeviceChannel.ts

Lines changed: 68 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ export class UsbDeviceChannel implements IDeviceChannel<Uint8Array, Uint8Array>
5050
private _readyFlag = false;
5151
private _readyPromise: Promise<boolean>;
5252
public get ready() { return this._readyPromise; }
53+
public get connected() {
54+
return !this._disposed
55+
&& this._readyFlag
56+
&& this.device.opened
57+
}
5358

5459
private _disposed = false;
5560

@@ -74,67 +79,6 @@ export class UsbDeviceChannel implements IDeviceChannel<Uint8Array, Uint8Array>
7479
return true;
7580
}
7681

77-
public async dispose() {
78-
if (this._disposed) {
79-
return;
80-
}
81-
82-
this._disposed = true;
83-
this._readyPromise = Promise.resolve(false);
84-
85-
try {
86-
await this.device.close();
87-
} catch (e) {
88-
if (
89-
e instanceof DOMException &&
90-
e.name === 'NotFoundError' &&
91-
e.message ===
92-
"Failed to execute 'close' on 'USBDevice': The device was disconnected."
93-
) {
94-
// Device was already closed, no-op.
95-
return;
96-
}
97-
98-
throw e;
99-
}
100-
}
101-
102-
public async sendCommands(
103-
commandBuffer: Uint8Array
104-
): Promise<DeviceCommunicationError | undefined> {
105-
if (!this._readyFlag || this._disposed || this.deviceOut === undefined) {
106-
throw new DeviceNotReadyError();
107-
}
108-
if (this._commOptions.debug) {
109-
console.debug('Sending command buffer to device via USB.');
110-
console.time('commandBufferSendTime');
111-
}
112-
113-
try {
114-
// TOOD: Add timeout in case of communication hang.
115-
await this.device.transferOut(this.deviceOut.endpointNumber, commandBuffer);
116-
return;
117-
} catch (e: unknown) {
118-
if (typeof e === 'string') {
119-
return new DeviceCommunicationError(e);
120-
}
121-
if (e instanceof Error) {
122-
return new DeviceCommunicationError(undefined, e);
123-
}
124-
// Dunno what this is but we can't wrap it.
125-
throw e;
126-
} finally {
127-
if (this._commOptions.debug) {
128-
console.timeEnd('commandBufferSendTime');
129-
console.debug('Completed sending commands.');
130-
}
131-
}
132-
}
133-
134-
public getDeviceInfo() {
135-
return deviceToInfo(this.device);
136-
}
137-
13882
private async connect() {
13983
const d = this.device;
14084

@@ -203,8 +147,69 @@ export class UsbDeviceChannel implements IDeviceChannel<Uint8Array, Uint8Array>
203147
}
204148
}
205149

206-
public async getInput(): Promise<Uint8Array[] | DeviceCommunicationError> {
207-
if (this.deviceIn === undefined || this._disposed) { return new DeviceCommunicationError('Channel is disposed.'); }
150+
public async dispose() {
151+
if (this._disposed) {
152+
return;
153+
}
154+
155+
this._disposed = true;
156+
this._readyPromise = Promise.resolve(false);
157+
158+
try {
159+
await this.device.close();
160+
} catch (e) {
161+
if (
162+
e instanceof DOMException &&
163+
e.name === 'NotFoundError' &&
164+
e.message ===
165+
"Failed to execute 'close' on 'USBDevice': The device was disconnected."
166+
) {
167+
// Device was already closed, no-op.
168+
return;
169+
}
170+
171+
throw e;
172+
}
173+
}
174+
175+
public async sendCommands(
176+
commandBuffer: Uint8Array
177+
): Promise<DeviceNotReadyError | undefined> {
178+
if (this.deviceOut === undefined || !this.connected) {
179+
return new DeviceNotReadyError();
180+
}
181+
if (this._commOptions.debug) {
182+
console.debug('Sending command buffer to device via USB.');
183+
console.time('commandBufferSendTime');
184+
}
185+
186+
try {
187+
// TOOD: Add timeout in case of communication hang.
188+
await this.device.transferOut(this.deviceOut.endpointNumber, commandBuffer);
189+
return;
190+
} catch (e: unknown) {
191+
if (typeof e === 'string') {
192+
return new DeviceCommunicationError(e);
193+
}
194+
if (e instanceof Error) {
195+
return new DeviceCommunicationError(undefined, e);
196+
}
197+
// Dunno what this is but we can't wrap it.
198+
throw e;
199+
} finally {
200+
if (this._commOptions.debug) {
201+
console.timeEnd('commandBufferSendTime');
202+
console.debug('Completed sending commands.');
203+
}
204+
}
205+
}
206+
207+
public getDeviceInfo() {
208+
return deviceToInfo(this.device);
209+
}
210+
211+
public async getInput(): Promise<Uint8Array[] | DeviceNotReadyError> {
212+
if (this.deviceIn === undefined || !this.connected) { return new DeviceNotReadyError('Channel is not connected.'); }
208213
const result = await this.device.transferIn(
209214
this.deviceIn.endpointNumber,
210215
this.deviceIn.packetSize * 8); // Usually 64 * 8 = 512

src/Printers/ReceiptPrinter.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ export class ReceiptPrinter extends EventTarget implements IDevice {
6464
get ready() {
6565
return this._ready;
6666
}
67+
get connected() {
68+
return !this._disposed
69+
&& this._channel.connected
70+
}
6771

6872
/** Construct a new printer from a given USB device. */
6973
static fromUSBDevice(device: USBDevice, options: IDeviceCommunicationOptions): ReceiptPrinter {
@@ -115,7 +119,7 @@ export class ReceiptPrinter extends EventTarget implements IDevice {
115119
return false;
116120
}
117121

118-
this._printerOptions.update(deviceInfoToOptionsUpdate(this._channel.getDeviceInfo()))
122+
this._printerOptions.update(deviceInfoToOptionsUpdate(this._channel.getDeviceInfo()));
119123

120124
// TODO: language detection
121125
this._commandSet = new EscPos();
@@ -132,6 +136,7 @@ export class ReceiptPrinter extends EventTarget implements IDevice {
132136
/** Close the connection to this printer, preventing future communication. */
133137
public async dispose() {
134138
this._disposed = true;
139+
this._ready = Promise.resolve(false);
135140
this._streamListener?.dispose();
136141
await this._channel.dispose();
137142
}

0 commit comments

Comments
 (0)