Print tasks rework

This commit is contained in:
MultiMote 2024-10-19 23:06:00 +03:00
parent 46c391f626
commit b5263c027d
13 changed files with 237 additions and 184 deletions

@ -19,7 +19,7 @@ Yarn:
yarn add @mmote/niimbluelib --exact yarn add @mmote/niimbluelib --exact
``` ```
### Usage example ### Usage example (may be outdated)
```js ```js
import { Utils, RequestCommandId, ResponseCommandId, NiimbotBluetoothClient, ImageEncoder, PrintTaskVersion } from "@mmote/niimbluelib"; import { Utils, RequestCommandId, ResponseCommandId, NiimbotBluetoothClient, ImageEncoder, PrintTaskVersion } from "@mmote/niimbluelib";
@ -75,14 +75,20 @@ ctx.stroke();
// draw border // draw border
ctx.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1); ctx.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
const image = ImageEncoder.encodeCanvas(canvas, props.printDirection); const encoded = ImageEncoder.encodeCanvas(canvas, props.printDirection);
const taskVersion = client.getPrintTaskVersion() ?? PrintTaskVersion.V3; const printTaskName = client.getPrintTaskType() ?? "D110";
await client.abstraction.print(taskVersion, image, { quantity }); const printTask = client.abstraction.newPrintTask(printTaskName, {
totalPages: quantity,
statusPollIntervalMs: 100,
statusTimeoutMs: 8_000
})
try { try {
await client.abstraction.waitUntilPrintFinished(taskVersion, quantity); await printTask.printInit();
await printTask.printPage(encoded, quantity);
await printTask.waitForFinished();
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }

@ -9,7 +9,7 @@ import {
} from "../packets"; } from "../packets";
import { PrinterModelMeta, getPrinterMetaById } from "../printer_models"; import { PrinterModelMeta, getPrinterMetaById } from "../printer_models";
import { ClientEventMap, PacketSentEvent, PrinterInfoFetchedEvent, HeartbeatEvent, HeartbeatFailedEvent } from "./events"; import { ClientEventMap, PacketSentEvent, PrinterInfoFetchedEvent, HeartbeatEvent, HeartbeatFailedEvent } from "./events";
import { getPrintTaskVersion, PrintTaskVersion } from "../print_task_versions"; import { findPrintTask, PrintTaskName } from "../print_tasks";
export type ConnectionInfo = { export type ConnectionInfo = {
deviceName?: string; deviceName?: string;
@ -159,14 +159,14 @@ export abstract class NiimbotAbstractClient extends TypedEventTarget<ClientEvent
} }
/** Determine print task version if any */ /** Determine print task version if any */
public getPrintTaskVersion(): PrintTaskVersion | undefined { public getPrintTaskType(): PrintTaskName | undefined {
const meta = this.getModelMetadata(); const meta = this.getModelMetadata();
if (meta === undefined) { if (meta === undefined) {
return undefined; return undefined;
} }
return getPrintTaskVersion(meta.model); return findPrintTask(meta.model);
} }
public setPacketInterval(milliseconds: number) { public setPacketInterval(milliseconds: number) {

@ -3,4 +3,4 @@ export * from "./packets";
export * from "./image_encoder"; export * from "./image_encoder";
export * from "./utils"; export * from "./utils";
export * from "./printer_models"; export * from "./printer_models";
export * from "./print_task_versions"; export * from "./print_tasks";

@ -11,13 +11,13 @@ import {
SoundSettingsType, SoundSettingsType,
} from "."; } from ".";
import { NiimbotAbstractClient, PacketReceivedEvent, PrintProgressEvent } from "../client"; import { NiimbotAbstractClient, PacketReceivedEvent, PrintProgressEvent } from "../client";
import { EncodedImage } from "../image_encoder"; import { PrintTaskName, printTasks } from "../print_tasks";
import { PrintTaskVersion } from "../print_task_versions"; import { AbstractPrintTask, PrintOptions } from "../print_tasks/AbstractPrintTask";
import { PrinterModel } from "../printer_models"; import { PrinterModel } from "../printer_models";
import { Validators, Utils } from "../utils"; import { Validators, Utils } from "../utils";
import { SequentialDataReader } from "./data_reader"; import { SequentialDataReader } from "./data_reader";
import { NiimbotPacket } from "./packet"; import { NiimbotPacket } from "./packet";
import { PacketGenerator, PrintOptions } from "./packet_generator"; import { PacketGenerator } from "./packet_generator";
export class PrintError extends Error { export class PrintError extends Error {
public readonly reasonId: number; public readonly reasonId: number;
@ -92,10 +92,16 @@ export class Abstraction {
} }
/** Send packet and wait for response */ /** Send packet and wait for response */
private async send(packet: NiimbotPacket, forceTimeout?: number): Promise<NiimbotPacket> { public async send(packet: NiimbotPacket, forceTimeout?: number): Promise<NiimbotPacket> {
return this.client.sendPacketWaitResponse(packet, forceTimeout ?? this.timeout); return this.client.sendPacketWaitResponse(packet, forceTimeout ?? this.timeout);
} }
public async sendAll(packets: NiimbotPacket[], forceTimeout?: number): Promise<void> {
for (const p of packets) {
await this.send(p, forceTimeout);
}
}
public async getPrintStatus(): Promise<PrintStatus> { public async getPrintStatus(): Promise<PrintStatus> {
const packet = await this.send(PacketGenerator.printStatus()); const packet = await this.send(PacketGenerator.printStatus());
@ -342,32 +348,6 @@ export class Abstraction {
await this.send(PacketGenerator.printerReset()); await this.send(PacketGenerator.printerReset());
} }
/**
*
* Call client.stopHeartbeat before print is started!
*
* @param taskVersion
* @param image
* @param options
* @param timeout
*/
public async print(
taskVersion: PrintTaskVersion,
image: EncodedImage,
options?: PrintOptions,
timeout?: number
): Promise<void> {
this.setTimeout(timeout ?? this.DEFAULT_PRINT_TIMEOUT);
const packets: NiimbotPacket[] = PacketGenerator.generatePrintSequence(taskVersion, image, options);
try {
for (const element of packets) {
await this.send(element);
}
} finally {
this.setDefaultTimeout();
}
}
public async waitUntilPrintFinishedV1(pagesToPrint: number, timeoutMs: number = 5_000): Promise<void> { public async waitUntilPrintFinishedV1(pagesToPrint: number, timeoutMs: number = 5_000): Promise<void> {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
const listener = (evt: PacketReceivedEvent) => { const listener = (evt: PacketReceivedEvent) => {
@ -381,7 +361,7 @@ export class Abstraction {
this.statusTimeoutTimer = setTimeout(() => { this.statusTimeoutTimer = setTimeout(() => {
this.client.removeEventListener("packetreceived", listener); this.client.removeEventListener("packetreceived", listener);
reject(new Error("Timeout waiting print status")); reject(new Error("Timeout waiting print status"));
}, timeoutMs); }, timeoutMs ?? 5_000);
if (page === pagesToPrint) { if (page === pagesToPrint) {
clearTimeout(this.statusTimeoutTimer); clearTimeout(this.statusTimeoutTimer);
@ -431,28 +411,15 @@ export class Abstraction {
clearInterval(this.statusPollTimer); clearInterval(this.statusPollTimer);
reject(e as Error); reject(e as Error);
}); });
}, pollIntervalMs); }, pollIntervalMs ?? 300);
}); });
} }
/**
* printprogress event is firing during this process.
*
* @param pagesToPrint Total pages to print.
*/
public async waitUntilPrintFinished(
taskVersion: PrintTaskVersion,
pagesToPrint: number,
options?: { pollIntervalMs?: number; timeoutMs?: number }
): Promise<void> {
if (taskVersion === PrintTaskVersion.V1) {
return this.waitUntilPrintFinishedV1(pagesToPrint, options?.timeoutMs);
}
return this.waitUntilPrintFinishedV2(pagesToPrint, options?.pollIntervalMs);
}
public async printEnd(): Promise<void> { public async printEnd(): Promise<void> {
await this.send(PacketGenerator.printEnd()); await this.send(PacketGenerator.printEnd());
} }
public newPrintTask(name: PrintTaskName, options?: PrintOptions): AbstractPrintTask {
return new printTasks[name](this, options);
}
} }

@ -1,7 +1,6 @@
import { import {
AutoShutdownTime, AutoShutdownTime,
HeartbeatType, HeartbeatType,
LabelType,
NiimbotPacket, NiimbotPacket,
PrinterInfoType, PrinterInfoType,
RequestCommandId, RequestCommandId,
@ -10,15 +9,8 @@ import {
SoundSettingsType, SoundSettingsType,
} from "."; } from ".";
import { EncodedImage, ImageEncoder, ImageRow } from "../image_encoder"; import { EncodedImage, ImageEncoder, ImageRow } from "../image_encoder";
import { PrintTaskVersion } from "../print_task_versions";
import { Utils } from "../utils"; import { Utils } from "../utils";
export type PrintOptions = {
labelType?: LabelType;
density?: number;
quantity?: number;
};
export class PacketGenerator { export class PacketGenerator {
public static generic( public static generic(
requestId: RequestCommandId, requestId: RequestCommandId,
@ -294,88 +286,4 @@ export class PacketGenerator {
} }
}); });
} }
public static generatePrintPageSequence(
taskVersion: PrintTaskVersion,
image: EncodedImage,
options?: PrintOptions
): NiimbotPacket[] {
const packets: NiimbotPacket[] = [];
switch (taskVersion) {
case PrintTaskVersion.V1:
packets.push(this.printClear());
packets.push(this.pageStart());
packets.push(this.setPageSizeV1(image.rows));
packets.push(this.setPrintQuantity(options?.quantity ?? 1));
break;
case PrintTaskVersion.V2:
packets.push(this.printClear());
packets.push(this.pageStart());
packets.push(this.setPageSizeV2(image.rows, image.cols));
packets.push(this.setPrintQuantity(options?.quantity ?? 1));
break;
case PrintTaskVersion.V3:
packets.push(this.pageStart());
packets.push(this.setPageSizeV2(image.rows, image.cols));
packets.push(this.setPrintQuantity(options?.quantity ?? 1));
break;
case PrintTaskVersion.V4:
packets.push(this.pageStart());
packets.push(this.setPageSizeV3(image.rows, image.cols, options?.quantity ?? 1));
break;
case PrintTaskVersion.V5:
packets.push(this.pageStart());
packets.push(this.setPageSizeV4(image.rows, image.cols, options?.quantity ?? 1, 0, false));
break;
default:
taskVersion satisfies never;
}
packets.push(...this.writeImageData(image));
packets.push(this.pageEnd());
return packets;
}
public static generatePrintInitSequence(taskVersion: PrintTaskVersion, options?: PrintOptions): NiimbotPacket[] {
const packets: NiimbotPacket[] = [];
packets.push(this.setDensity(options?.density ?? 2));
packets.push(this.setLabelType(options?.labelType ?? LabelType.WithGaps));
switch (taskVersion) {
case PrintTaskVersion.V1:
case PrintTaskVersion.V2:
case PrintTaskVersion.V3:
packets.push(this.printStart());
break;
case PrintTaskVersion.V4:
packets.push(this.printStartV4(options?.quantity ?? 1));
break;
case PrintTaskVersion.V5:
packets.push(this.printStartV5(options?.quantity ?? 1, 0, 0));
break;
default:
taskVersion satisfies never;
}
return packets;
}
/**
* Generate print sequence for one page (with one or multiple copies).
*
* You should send PrintEnd manually after this sequence after print is finished
*/
public static generatePrintSequence(
taskVersion: PrintTaskVersion,
image: EncodedImage,
options?: PrintOptions
): NiimbotPacket[] {
return [
...this.generatePrintInitSequence(taskVersion, options),
...this.generatePrintPageSequence(taskVersion, image, options),
];
}
} }

@ -1,33 +0,0 @@
import { PrinterModel as M } from "./printer_models";
export enum PrintTaskVersion {
V1 = 1,
V2,
V3,
V4,
V5,
}
export const getPrintTaskVersion = (model: M): PrintTaskVersion | undefined => {
switch (model) {
case M.D11:
case M.D11S:
case M.B21_L2B:
case M.B21:
case M.B21_PRO:
case M.B21S:
case M.B21S_C2B:
case M.B21_C2B:
return PrintTaskVersion.V1;
case M.D110:
return PrintTaskVersion.V3;
case M.D11_H:
case M.D110_M:
case M.B1:
return PrintTaskVersion.V4;
}
return undefined;
};

@ -0,0 +1,51 @@
import { EncodedImage } from "../image_encoder";
import { LabelType } from "../packets";
import { Abstraction } from "../packets/abstraction";
export type PrintOptions = {
/** Printer label type */
labelType?: LabelType;
/** Print density */
density?: number;
/** How many pages will be printed */
totalPages?: number;
/** Used in {@link waitForFinished} where status is received by polling */
statusPollIntervalMs?: number;
/** Used in {@link waitForFinished} where status is received by waiting */
statusTimeoutMs?: number;
};
const printOptionsDefaults: PrintOptions = {
totalPages: 1,
statusPollIntervalMs: 300,
statusTimeoutMs: 5_000,
};
export abstract class AbstractPrintTask {
protected abstraction: Abstraction;
protected printOptions: PrintOptions;
protected pagesPrinted: number;
constructor(abstraction: Abstraction, printOptions?: PrintOptions) {
this.abstraction = abstraction;
this.pagesPrinted = 0;
this.printOptions = {
...printOptionsDefaults,
...printOptions,
};
}
protected checkAddPage(quantity: number) {
if (this.pagesPrinted + quantity > (this.printOptions.totalPages ?? 1)) {
throw new Error("Trying to print too many pages (task totalPages may not be set correctly)");
}
}
/** Prepare print (set label type, density, print start, ...) */
abstract printInit(): Promise<void>;
/** Print image with a specified number of copies */
abstract printPage(image: EncodedImage, quantity?: number): Promise<void>;
/** Wait for print is finished */
abstract waitForFinished(): Promise<void>;
}

@ -0,0 +1,31 @@
import { EncodedImage } from "..";
import { LabelType, PacketGenerator } from "../packets";
import { AbstractPrintTask } from "./AbstractPrintTask";
export class B1PrintTask extends AbstractPrintTask {
override printInit(): Promise<void> {
return this.abstraction.sendAll([
PacketGenerator.setDensity(this.printOptions.density ?? 3),
PacketGenerator.setLabelType(this.printOptions.labelType ?? LabelType.WithGaps),
PacketGenerator.printStartV4(this.printOptions.totalPages ?? 1),
]);
}
override printPage(image: EncodedImage, quantity?: number): Promise<void> {
this.checkAddPage(quantity ?? 1);
return this.abstraction.sendAll([
PacketGenerator.pageStart(),
PacketGenerator.setPageSizeV3(image.rows, image.cols, quantity ?? 1),
...PacketGenerator.writeImageData(image),
PacketGenerator.pageEnd(),
]);
}
override waitForFinished(): Promise<void> {
return this.abstraction.waitUntilPrintFinishedV2(
this.printOptions.totalPages ?? 1,
this.printOptions.statusPollIntervalMs
);
}
}

@ -0,0 +1,32 @@
import { EncodedImage } from "../image_encoder";
import { LabelType, PacketGenerator } from "../packets";
import { AbstractPrintTask } from "./AbstractPrintTask";
export class D110PrintTask extends AbstractPrintTask {
override printInit(): Promise<void> {
return this.abstraction.sendAll([
PacketGenerator.setDensity(this.printOptions.density ?? 2),
PacketGenerator.setLabelType(this.printOptions.labelType ?? LabelType.WithGaps),
PacketGenerator.printStart(),
]);
}
override printPage(image: EncodedImage, quantity?: number): Promise<void> {
this.checkAddPage(quantity ?? 1);
return this.abstraction.sendAll([
PacketGenerator.pageStart(),
PacketGenerator.setPageSizeV2(image.rows, image.cols),
PacketGenerator.setPrintQuantity(quantity ?? 1),
...PacketGenerator.writeImageData(image),
PacketGenerator.pageEnd(),
]);
}
override waitForFinished(): Promise<void> {
return this.abstraction.waitUntilPrintFinishedV2(
this.printOptions.totalPages ?? 1,
this.printOptions.statusPollIntervalMs
);
}
}

@ -0,0 +1,33 @@
import { EncodedImage } from "../image_encoder";
import { LabelType, PacketGenerator } from "../packets";
import { AbstractPrintTask } from "./AbstractPrintTask";
export class OldD11PrintTask extends AbstractPrintTask {
override printInit(): Promise<void> {
return this.abstraction.sendAll([
PacketGenerator.setDensity(this.printOptions.density ?? 2),
PacketGenerator.setLabelType(this.printOptions.labelType ?? LabelType.WithGaps),
PacketGenerator.printStart(),
]);
}
override printPage(image: EncodedImage, quantity: number): Promise<void> {
this.checkAddPage(quantity ?? 1);
return this.abstraction.sendAll([
PacketGenerator.printClear(),
PacketGenerator.pageStart(),
PacketGenerator.setPageSizeV1(image.rows),
PacketGenerator.setPrintQuantity(quantity ?? 1),
...PacketGenerator.writeImageData(image),
PacketGenerator.pageEnd(),
]);
}
override waitForFinished(): Promise<void> {
return this.abstraction.waitUntilPrintFinishedV1(
this.printOptions.totalPages ?? 1,
this.printOptions.statusTimeoutMs
);
}
}

@ -0,0 +1,31 @@
import { EncodedImage } from "..";
import { LabelType, PacketGenerator } from "../packets";
import { AbstractPrintTask } from "./AbstractPrintTask";
export class V5PrintTask extends AbstractPrintTask {
override printInit(): Promise<void> {
return this.abstraction.sendAll([
PacketGenerator.setDensity(this.printOptions.density ?? 3),
PacketGenerator.setLabelType(this.printOptions.labelType ?? LabelType.WithGaps),
PacketGenerator.printStartV5(this.printOptions.totalPages ?? 1, 0, 0)
]);
}
override printPage(image: EncodedImage, quantity?: number): Promise<void> {
this.checkAddPage(quantity ?? 1);
return this.abstraction.sendAll([
PacketGenerator.pageStart(),
PacketGenerator.setPageSizeV4(image.rows, image.cols, quantity ?? 1, 0, false),
...PacketGenerator.writeImageData(image),
PacketGenerator.pageEnd(),
]);
}
override waitForFinished(): Promise<void> {
return this.abstraction.waitUntilPrintFinishedV2(
this.printOptions.totalPages ?? 1,
this.printOptions.statusPollIntervalMs
);
}
}

26
src/print_tasks/index.ts Normal file

@ -0,0 +1,26 @@
import { PrinterModel as M } from "../printer_models";
import { B1PrintTask } from "./B1PrintTask";
import { D110PrintTask } from "./D110PrintTask";
import { OldD11PrintTask } from "./OldD11PrintTask";
import { V5PrintTask } from "./V5PrintTask";
export const printTasks = {
D11_OLD: OldD11PrintTask,
D110: D110PrintTask,
B1: B1PrintTask,
V5: V5PrintTask,
};
export type PrintTaskName = keyof typeof printTasks;
export const printTaskNames = Object.keys(printTasks) as PrintTaskName[];
export const modelPrintTasks: Partial<Record<PrintTaskName, M[]>> = {
D11_OLD: [M.D11, M.D11S, M.B21_L2B, M.B21, M.B21_PRO, M.B21_C2B],
D110: [M.B21S, M.B21S_C2B, M.D110],
B1: [M.D11_H, M.D110_M, M.B1],
};
export const findPrintTask = (model: M): PrintTaskName | undefined => {
return (Object.keys(modelPrintTasks) as PrintTaskName[]).find((key) => modelPrintTasks[key]?.includes(model));
};

@ -9,6 +9,7 @@
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
"strict": true, /* Enable all strict type-checking options. */ "strict": true, /* Enable all strict type-checking options. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */ "skipLibCheck": true, /* Skip type checking all .d.ts files. */
"noImplicitOverride": true /* Ensure overriding members in derived classes are marked with an override modifier. */
} }
} }