diff --git a/README.md b/README.md
index 4c44797..6c47ff2 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,7 @@ yarn add @mmote/niimbluelib --exact
 ### Usage example
 
 ```js
-import { Utils, RequestCommandId, ResponseCommandId, NiimbotBluetoothClient, ImageEncoder } from "@mmote/niimbluelib";
+import { Utils, RequestCommandId, ResponseCommandId, NiimbotBluetoothClient, ImageEncoder, PrintTaskVersion } from "@mmote/niimbluelib";
 
 const client = new NiimbotBluetoothClient();
 
@@ -77,10 +77,12 @@ ctx.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
 
 const image = ImageEncoder.encodeCanvas(canvas, props.printDirection);
 
-await client.abstraction.print(client.getPrintTaskVersion(), image, { quantity });
+const taskVersion = client.getPrintTaskVersion() ?? PrintTaskVersion.V3;
+
+await client.abstraction.print(taskVersion, image, { quantity });
 
 try {
-  await client.abstraction.waitUntilPrintFinished(quantity);
+  await client.abstraction.waitUntilPrintFinished(taskVersion, quantity);
 } catch (e) {
   console.error(e);
 }
@@ -95,4 +97,4 @@ Eslint not included. Install it with:
 
 ```
 npm install --no-save --no-package-lock eslint@9.x globals @eslint/js typescript-eslint
-```
\ No newline at end of file
+```
diff --git a/src/packets/abstraction.ts b/src/packets/abstraction.ts
index bea5925..b02d293 100644
--- a/src/packets/abstraction.ts
+++ b/src/packets/abstraction.ts
@@ -10,7 +10,7 @@ import {
   SoundSettingsItemType,
   SoundSettingsType,
 } from ".";
-import { NiimbotAbstractClient, PrintProgressEvent } from "../client";
+import { NiimbotAbstractClient, PacketReceivedEvent, PrintProgressEvent } from "../client";
 import { EncodedImage } from "../image_encoder";
 import { PrintTaskVersion } from "../print_task_versions";
 import { PrinterModel } from "../printer_models";
@@ -73,6 +73,7 @@ export class Abstraction {
   private client: NiimbotAbstractClient;
   private timeout: number = this.DEFAULT_TIMEOUT;
   private statusPollTimer: NodeJS.Timeout | undefined;
+  private statusTimeoutTimer: NodeJS.Timeout | undefined;
 
   constructor(client: NiimbotAbstractClient) {
     this.client = client;
@@ -102,7 +103,8 @@ export class Abstraction {
       Validators.u8ArrayLengthEquals(packet.data, 1);
       const errorName = PrinterErrorCode[packet.data[0]] ?? "unknown";
       throw new PrintError(
-        `Print error (${ResponseCommandId[packet.command]} packet received, code is ${packet.data[0]} - ${errorName})`, packet.data[0]
+        `Print error (${ResponseCommandId[packet.command]} packet received, code is ${packet.data[0]} - ${errorName})`,
+        packet.data[0]
       );
     }
 
@@ -357,6 +359,40 @@ export class Abstraction {
     }
   }
 
+  public async waitUntilPrintFinishedV1(pagesToPrint: number, timeoutMs: number = 5_000): Promise<void> {
+    return new Promise<void>((resolve, reject) => {
+      const listener = (evt: PacketReceivedEvent) => {
+        if (evt.packet.command === ResponseCommandId.In_PrinterPageIndex) {
+          Validators.u8ArrayLengthEquals(evt.packet.data, 2);
+          const page = Utils.bytesToI16(evt.packet.data);
+
+          this.client.dispatchTypedEvent("printprogress", new PrintProgressEvent(page, pagesToPrint, 100, 100));
+
+          clearTimeout(this.statusTimeoutTimer);
+          this.statusTimeoutTimer = setTimeout(() => {
+            this.client.removeEventListener("packetreceived", listener);
+            reject(new Error("Timeout waiting print status"));
+          }, timeoutMs);
+
+          if (page === pagesToPrint) {
+            clearTimeout(this.statusTimeoutTimer);
+            this.client.removeEventListener("packetreceived", listener);
+            resolve();
+          }
+        }
+      };
+
+      clearTimeout(this.statusTimeoutTimer);
+      this.statusTimeoutTimer = setTimeout(() => {
+        this.client.removeEventListener("packetreceived", listener);
+        reject(new Error("Timeout waiting print status"));
+      }, timeoutMs);
+
+      this.client.dispatchTypedEvent("printprogress", new PrintProgressEvent(1, pagesToPrint, 0, 0));
+      this.client.addEventListener("packetreceived", listener);
+    });
+  }
+
   /**
    * Poll printer every {@link pollIntervalMs} and resolve when printer pages equals {@link pagesToPrint}, pagePrintProgress=100, pageFeedProgress=100.
    *
@@ -365,8 +401,10 @@ export class Abstraction {
    * @param pagesToPrint Total pages to print.
    * @param pollIntervalMs Poll interval in milliseconds.
    */
-  public async waitUntilPrintFinished(pagesToPrint: number, pollIntervalMs: number = 300): Promise<void> {
-    return new Promise((resolve, reject) => {
+  public async waitUntilPrintFinishedV2(pagesToPrint: number, pollIntervalMs: number = 300): Promise<void> {
+    return new Promise<void>((resolve, reject) => {
+      this.client.dispatchTypedEvent("printprogress", new PrintProgressEvent(1, pagesToPrint, 0, 0));
+
       this.statusPollTimer = setInterval(() => {
         this.getPrintStatus()
           .then((status: PrintStatus) => {
@@ -388,6 +426,23 @@ export class Abstraction {
     });
   }
 
+  /**
+   * 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());
   }