From 1ab81bc0bd5d337798d360ed7bf1e9b48487edf6 Mon Sep 17 00:00:00 2001
From: MultiMote <contact@mmote.ru>
Date: Fri, 24 Jan 2025 18:50:06 +0300
Subject: [PATCH] Implement PrinterCheckLine

---
 docs/documents/niimbot_print_tasks.md |  2 +-
 src/client/abstract_client.ts         |  2 +-
 src/image_encoder.ts                  | 23 +++++++-
 src/packets/commands.ts               |  2 +-
 src/packets/packet_generator.ts       | 82 +++++++++++++++++++++------
 src/print_tasks/B1PrintTask.ts        | 22 +++----
 src/print_tasks/B21V1PrintTask.ts     | 28 +++++----
 src/print_tasks/D110PrintTask.ts      | 26 +++++----
 src/print_tasks/OldD11PrintTask.ts    | 19 ++++---
 src/print_tasks/V5PrintTask.ts        | 26 +++++----
 10 files changed, 157 insertions(+), 75 deletions(-)

diff --git a/docs/documents/niimbot_print_tasks.md b/docs/documents/niimbot_print_tasks.md
index 0c42729..48c6007 100644
--- a/docs/documents/niimbot_print_tasks.md
+++ b/docs/documents/niimbot_print_tasks.md
@@ -57,10 +57,10 @@ PrintStart [1(u8)]
 Page:
 
 ```
-PrintClear [1(u8)]
 PageStart [1(u8)]
 SetPageSize [rows(u16), cols(u16)]
 PrintEmptyRow | PrintBitmapRow | PrintBitmapRowIndexed
+PrinterCheckLine [line(u16), 1(u8)] - after every 200 lines (data example: 00c701, 018f01, 025701, 031f01, ...)
 PageEnd [1(u8)]
 ```
 
diff --git a/src/client/abstract_client.ts b/src/client/abstract_client.ts
index a622076..bdab153 100644
--- a/src/client/abstract_client.ts
+++ b/src/client/abstract_client.ts
@@ -90,7 +90,7 @@ export abstract class NiimbotAbstractClient extends EventEmitter<ClientEventMap>
       await this.sendPacket(packet, true);
 
       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);
diff --git a/src/image_encoder.ts b/src/image_encoder.ts
index 8ed641a..5738203 100644
--- a/src/image_encoder.ts
+++ b/src/image_encoder.ts
@@ -2,7 +2,7 @@ import { Utils } from ".";
 
 /** @category Image encoder */
 export type ImageRow = {
-  dataType: "void" | "pixels";
+  dataType: "void" | "pixels" | "check";
   rowNumber: number;
   repeat: number;
   blackPixelsCount: number;
@@ -83,14 +83,31 @@ export class ImageEncoder {
         } else {
           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 };
   }
 
   /** 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;
 
     if (printDirection === "left") {
diff --git a/src/packets/commands.ts b/src/packets/commands.ts
index 586d746..5bb016a 100644
--- a/src/packets/commands.ts
+++ b/src/packets/commands.ts
@@ -55,7 +55,7 @@ export enum RequestCommandId {
  * @category Packets
  **/
 export enum ResponseCommandId {
-  Invalid = -1,
+  In_Invalid = -1,
   In_NotSupported = 0x00,
   In_Connect = 0xc2,
   In_CalibrateHeight = 0x69,
diff --git a/src/packets/packet_generator.ts b/src/packets/packet_generator.ts
index e6451e9..a66aac2 100644
--- a/src/packets/packet_generator.ts
+++ b/src/packets/packet_generator.ts
@@ -10,9 +10,20 @@ import {
   commandsMap,
   NiimbotCrc32Packet,
 } from ".";
-import { EncodedImage, ImageEncoder, ImageRow } from "../image_encoder";
+import { EncodedImage, ImageEncoder } from "../image_encoder";
 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.
  * @category Packets
@@ -178,8 +189,14 @@ export class PacketGenerator {
     return this.mapped(TX.PrintEmptyRow, [...Utils.u16ToBytes(pos), repeats]);
   }
 
-  public static printBitmapRow(pos: number, repeats: number, data: Uint8Array, printheadPixels?: number): NiimbotPacket {
-    const counts = Utils.countPixelsForBitmapPacket(data, printheadPixels ?? 0);
+  public static printBitmapRow(
+    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]);
   }
 
@@ -189,9 +206,10 @@ export class PacketGenerator {
     pos: number,
     repeats: number,
     data: Uint8Array,
-    printheadPixels?: number
+    printheadPixels: number,
+    countsMode: "auto" | "split" | "total" = "auto"
   ): NiimbotPacket {
-    const counts = Utils.countPixelsForBitmapPacket(data, printheadPixels ?? 0);
+    const counts = Utils.countPixelsForBitmapPacket(data, printheadPixels ?? 0, countsMode);
     const indexes: Uint8Array = ImageEncoder.indexPixels(data);
 
     if (counts.total > 6) {
@@ -209,18 +227,50 @@ export class PacketGenerator {
     return this.mapped(TX.WriteRFID, data);
   }
 
-  public static writeImageData(image: EncodedImage, printheadPixels?: number): NiimbotPacket[] {
-    return image.rowsData.map((p: ImageRow) => {
-      if (p.dataType === "pixels") {
-        if (p.blackPixelsCount > 6) {
-          return this.printBitmapRow(p.rowNumber, p.repeat, p.rowData!, printheadPixels);
+  public static checkLine(line: number): NiimbotPacket {
+    return this.mapped(TX.PrinterCheckLine, [...Utils.u16ToBytes(line), 0x01]);
+  }
+
+  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 {
-          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 {
-        return this.printEmptySpace(p.rowNumber, p.repeat);
+        continue;
       }
-    });
+
+      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 {
@@ -232,11 +282,11 @@ export class PacketGenerator {
   }
 
   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)");
     }
 
-    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]);
   }
diff --git a/src/print_tasks/B1PrintTask.ts b/src/print_tasks/B1PrintTask.ts
index 626b14b..dd8996e 100644
--- a/src/print_tasks/B1PrintTask.ts
+++ b/src/print_tasks/B1PrintTask.ts
@@ -17,20 +17,22 @@ export class B1PrintTask extends AbstractPrintTask {
   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, this.printheadPixels()),
-      PacketGenerator.pageEnd(),
-    ], this.printOptions.pageTimeoutMs);
+    return this.abstraction.sendAll(
+      [
+        PacketGenerator.pageStart(),
+        PacketGenerator.setPageSizeV3(image.rows, image.cols, quantity ?? 1),
+        ...PacketGenerator.writeImageData(image, { printheadPixels: this.printheadPixels() }),
+        PacketGenerator.pageEnd(),
+      ],
+      this.printOptions.pageTimeoutMs
+    );
   }
 
   override waitForFinished(): Promise<void> {
     this.abstraction.setPacketTimeout(this.printOptions.statusTimeoutMs);
 
-    return this.abstraction.waitUntilPrintFinishedByStatusPoll(
-      this.printOptions.totalPages,
-      this.printOptions.statusPollIntervalMs
-    ).finally(() => this.abstraction.setDefaultPacketTimeout());
+    return this.abstraction
+      .waitUntilPrintFinishedByStatusPoll(this.printOptions.totalPages, this.printOptions.statusPollIntervalMs)
+      .finally(() => this.abstraction.setDefaultPacketTimeout());
   }
 }
diff --git a/src/print_tasks/B21V1PrintTask.ts b/src/print_tasks/B21V1PrintTask.ts
index 65b4153..330a6c8 100644
--- a/src/print_tasks/B21V1PrintTask.ts
+++ b/src/print_tasks/B21V1PrintTask.ts
@@ -18,22 +18,28 @@ export class B21V1PrintTask extends AbstractPrintTask {
     this.checkAddPage(quantity ?? 1);
 
     for (let i = 0; i < (quantity ?? 1); i++) {
-      await this.abstraction.sendAll([
-        PacketGenerator.printClear(),
-        PacketGenerator.pageStart(),
-        PacketGenerator.setPageSizeV2(image.rows, image.cols),
-        ...PacketGenerator.writeImageData(image, this.printheadPixels()),
-        PacketGenerator.pageEnd(),
-      ], this.printOptions.pageTimeoutMs);
+      await this.abstraction.sendAll(
+        [
+          // PacketGenerator.printClear(),
+          PacketGenerator.pageStart(),
+          PacketGenerator.setPageSizeV2(image.rows, image.cols),
+          ...PacketGenerator.writeImageData(image, {
+            countsMode: "total",
+            enableCheckLine: true,
+            printheadPixels: this.printheadPixels(),
+          }),
+          PacketGenerator.pageEnd(),
+        ],
+        this.printOptions.pageTimeoutMs
+      );
     }
   }
 
   override waitForFinished(): Promise<void> {
     this.abstraction.setPacketTimeout(this.printOptions.statusTimeoutMs);
 
-    return this.abstraction.waitUntilPrintFinishedByPrintEndPoll(
-      this.printOptions.totalPages,
-      this.printOptions.statusPollIntervalMs
-    ).finally(() => this.abstraction.setDefaultPacketTimeout());
+    return this.abstraction
+      .waitUntilPrintFinishedByPrintEndPoll(this.printOptions.totalPages, this.printOptions.statusPollIntervalMs)
+      .finally(() => this.abstraction.setDefaultPacketTimeout());
   }
 }
diff --git a/src/print_tasks/D110PrintTask.ts b/src/print_tasks/D110PrintTask.ts
index 69614a9..f738814 100644
--- a/src/print_tasks/D110PrintTask.ts
+++ b/src/print_tasks/D110PrintTask.ts
@@ -17,22 +17,24 @@ export class D110PrintTask extends AbstractPrintTask {
   override printPage(image: EncodedImage, quantity?: number): Promise<void> {
     this.checkAddPage(quantity ?? 1);
 
-    return this.abstraction.sendAll([
-      PacketGenerator.printClear(),
-      PacketGenerator.pageStart(),
-      PacketGenerator.setPageSizeV2(image.rows, image.cols),
-      PacketGenerator.setPrintQuantity(quantity ?? 1),
-      ...PacketGenerator.writeImageData(image, this.printheadPixels()),
-      PacketGenerator.pageEnd(),
-    ], this.printOptions.pageTimeoutMs);
+    return this.abstraction.sendAll(
+      [
+        PacketGenerator.printClear(),
+        PacketGenerator.pageStart(),
+        PacketGenerator.setPageSizeV2(image.rows, image.cols),
+        PacketGenerator.setPrintQuantity(quantity ?? 1),
+        ...PacketGenerator.writeImageData(image, { printheadPixels: this.printheadPixels() }),
+        PacketGenerator.pageEnd(),
+      ],
+      this.printOptions.pageTimeoutMs
+    );
   }
 
   override waitForFinished(): Promise<void> {
     this.abstraction.setPacketTimeout(this.printOptions.statusTimeoutMs);
 
-    return this.abstraction.waitUntilPrintFinishedByStatusPoll(
-      this.printOptions.totalPages,
-      this.printOptions.statusPollIntervalMs
-    ).finally(() => this.abstraction.setDefaultPacketTimeout());
+    return this.abstraction
+      .waitUntilPrintFinishedByStatusPoll(this.printOptions.totalPages, this.printOptions.statusPollIntervalMs)
+      .finally(() => this.abstraction.setDefaultPacketTimeout());
   }
 }
diff --git a/src/print_tasks/OldD11PrintTask.ts b/src/print_tasks/OldD11PrintTask.ts
index 7a21e83..6325c36 100644
--- a/src/print_tasks/OldD11PrintTask.ts
+++ b/src/print_tasks/OldD11PrintTask.ts
@@ -17,14 +17,17 @@ export class OldD11PrintTask extends AbstractPrintTask {
   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, this.printheadPixels()),
-      PacketGenerator.pageEnd(),
-    ], this.printOptions.pageTimeoutMs);
+    return this.abstraction.sendAll(
+      [
+        PacketGenerator.printClear(),
+        PacketGenerator.pageStart(),
+        PacketGenerator.setPageSizeV1(image.rows),
+        PacketGenerator.setPrintQuantity(quantity ?? 1),
+        ...PacketGenerator.writeImageData(image, { printheadPixels: this.printheadPixels() }),
+        PacketGenerator.pageEnd(),
+      ],
+      this.printOptions.pageTimeoutMs
+    );
   }
 
   override waitForFinished(): Promise<void> {
diff --git a/src/print_tasks/V5PrintTask.ts b/src/print_tasks/V5PrintTask.ts
index 8affe0c..badf406 100644
--- a/src/print_tasks/V5PrintTask.ts
+++ b/src/print_tasks/V5PrintTask.ts
@@ -1,5 +1,5 @@
 import { EncodedImage } from "../image_encoder";
-import {  PacketGenerator } from "../packets";
+import { PacketGenerator } from "../packets";
 import { AbstractPrintTask } from "./AbstractPrintTask";
 
 /**
@@ -10,27 +10,29 @@ export class V5PrintTask extends AbstractPrintTask {
     return this.abstraction.sendAll([
       PacketGenerator.setDensity(this.printOptions.density),
       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> {
     this.checkAddPage(quantity ?? 1);
 
-    return this.abstraction.sendAll([
-      PacketGenerator.pageStart(),
-      PacketGenerator.setPageSizeV4(image.rows, image.cols, quantity ?? 1, 0, false),
-      ...PacketGenerator.writeImageData(image, this.printheadPixels()),
-      PacketGenerator.pageEnd(),
-    ], this.printOptions.pageTimeoutMs);
+    return this.abstraction.sendAll(
+      [
+        PacketGenerator.pageStart(),
+        PacketGenerator.setPageSizeV4(image.rows, image.cols, quantity ?? 1, 0, false),
+        ...PacketGenerator.writeImageData(image, { printheadPixels: this.printheadPixels() }),
+        PacketGenerator.pageEnd(),
+      ],
+      this.printOptions.pageTimeoutMs
+    );
   }
 
   override waitForFinished(): Promise<void> {
     this.abstraction.setPacketTimeout(this.printOptions.statusTimeoutMs);
 
-    return this.abstraction.waitUntilPrintFinishedByStatusPoll(
-      this.printOptions.totalPages ?? 1,
-      this.printOptions.statusPollIntervalMs
-    ).finally(() => this.abstraction.setDefaultPacketTimeout());
+    return this.abstraction
+      .waitUntilPrintFinishedByStatusPoll(this.printOptions.totalPages ?? 1, this.printOptions.statusPollIntervalMs)
+      .finally(() => this.abstraction.setDefaultPacketTimeout());
   }
 }