diff --git a/README.md b/README.md index 6c47ff2..99cf22c 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Yarn: yarn add @mmote/niimbluelib --exact ``` -### Usage example +### Usage example (may be outdated) ```js import { Utils, RequestCommandId, ResponseCommandId, NiimbotBluetoothClient, ImageEncoder, PrintTaskVersion } from "@mmote/niimbluelib"; @@ -75,14 +75,20 @@ ctx.stroke(); // draw border 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 { - await client.abstraction.waitUntilPrintFinished(taskVersion, quantity); + await printTask.printInit(); + await printTask.printPage(encoded, quantity); + await printTask.waitForFinished(); } catch (e) { console.error(e); } diff --git a/src/client/index.ts b/src/client/index.ts index e0ea156..6a9b3ac 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -9,7 +9,7 @@ import { } from "../packets"; import { PrinterModelMeta, getPrinterMetaById } from "../printer_models"; import { ClientEventMap, PacketSentEvent, PrinterInfoFetchedEvent, HeartbeatEvent, HeartbeatFailedEvent } from "./events"; -import { getPrintTaskVersion, PrintTaskVersion } from "../print_task_versions"; +import { findPrintTask, PrintTaskName } from "../print_tasks"; export type ConnectionInfo = { deviceName?: string; @@ -159,14 +159,14 @@ export abstract class NiimbotAbstractClient extends TypedEventTarget<ClientEvent } /** Determine print task version if any */ - public getPrintTaskVersion(): PrintTaskVersion | undefined { + public getPrintTaskType(): PrintTaskName | undefined { const meta = this.getModelMetadata(); if (meta === undefined) { return undefined; } - return getPrintTaskVersion(meta.model); + return findPrintTask(meta.model); } public setPacketInterval(milliseconds: number) { diff --git a/src/index.ts b/src/index.ts index 549ddba..1769d2c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,4 +3,4 @@ export * from "./packets"; export * from "./image_encoder"; export * from "./utils"; export * from "./printer_models"; -export * from "./print_task_versions"; +export * from "./print_tasks"; diff --git a/src/packets/abstraction.ts b/src/packets/abstraction.ts index ae48c01..e33af73 100644 --- a/src/packets/abstraction.ts +++ b/src/packets/abstraction.ts @@ -11,13 +11,13 @@ import { SoundSettingsType, } from "."; import { NiimbotAbstractClient, PacketReceivedEvent, PrintProgressEvent } from "../client"; -import { EncodedImage } from "../image_encoder"; -import { PrintTaskVersion } from "../print_task_versions"; +import { PrintTaskName, printTasks } from "../print_tasks"; +import { AbstractPrintTask, PrintOptions } from "../print_tasks/AbstractPrintTask"; import { PrinterModel } from "../printer_models"; import { Validators, Utils } from "../utils"; import { SequentialDataReader } from "./data_reader"; import { NiimbotPacket } from "./packet"; -import { PacketGenerator, PrintOptions } from "./packet_generator"; +import { PacketGenerator } from "./packet_generator"; export class PrintError extends Error { public readonly reasonId: number; @@ -92,10 +92,16 @@ export class Abstraction { } /** 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); } + public async sendAll(packets: NiimbotPacket[], forceTimeout?: number): Promise<void> { + for (const p of packets) { + await this.send(p, forceTimeout); + } + } + public async getPrintStatus(): Promise<PrintStatus> { const packet = await this.send(PacketGenerator.printStatus()); @@ -342,32 +348,6 @@ export class Abstraction { 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> { return new Promise<void>((resolve, reject) => { const listener = (evt: PacketReceivedEvent) => { @@ -381,7 +361,7 @@ export class Abstraction { this.statusTimeoutTimer = setTimeout(() => { this.client.removeEventListener("packetreceived", listener); reject(new Error("Timeout waiting print status")); - }, timeoutMs); + }, timeoutMs ?? 5_000); if (page === pagesToPrint) { clearTimeout(this.statusTimeoutTimer); @@ -431,28 +411,15 @@ export class Abstraction { clearInterval(this.statusPollTimer); 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> { await this.send(PacketGenerator.printEnd()); } + + public newPrintTask(name: PrintTaskName, options?: PrintOptions): AbstractPrintTask { + return new printTasks[name](this, options); + } } diff --git a/src/packets/packet_generator.ts b/src/packets/packet_generator.ts index 71c23b3..ade938c 100644 --- a/src/packets/packet_generator.ts +++ b/src/packets/packet_generator.ts @@ -1,7 +1,6 @@ import { AutoShutdownTime, HeartbeatType, - LabelType, NiimbotPacket, PrinterInfoType, RequestCommandId, @@ -10,15 +9,8 @@ import { SoundSettingsType, } from "."; import { EncodedImage, ImageEncoder, ImageRow } from "../image_encoder"; -import { PrintTaskVersion } from "../print_task_versions"; import { Utils } from "../utils"; -export type PrintOptions = { - labelType?: LabelType; - density?: number; - quantity?: number; -}; - export class PacketGenerator { public static generic( 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), - ]; - } } diff --git a/src/print_task_versions.ts b/src/print_task_versions.ts deleted file mode 100644 index ccfc4fb..0000000 --- a/src/print_task_versions.ts +++ /dev/null @@ -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; -}; diff --git a/src/print_tasks/AbstractPrintTask.ts b/src/print_tasks/AbstractPrintTask.ts new file mode 100644 index 0000000..d907cad --- /dev/null +++ b/src/print_tasks/AbstractPrintTask.ts @@ -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>; +} diff --git a/src/print_tasks/B1PrintTask.ts b/src/print_tasks/B1PrintTask.ts new file mode 100644 index 0000000..f9664fd --- /dev/null +++ b/src/print_tasks/B1PrintTask.ts @@ -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 + ); + } +} diff --git a/src/print_tasks/D110PrintTask.ts b/src/print_tasks/D110PrintTask.ts new file mode 100644 index 0000000..0c03d2f --- /dev/null +++ b/src/print_tasks/D110PrintTask.ts @@ -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 + ); + } +} diff --git a/src/print_tasks/OldD11PrintTask.ts b/src/print_tasks/OldD11PrintTask.ts new file mode 100644 index 0000000..b16a583 --- /dev/null +++ b/src/print_tasks/OldD11PrintTask.ts @@ -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 + ); + } +} diff --git a/src/print_tasks/V5PrintTask.ts b/src/print_tasks/V5PrintTask.ts new file mode 100644 index 0000000..60c0d77 --- /dev/null +++ b/src/print_tasks/V5PrintTask.ts @@ -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 + ); + } +} diff --git a/src/print_tasks/index.ts b/src/print_tasks/index.ts new file mode 100644 index 0000000..8dcb860 --- /dev/null +++ b/src/print_tasks/index.ts @@ -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)); +}; diff --git a/tsconfig.json b/tsconfig.json index 0b15f57..8a86686 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,7 @@ "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. */ "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. */ } }