Implement PrinterCheckLine

This commit is contained in:
MultiMote 2025-01-24 18:50:06 +03:00
parent a453dfe497
commit 1ab81bc0bd
10 changed files with 157 additions and 75 deletions

@ -57,10 +57,10 @@ PrintStart [1(u8)]
Page: Page:
``` ```
PrintClear [1(u8)]
PageStart [1(u8)] PageStart [1(u8)]
SetPageSize [rows(u16), cols(u16)] SetPageSize [rows(u16), cols(u16)]
PrintEmptyRow | PrintBitmapRow | PrintBitmapRowIndexed PrintEmptyRow | PrintBitmapRow | PrintBitmapRowIndexed
PrinterCheckLine [line(u16), 1(u8)] - after every 200 lines (data example: 00c701, 018f01, 025701, 031f01, ...)
PageEnd [1(u8)] PageEnd [1(u8)]
``` ```

@ -90,7 +90,7 @@ export abstract class NiimbotAbstractClient extends EventEmitter<ClientEventMap>
await this.sendPacket(packet, true); await this.sendPacket(packet, true);
if (packet.oneWay) { if (packet.oneWay) {
return new NiimbotPacket(ResponseCommandId.Invalid, []); // or undefined is better? return new NiimbotPacket(ResponseCommandId.In_Invalid, []); // or undefined is better?
} }
return this.waitForPacket(packet.validResponseIds, true, timeoutMs); return this.waitForPacket(packet.validResponseIds, true, timeoutMs);

@ -2,7 +2,7 @@ import { Utils } from ".";
/** @category Image encoder */ /** @category Image encoder */
export type ImageRow = { export type ImageRow = {
dataType: "void" | "pixels"; dataType: "void" | "pixels" | "check";
rowNumber: number; rowNumber: number;
repeat: number; repeat: number;
blackPixelsCount: number; blackPixelsCount: number;
@ -83,14 +83,31 @@ export class ImageEncoder {
} else { } else {
rowsData.push(newPart); rowsData.push(newPart);
} }
const sendRowCheck = row % 200 === 199;
if (sendRowCheck) {
rowsData.push({
dataType: "check",
rowNumber: row,
repeat: 0,
rowData: undefined,
blackPixelsCount: 0,
});
}
} }
} }
return { cols, rows, rowsData }; return { cols, rows, rowsData };
} }
/** printDirection = "left" rotates image to 90 degrees clockwise */ /** printDirection = "left" rotates image to 90 degrees clockwise */
public static isPixelNonWhite(iData: ImageData, x: number, y: number, printDirection: PrintDirection = "left"): boolean { public static isPixelNonWhite(
iData: ImageData,
x: number,
y: number,
printDirection: PrintDirection = "left"
): boolean {
let idx = y * iData.width + x; let idx = y * iData.width + x;
if (printDirection === "left") { if (printDirection === "left") {

@ -55,7 +55,7 @@ export enum RequestCommandId {
* @category Packets * @category Packets
**/ **/
export enum ResponseCommandId { export enum ResponseCommandId {
Invalid = -1, In_Invalid = -1,
In_NotSupported = 0x00, In_NotSupported = 0x00,
In_Connect = 0xc2, In_Connect = 0xc2,
In_CalibrateHeight = 0x69, In_CalibrateHeight = 0x69,

@ -10,9 +10,20 @@ import {
commandsMap, commandsMap,
NiimbotCrc32Packet, NiimbotCrc32Packet,
} from "."; } from ".";
import { EncodedImage, ImageEncoder, ImageRow } from "../image_encoder"; import { EncodedImage, ImageEncoder } from "../image_encoder";
import { Utils } from "../utils"; import { Utils } from "../utils";
export interface ImagePacketsGenerateOptions {
/** Mode for "black pixel count" section of bitmap packet. */
countsMode?: "auto" | "split" | "total";
/** Disable PrintBitmapRowIndexed packet. */
noIndexPacket?: boolean;
/** Send PrinterCheckLine every 200 line. */
enableCheckLine?: boolean;
/** Printer head resolution. Used for "black pixel count" section calculation. */
printheadPixels?: number;
}
/** /**
* A helper class that generates various types of packets. * A helper class that generates various types of packets.
* @category Packets * @category Packets
@ -178,8 +189,14 @@ export class PacketGenerator {
return this.mapped(TX.PrintEmptyRow, [...Utils.u16ToBytes(pos), repeats]); return this.mapped(TX.PrintEmptyRow, [...Utils.u16ToBytes(pos), repeats]);
} }
public static printBitmapRow(pos: number, repeats: number, data: Uint8Array, printheadPixels?: number): NiimbotPacket { public static printBitmapRow(
const counts = Utils.countPixelsForBitmapPacket(data, printheadPixels ?? 0); pos: number,
repeats: number,
data: Uint8Array,
printheadPixels: number,
countsMode: "auto" | "split" | "total" = "auto"
): NiimbotPacket {
const counts = Utils.countPixelsForBitmapPacket(data, printheadPixels, countsMode);
return this.mapped(TX.PrintBitmapRow, [...Utils.u16ToBytes(pos), ...counts.parts, repeats, ...data]); return this.mapped(TX.PrintBitmapRow, [...Utils.u16ToBytes(pos), ...counts.parts, repeats, ...data]);
} }
@ -189,9 +206,10 @@ export class PacketGenerator {
pos: number, pos: number,
repeats: number, repeats: number,
data: Uint8Array, data: Uint8Array,
printheadPixels?: number printheadPixels: number,
countsMode: "auto" | "split" | "total" = "auto"
): NiimbotPacket { ): NiimbotPacket {
const counts = Utils.countPixelsForBitmapPacket(data, printheadPixels ?? 0); const counts = Utils.countPixelsForBitmapPacket(data, printheadPixels ?? 0, countsMode);
const indexes: Uint8Array = ImageEncoder.indexPixels(data); const indexes: Uint8Array = ImageEncoder.indexPixels(data);
if (counts.total > 6) { if (counts.total > 6) {
@ -209,18 +227,50 @@ export class PacketGenerator {
return this.mapped(TX.WriteRFID, data); return this.mapped(TX.WriteRFID, data);
} }
public static writeImageData(image: EncodedImage, printheadPixels?: number): NiimbotPacket[] { public static checkLine(line: number): NiimbotPacket {
return image.rowsData.map((p: ImageRow) => { return this.mapped(TX.PrinterCheckLine, [...Utils.u16ToBytes(line), 0x01]);
if (p.dataType === "pixels") { }
if (p.blackPixelsCount > 6) {
return this.printBitmapRow(p.rowNumber, p.repeat, p.rowData!, printheadPixels); public static writeImageData(image: EncodedImage, options?: ImagePacketsGenerateOptions): NiimbotPacket[] {
let out: NiimbotPacket[] = [];
for (const d of image.rowsData) {
if (d.dataType === "pixels") {
if (d.blackPixelsCount <= 6 && !options?.noIndexPacket) {
out.push(
this.printBitmapRowIndexed(
d.rowNumber,
d.repeat,
d.rowData!,
options?.printheadPixels ?? 0,
options?.countsMode ?? "auto"
)
);
} else { } else {
return this.printBitmapRowIndexed(p.rowNumber, p.repeat, p.rowData!, printheadPixels); out.push(
this.printBitmapRow(
d.rowNumber,
d.repeat,
d.rowData!,
options?.printheadPixels ?? 0,
options?.countsMode ?? "auto"
)
);
} }
} else { continue;
return this.printEmptySpace(p.rowNumber, p.repeat);
} }
});
if (d.dataType === "check" && options?.enableCheckLine) {
out.push(this.checkLine(d.rowNumber));
continue;
}
if (d.dataType === "void") {
out.push(this.printEmptySpace(d.rowNumber, d.repeat));
}
}
return out;
} }
public static printTestPage(): NiimbotPacket { public static printTestPage(): NiimbotPacket {
@ -232,11 +282,11 @@ export class PacketGenerator {
} }
public static startFirmwareUpgrade(version: string): NiimbotPacket { public static startFirmwareUpgrade(version: string): NiimbotPacket {
if(!/^\d+\.\d+$/.test(version)) { if (!/^\d+\.\d+$/.test(version)) {
throw new Error("Invalid version format (x.x expected)"); throw new Error("Invalid version format (x.x expected)");
} }
const [a, b] = version.split(".").map(p => parseInt(p)); const [a, b] = version.split(".").map((p) => parseInt(p));
return this.mapped(TX.StartFirmwareUpgrade, [a, b]); return this.mapped(TX.StartFirmwareUpgrade, [a, b]);
} }

@ -17,20 +17,22 @@ export class B1PrintTask extends AbstractPrintTask {
override printPage(image: EncodedImage, quantity?: number): Promise<void> { override printPage(image: EncodedImage, quantity?: number): Promise<void> {
this.checkAddPage(quantity ?? 1); this.checkAddPage(quantity ?? 1);
return this.abstraction.sendAll([ return this.abstraction.sendAll(
PacketGenerator.pageStart(), [
PacketGenerator.setPageSizeV3(image.rows, image.cols, quantity ?? 1), PacketGenerator.pageStart(),
...PacketGenerator.writeImageData(image, this.printheadPixels()), PacketGenerator.setPageSizeV3(image.rows, image.cols, quantity ?? 1),
PacketGenerator.pageEnd(), ...PacketGenerator.writeImageData(image, { printheadPixels: this.printheadPixels() }),
], this.printOptions.pageTimeoutMs); PacketGenerator.pageEnd(),
],
this.printOptions.pageTimeoutMs
);
} }
override waitForFinished(): Promise<void> { override waitForFinished(): Promise<void> {
this.abstraction.setPacketTimeout(this.printOptions.statusTimeoutMs); this.abstraction.setPacketTimeout(this.printOptions.statusTimeoutMs);
return this.abstraction.waitUntilPrintFinishedByStatusPoll( return this.abstraction
this.printOptions.totalPages, .waitUntilPrintFinishedByStatusPoll(this.printOptions.totalPages, this.printOptions.statusPollIntervalMs)
this.printOptions.statusPollIntervalMs .finally(() => this.abstraction.setDefaultPacketTimeout());
).finally(() => this.abstraction.setDefaultPacketTimeout());
} }
} }

@ -18,22 +18,28 @@ export class B21V1PrintTask extends AbstractPrintTask {
this.checkAddPage(quantity ?? 1); this.checkAddPage(quantity ?? 1);
for (let i = 0; i < (quantity ?? 1); i++) { for (let i = 0; i < (quantity ?? 1); i++) {
await this.abstraction.sendAll([ await this.abstraction.sendAll(
PacketGenerator.printClear(), [
PacketGenerator.pageStart(), // PacketGenerator.printClear(),
PacketGenerator.setPageSizeV2(image.rows, image.cols), PacketGenerator.pageStart(),
...PacketGenerator.writeImageData(image, this.printheadPixels()), PacketGenerator.setPageSizeV2(image.rows, image.cols),
PacketGenerator.pageEnd(), ...PacketGenerator.writeImageData(image, {
], this.printOptions.pageTimeoutMs); countsMode: "total",
enableCheckLine: true,
printheadPixels: this.printheadPixels(),
}),
PacketGenerator.pageEnd(),
],
this.printOptions.pageTimeoutMs
);
} }
} }
override waitForFinished(): Promise<void> { override waitForFinished(): Promise<void> {
this.abstraction.setPacketTimeout(this.printOptions.statusTimeoutMs); this.abstraction.setPacketTimeout(this.printOptions.statusTimeoutMs);
return this.abstraction.waitUntilPrintFinishedByPrintEndPoll( return this.abstraction
this.printOptions.totalPages, .waitUntilPrintFinishedByPrintEndPoll(this.printOptions.totalPages, this.printOptions.statusPollIntervalMs)
this.printOptions.statusPollIntervalMs .finally(() => this.abstraction.setDefaultPacketTimeout());
).finally(() => this.abstraction.setDefaultPacketTimeout());
} }
} }

@ -17,22 +17,24 @@ export class D110PrintTask extends AbstractPrintTask {
override printPage(image: EncodedImage, quantity?: number): Promise<void> { override printPage(image: EncodedImage, quantity?: number): Promise<void> {
this.checkAddPage(quantity ?? 1); this.checkAddPage(quantity ?? 1);
return this.abstraction.sendAll([ return this.abstraction.sendAll(
PacketGenerator.printClear(), [
PacketGenerator.pageStart(), PacketGenerator.printClear(),
PacketGenerator.setPageSizeV2(image.rows, image.cols), PacketGenerator.pageStart(),
PacketGenerator.setPrintQuantity(quantity ?? 1), PacketGenerator.setPageSizeV2(image.rows, image.cols),
...PacketGenerator.writeImageData(image, this.printheadPixels()), PacketGenerator.setPrintQuantity(quantity ?? 1),
PacketGenerator.pageEnd(), ...PacketGenerator.writeImageData(image, { printheadPixels: this.printheadPixels() }),
], this.printOptions.pageTimeoutMs); PacketGenerator.pageEnd(),
],
this.printOptions.pageTimeoutMs
);
} }
override waitForFinished(): Promise<void> { override waitForFinished(): Promise<void> {
this.abstraction.setPacketTimeout(this.printOptions.statusTimeoutMs); this.abstraction.setPacketTimeout(this.printOptions.statusTimeoutMs);
return this.abstraction.waitUntilPrintFinishedByStatusPoll( return this.abstraction
this.printOptions.totalPages, .waitUntilPrintFinishedByStatusPoll(this.printOptions.totalPages, this.printOptions.statusPollIntervalMs)
this.printOptions.statusPollIntervalMs .finally(() => this.abstraction.setDefaultPacketTimeout());
).finally(() => this.abstraction.setDefaultPacketTimeout());
} }
} }

@ -17,14 +17,17 @@ export class OldD11PrintTask extends AbstractPrintTask {
override printPage(image: EncodedImage, quantity: number): Promise<void> { override printPage(image: EncodedImage, quantity: number): Promise<void> {
this.checkAddPage(quantity ?? 1); this.checkAddPage(quantity ?? 1);
return this.abstraction.sendAll([ return this.abstraction.sendAll(
PacketGenerator.printClear(), [
PacketGenerator.pageStart(), PacketGenerator.printClear(),
PacketGenerator.setPageSizeV1(image.rows), PacketGenerator.pageStart(),
PacketGenerator.setPrintQuantity(quantity ?? 1), PacketGenerator.setPageSizeV1(image.rows),
...PacketGenerator.writeImageData(image, this.printheadPixels()), PacketGenerator.setPrintQuantity(quantity ?? 1),
PacketGenerator.pageEnd(), ...PacketGenerator.writeImageData(image, { printheadPixels: this.printheadPixels() }),
], this.printOptions.pageTimeoutMs); PacketGenerator.pageEnd(),
],
this.printOptions.pageTimeoutMs
);
} }
override waitForFinished(): Promise<void> { override waitForFinished(): Promise<void> {

@ -1,5 +1,5 @@
import { EncodedImage } from "../image_encoder"; import { EncodedImage } from "../image_encoder";
import { PacketGenerator } from "../packets"; import { PacketGenerator } from "../packets";
import { AbstractPrintTask } from "./AbstractPrintTask"; import { AbstractPrintTask } from "./AbstractPrintTask";
/** /**
@ -10,27 +10,29 @@ export class V5PrintTask extends AbstractPrintTask {
return this.abstraction.sendAll([ return this.abstraction.sendAll([
PacketGenerator.setDensity(this.printOptions.density), PacketGenerator.setDensity(this.printOptions.density),
PacketGenerator.setLabelType(this.printOptions.labelType), PacketGenerator.setLabelType(this.printOptions.labelType),
PacketGenerator.printStartV5(this.printOptions.totalPages, 0, 0) PacketGenerator.printStartV5(this.printOptions.totalPages, 0, 0),
]); ]);
} }
override printPage(image: EncodedImage, quantity?: number): Promise<void> { override printPage(image: EncodedImage, quantity?: number): Promise<void> {
this.checkAddPage(quantity ?? 1); this.checkAddPage(quantity ?? 1);
return this.abstraction.sendAll([ return this.abstraction.sendAll(
PacketGenerator.pageStart(), [
PacketGenerator.setPageSizeV4(image.rows, image.cols, quantity ?? 1, 0, false), PacketGenerator.pageStart(),
...PacketGenerator.writeImageData(image, this.printheadPixels()), PacketGenerator.setPageSizeV4(image.rows, image.cols, quantity ?? 1, 0, false),
PacketGenerator.pageEnd(), ...PacketGenerator.writeImageData(image, { printheadPixels: this.printheadPixels() }),
], this.printOptions.pageTimeoutMs); PacketGenerator.pageEnd(),
],
this.printOptions.pageTimeoutMs
);
} }
override waitForFinished(): Promise<void> { override waitForFinished(): Promise<void> {
this.abstraction.setPacketTimeout(this.printOptions.statusTimeoutMs); this.abstraction.setPacketTimeout(this.printOptions.statusTimeoutMs);
return this.abstraction.waitUntilPrintFinishedByStatusPoll( return this.abstraction
this.printOptions.totalPages ?? 1, .waitUntilPrintFinishedByStatusPoll(this.printOptions.totalPages ?? 1, this.printOptions.statusPollIntervalMs)
this.printOptions.statusPollIntervalMs .finally(() => this.abstraction.setDefaultPacketTimeout());
).finally(() => this.abstraction.setDefaultPacketTimeout());
} }
} }