diff --git a/src/client/bluetooth_impl.ts b/src/client/bluetooth_impl.ts
index fbb85a8..bb1f32b 100644
--- a/src/client/bluetooth_impl.ts
+++ b/src/client/bluetooth_impl.ts
@@ -43,7 +43,6 @@ export class NiimbotBluetoothClient extends NiimbotAbstractClient {
       this.gattServer = undefined;
       this.channel = undefined;
       this.info = {};
-      this.stopHeartbeat();
       this.dispatchTypedEvent("disconnect", new DisconnectEvent());
       device.removeEventListener("gattserverdisconnected", disconnectListener);
     };
diff --git a/src/client/events.ts b/src/client/events.ts
index b3d8230..42316aa 100644
--- a/src/client/events.ts
+++ b/src/client/events.ts
@@ -57,6 +57,14 @@ export class HeartbeatEvent extends Event {
   }
 }
 
+export class HeartbeatFailedEvent extends Event {
+  failedAttempts: number;
+  constructor(failedAttempts: number) {
+    super("heartbeatfailed");
+    this.failedAttempts = failedAttempts;
+  }
+}
+
 export class PrinterInfoFetchedEvent extends Event {
   info: PrinterInfo;
   constructor(info: PrinterInfo) {
@@ -92,6 +100,7 @@ export interface ClientEventMap {
   packetreceived: PacketReceivedEvent;
   packetsent: PacketSentEvent;
   heartbeat: HeartbeatEvent;
+  heartbeatfailed: HeartbeatFailedEvent;
   printerinfofetched: PrinterInfoFetchedEvent;
   printprogress: PrintProgressEvent;
 }
diff --git a/src/client/index.ts b/src/client/index.ts
index fb571cf..e0ea156 100644
--- a/src/client/index.ts
+++ b/src/client/index.ts
@@ -8,7 +8,7 @@ import {
   NiimbotPacket,
 } from "../packets";
 import { PrinterModelMeta, getPrinterMetaById } from "../printer_models";
-import { ClientEventMap, PacketSentEvent, PrinterInfoFetchedEvent, HeartbeatEvent } from "./events";
+import { ClientEventMap, PacketSentEvent, PrinterInfoFetchedEvent, HeartbeatEvent, HeartbeatFailedEvent } from "./events";
 import { getPrintTaskVersion, PrintTaskVersion } from "../print_task_versions";
 
 export type ConnectionInfo = {
@@ -33,12 +33,17 @@ export abstract class NiimbotAbstractClient extends TypedEventTarget<ClientEvent
   public readonly abstraction: Abstraction;
   protected info: PrinterInfo = {};
   private heartbeatTimer?: NodeJS.Timeout;
+  private heartbeatFails: number = 0;
+  private heartbeatIntervalMs: number = 2_000;
+
   /** https://github.com/MultiMote/niimblue/issues/5 */
   protected packetIntervalMs: number = 10;
 
   constructor() {
     super();
     this.abstraction = new Abstraction(this);
+    this.addEventListener("connect", () => this.startHeartbeat())
+    this.addEventListener("disconnect", () => this.stopHeartbeat())
   }
 
   /** Connect to printer port */
@@ -102,19 +107,38 @@ export abstract class NiimbotAbstractClient extends TypedEventTarget<ClientEvent
   }
 
   /**
-   * Starts the heartbeat timer, "heartbeat" is emitted after packet received.
+   * Set interval for {@link startHeartbeat}.
    *
    * @param interval Heartbeat interval, default is 1000ms
    */
-  public startHeartbeat(intervalMs: number = 1000): void {
+  public setHeartbeatInterval(intervalMs: number): void {
+    this.heartbeatIntervalMs = intervalMs;
+  }
+
+
+  /**
+   * Starts the heartbeat timer, "heartbeat" is emitted after packet received.
+   *
+   * If you need to change interval, call {@link setHeartbeatInterval} before.
+   */
+  public startHeartbeat(): void {
+    this.heartbeatFails = 0;
+
+    this.stopHeartbeat();
+
     this.heartbeatTimer = setInterval(() => {
       this.abstraction
         .heartbeat()
         .then((data) => {
+          this.heartbeatFails = 0;
           this.dispatchTypedEvent("heartbeat", new HeartbeatEvent(data));
         })
-        .catch(console.error);
-    }, intervalMs);
+        .catch((e) => {
+          console.error(e);
+          this.heartbeatFails++;
+          this.dispatchTypedEvent("heartbeatfailed", new HeartbeatFailedEvent(this.heartbeatFails));
+        });
+    }, this.heartbeatIntervalMs);
   }
 
   public stopHeartbeat(): void {
diff --git a/src/client/serial_impl.ts b/src/client/serial_impl.ts
index bb87809..6beaed7 100644
--- a/src/client/serial_impl.ts
+++ b/src/client/serial_impl.ts
@@ -25,7 +25,6 @@ export class NiimbotSerialClient extends NiimbotAbstractClient {
 
     _port.addEventListener("disconnect", () => {
       this.port = undefined;
-      console.log("serial disconnect event");
       this.dispatchTypedEvent("disconnect", new DisconnectEvent());
     });
 
diff --git a/src/packets/abstraction.ts b/src/packets/abstraction.ts
index b02d293..ae48c01 100644
--- a/src/packets/abstraction.ts
+++ b/src/packets/abstraction.ts
@@ -92,8 +92,8 @@ export class Abstraction {
   }
 
   /** Send packet and wait for response */
-  private async send(packet: NiimbotPacket): Promise<NiimbotPacket> {
-    return this.client.sendPacketWaitResponse(packet, this.timeout);
+  private async send(packet: NiimbotPacket, forceTimeout?: number): Promise<NiimbotPacket> {
+    return this.client.sendPacketWaitResponse(packet, forceTimeout ?? this.timeout);
   }
 
   public async getPrintStatus(): Promise<PrintStatus> {
@@ -200,7 +200,7 @@ export class Abstraction {
   }
 
   public async heartbeat(): Promise<HeartbeatData> {
-    const packet = await this.send(PacketGenerator.heartbeat(HeartbeatType.Advanced1));
+    const packet = await this.send(PacketGenerator.heartbeat(HeartbeatType.Advanced1), 500);
 
     const info: HeartbeatData = {
       paperState: -1,
@@ -342,6 +342,15 @@ 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,