diff --git a/eslint.config.mjs b/eslint.config.mjs
index 2ee9ae4..a52edb9 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -15,4 +15,14 @@ export default [
   pluginJs.configs.recommended,
   ...tseslint.configs.recommendedTypeChecked,
   { ignores: ["dist/*", "dumps/*", "**/*.{mjs,js}"] },
+  {
+    rules: {
+      "@typescript-eslint/no-unused-vars": [
+        "error",
+        {
+          caughtErrorsIgnorePattern: "^_",
+        },
+      ],
+    },
+  },
 ];
diff --git a/src/client/bluetooth_impl.ts b/src/client/bluetooth_impl.ts
index 194f1e7..0f411a0 100644
--- a/src/client/bluetooth_impl.ts
+++ b/src/client/bluetooth_impl.ts
@@ -1,9 +1,8 @@
-import { Mutex } from "async-mutex";
 import { ConnectEvent, DisconnectEvent, PacketReceivedEvent, RawPacketReceivedEvent, RawPacketSentEvent } from "../events";
 import { ConnectionInfo, NiimbotAbstractClient } from ".";
 import { NiimbotPacket } from "../packets/packet";
-import { ConnectResult, PrinterErrorCode, PrintError, ResponseCommandId } from "../packets";
-import { Utils, Validators } from "../utils";
+import { ConnectResult, ResponseCommandId } from "../packets";
+import { Utils } from "../utils";
 
 class BleConfiguration {
   public static readonly SERVICE: string = "e7810a71-73ae-499d-8c15-faa9aef0c3f2";
@@ -35,7 +34,6 @@ class BleConfiguration {
 export class NiimbotBluetoothClient extends NiimbotAbstractClient {
   private gattServer?: BluetoothRemoteGATTServer = undefined;
   private channel?: BluetoothRemoteGATTCharacteristic = undefined;
-  private mutex: Mutex = new Mutex();
 
   public async connect(): Promise<ConnectionInfo> {
     await this.disconnect();
@@ -114,57 +112,6 @@ export class NiimbotBluetoothClient extends NiimbotAbstractClient {
     this.info = {};
   }
 
-  /**
-   * Send packet and wait for response.
-   * If packet.responsePacketCommandId is defined, it will wait for packet with this command id.
-   */
-  public async sendPacketWaitResponse(packet: NiimbotPacket, timeoutMs?: number): Promise<NiimbotPacket> {
-    return this.mutex.runExclusive(async () => {
-      await this.sendPacket(packet, true);
-
-      if (packet.oneWay) {
-        return new NiimbotPacket(ResponseCommandId.Invalid, []); // or undefined is better?
-      }
-
-      // what if response received at this point?
-
-      return new Promise((resolve, reject) => {
-        let timeout: NodeJS.Timeout | undefined = undefined;
-
-        const listener = (evt: PacketReceivedEvent) => {
-          const pktIn = evt.packet;
-          const cmdIn = pktIn.command as ResponseCommandId;
-
-          if (
-            packet.validResponseIds.length === 0 ||
-            packet.validResponseIds.includes(cmdIn) ||
-            [ResponseCommandId.In_PrintError, ResponseCommandId.In_NotSupported].includes(cmdIn)
-          ) {
-            clearTimeout(timeout);
-            this.off("packetreceived", listener);
-
-            if (cmdIn === ResponseCommandId.In_PrintError) {
-              Validators.u8ArrayLengthEquals(pktIn.data, 1);
-              const errorName = PrinterErrorCode[pktIn.data[0]] ?? "unknown";
-              reject(new PrintError(`Print error ${pktIn.data[0]}: ${errorName}`, pktIn.data[0]));
-            } else if (cmdIn === ResponseCommandId.In_NotSupported) {
-              reject(new PrintError("Feature not supported", 0));
-            } else {
-              resolve(pktIn);
-            }
-          }
-        };
-
-        timeout = setTimeout(() => {
-          this.off("packetreceived", listener);
-          reject(new Error(`Timeout waiting response (waited for ${Utils.bufToHex(packet.validResponseIds, ", ")})`));
-        }, timeoutMs ?? 1000);
-
-        this.on("packetreceived", listener);
-      });
-    });
-  }
-
   public async sendRaw(data: Uint8Array, force?: boolean) {
     const send = async () => {
       if (this.channel === undefined) {
diff --git a/src/client/index.ts b/src/client/index.ts
index 3315675..57b2f8a 100644
--- a/src/client/index.ts
+++ b/src/client/index.ts
@@ -1,4 +1,5 @@
 import { EventEmitter } from "eventemitter3";
+import { Mutex } from "async-mutex";
 import {
   Abstraction,
   AutoShutdownTime,
@@ -6,11 +7,21 @@ import {
   ConnectResult,
   LabelType,
   NiimbotPacket,
+  PrinterErrorCode,
+  PrintError,
+  ResponseCommandId,
 } from "../packets";
 import { PrinterModelMeta, getPrinterMetaById } from "../printer_models";
-import { ClientEventMap, PacketSentEvent, PrinterInfoFetchedEvent, HeartbeatEvent, HeartbeatFailedEvent } from "../events";
+import {
+  ClientEventMap,
+  PacketSentEvent,
+  PrinterInfoFetchedEvent,
+  HeartbeatEvent,
+  HeartbeatFailedEvent,
+  PacketReceivedEvent,
+} from "../events";
 import { findPrintTask, PrintTaskName } from "../print_tasks";
-
+import { Utils, Validators } from "../utils";
 
 /**
  * Represents the connection result information.
@@ -40,7 +51,6 @@ export interface PrinterInfo {
   hardwareVersion?: string;
 }
 
-
 /**
  * Abstract class representing a client with common functionality for interacting with a printer.
  * Hardware interface must be defined after extending this class.
@@ -53,6 +63,8 @@ export abstract class NiimbotAbstractClient extends EventEmitter<ClientEventMap>
   private heartbeatTimer?: NodeJS.Timeout;
   private heartbeatFails: number = 0;
   private heartbeatIntervalMs: number = 2_000;
+  protected mutex: Mutex = new Mutex();
+  protected debug: boolean = false;
 
   /** @see https://github.com/MultiMote/niimblue/issues/5 */
   protected packetIntervalMs: number = 10;
@@ -80,16 +92,64 @@ export abstract class NiimbotAbstractClient extends EventEmitter<ClientEventMap>
   public abstract isConnected(): boolean;
 
   /**
-   * Send packet and wait for response.
-   * If packet.responsePacketCommandId is defined, it will wait for packet with this command id.
+   * Send packet and wait for response for {@link timeoutMs} milliseconds.
+   *
+   * If {@link NiimbotPacket.validResponseIds() validResponseIds} is defined, it will wait for packet with this command id.
+   *
+   * @throws {@link PrintError} when {@link ResponseCommandId.In_PrintError} or {@link ResponseCommandId.In_NotSupported} received.
+   *
+   * @returns {NiimbotPacket} Printer response object.
    */
-  public abstract sendPacketWaitResponse(packet: NiimbotPacket, timeoutMs?: number): Promise<NiimbotPacket>;
+  public async sendPacketWaitResponse(packet: NiimbotPacket, timeoutMs: number = 1000): Promise<NiimbotPacket> {
+    return this.mutex.runExclusive(async () => {
+      await this.sendPacket(packet, true);
+
+      if (packet.oneWay) {
+        return new NiimbotPacket(ResponseCommandId.Invalid, []); // or undefined is better?
+      }
+
+      return new Promise((resolve, reject) => {
+        let timeout: NodeJS.Timeout | undefined = undefined;
+
+        const listener = (evt: PacketReceivedEvent) => {
+          const pktIn = evt.packet;
+          const cmdIn = pktIn.command as ResponseCommandId;
+
+          if (
+            packet.validResponseIds.length === 0 ||
+            packet.validResponseIds.includes(cmdIn) ||
+            [ResponseCommandId.In_PrintError, ResponseCommandId.In_NotSupported].includes(cmdIn)
+          ) {
+            clearTimeout(timeout);
+            this.off("packetreceived", listener);
+
+            if (cmdIn === ResponseCommandId.In_PrintError) {
+              Validators.u8ArrayLengthEquals(pktIn.data, 1);
+              const errorName = PrinterErrorCode[pktIn.data[0]] ?? "unknown";
+              reject(new PrintError(`Print error ${pktIn.data[0]}: ${errorName}`, pktIn.data[0]));
+            } else if (cmdIn === ResponseCommandId.In_NotSupported) {
+              reject(new PrintError("Feature not supported", 0));
+            } else {
+              resolve(pktIn);
+            }
+          }
+        };
+
+        timeout = setTimeout(() => {
+          this.off("packetreceived", listener);
+          reject(new Error(`Timeout waiting response (waited for ${Utils.bufToHex(packet.validResponseIds, ", ")})`));
+        }, timeoutMs ?? 1000);
+
+        this.on("packetreceived", listener);
+      });
+    });
+  }
 
   /**
    * Send raw bytes to the printer port.
    *
    * @param data Bytes to send.
-   * @param force Ignore mutex lock. You should avoid using it.
+   * @param force Ignore mutex lock. It used internally and you should avoid using it.
    */
   public abstract sendRaw(data: Uint8Array, force?: boolean): Promise<void>;
 
@@ -98,7 +158,9 @@ export abstract class NiimbotAbstractClient extends EventEmitter<ClientEventMap>
     this.emit("packetsent", new PacketSentEvent(packet));
   }
 
-  /** Send "connect" packet and fetch the protocol version */
+  /**
+   * Send "connect" packet and fetch the protocol version.
+   **/
   protected async initialNegotiate(): Promise<void> {
     const cfg = this.info;
     cfg.connectResult = await this.abstraction.connectResult();
@@ -112,20 +174,19 @@ export abstract class NiimbotAbstractClient extends EventEmitter<ClientEventMap>
     }
   }
 
-
   /**
    * Fetches printer information and stores it.
    */
   public async fetchPrinterInfo(): Promise<PrinterInfo> {
     this.info.modelId = await this.abstraction.getPrinterModel();
 
-    this.info.serial = await this.abstraction.getPrinterSerialNumber().catch(console.error) ?? undefined;
-    this.info.mac = await this.abstraction.getPrinterBluetoothMacAddress().catch(console.error) ?? undefined;
-    this.info.charge = await this.abstraction.getBatteryChargeLevel().catch(console.error) ?? undefined;
-    this.info.autoShutdownTime = await this.abstraction.getAutoShutDownTime().catch(console.error) ?? undefined;
-    this.info.labelType = await this.abstraction.getLabelType().catch(console.error) ?? undefined;
-    this.info.hardwareVersion = await this.abstraction.getHardwareVersion().catch(console.error) ?? undefined;
-    this.info.softwareVersion = await this.abstraction.getSoftwareVersion().catch(console.error) ?? undefined;
+    this.info.serial = (await this.abstraction.getPrinterSerialNumber().catch(console.error)) ?? undefined;
+    this.info.mac = (await this.abstraction.getPrinterBluetoothMacAddress().catch(console.error)) ?? undefined;
+    this.info.charge = (await this.abstraction.getBatteryChargeLevel().catch(console.error)) ?? undefined;
+    this.info.autoShutdownTime = (await this.abstraction.getAutoShutDownTime().catch(console.error)) ?? undefined;
+    this.info.labelType = (await this.abstraction.getLabelType().catch(console.error)) ?? undefined;
+    this.info.hardwareVersion = (await this.abstraction.getHardwareVersion().catch(console.error)) ?? undefined;
+    this.info.softwareVersion = (await this.abstraction.getSoftwareVersion().catch(console.error)) ?? undefined;
 
     this.emit("printerinfofetched", new PrinterInfoFetchedEvent(this.info));
     return this.info;
@@ -216,6 +277,13 @@ export abstract class NiimbotAbstractClient extends EventEmitter<ClientEventMap>
   public setPacketInterval(milliseconds: number) {
     this.packetIntervalMs = milliseconds;
   }
+
+  /**
+   * Enable some debug information logging.
+   */
+  public setDebug(value: boolean) {
+    this.debug = value;
+  }
 }
 
 export * from "./bluetooth_impl";
diff --git a/src/client/serial_impl.ts b/src/client/serial_impl.ts
index 0fdd5ea..192c39f 100644
--- a/src/client/serial_impl.ts
+++ b/src/client/serial_impl.ts
@@ -1,15 +1,8 @@
-import { Mutex } from "async-mutex";
-import {
-  ConnectEvent,
-  DisconnectEvent,
-  PacketReceivedEvent,
-  RawPacketReceivedEvent,
-  RawPacketSentEvent,
-} from "../events";
+import { ConnectEvent, DisconnectEvent, PacketReceivedEvent, RawPacketReceivedEvent, RawPacketSentEvent } from "../events";
 import { ConnectionInfo, NiimbotAbstractClient } from ".";
 import { NiimbotPacket } from "../packets/packet";
-import { ConnectResult, PrinterErrorCode, PrintError, ResponseCommandId } from "../packets";
-import { Utils, Validators } from "../utils";
+import { ConnectResult } from "../packets";
+import { Utils } from "../utils";
 
 /**
  * Uses [Web Serial API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API)
@@ -20,7 +13,6 @@ export class NiimbotSerialClient extends NiimbotAbstractClient {
   private port?: SerialPort = undefined;
   private writer?: WritableStreamDefaultWriter<Uint8Array> = undefined;
   private reader?: ReadableStreamDefaultReader<Uint8Array> = undefined;
-  private mutex: Mutex = new Mutex();
 
   public async connect(): Promise<ConnectionInfo> {
     await this.disconnect();
@@ -77,7 +69,9 @@ export class NiimbotSerialClient extends NiimbotAbstractClient {
       try {
         const result = await this.reader!.read();
         if (result.value) {
-          // console.info(`<< serial chunk ${Utils.bufToHex(result.value)}`);
+          if (this.debug) {
+            console.info(`<< serial chunk ${Utils.bufToHex(result.value)}`);
+          }
 
           const newBuf = new Uint8Array(buf.length + result.value.length);
           newBuf.set(buf, 0);
@@ -89,7 +83,7 @@ export class NiimbotSerialClient extends NiimbotAbstractClient {
           console.log("done");
           break;
         }
-      } catch (e) {
+      } catch (_e) {
         break;
       }
 
@@ -105,8 +99,10 @@ export class NiimbotSerialClient extends NiimbotAbstractClient {
 
           buf = new Uint8Array();
         }
-      } catch (e) {
-        // console.info(`Incomplete packet, ignoring:${Utils.bufToHex(buf)}`);
+      } catch (_e) {
+        if (this.debug) {
+          console.info(`Incomplete packet, ignoring:${Utils.bufToHex(buf)}`);
+        }
       }
     }
   }
@@ -132,65 +128,16 @@ export class NiimbotSerialClient extends NiimbotAbstractClient {
   }
 
   public isConnected(): boolean {
-    return this.port !== undefined && this.writer !== undefined;
-  }
-
-  public async sendPacketWaitResponse(packet: NiimbotPacket, timeoutMs: number = 1000): Promise<NiimbotPacket> {
-    if (!this.port?.readable || !this.port?.writable) {
-      throw new Error("Port is not readable/writable");
-    }
-
-    return this.mutex.runExclusive(async () => {
-      await this.sendPacket(packet, true);
-
-      if (packet.oneWay) {
-        return new NiimbotPacket(ResponseCommandId.Invalid, []); // or undefined is better?
-      }
-
-      return new Promise((resolve, reject) => {
-        let timeout: NodeJS.Timeout | undefined = undefined;
-
-        const listener = (evt: PacketReceivedEvent) => {
-          const pktIn = evt.packet;
-          const cmdIn = pktIn.command as ResponseCommandId;
-
-          if (
-            packet.validResponseIds.length === 0 ||
-            packet.validResponseIds.includes(cmdIn) ||
-            [ResponseCommandId.In_PrintError, ResponseCommandId.In_NotSupported].includes(cmdIn)
-          ) {
-            clearTimeout(timeout);
-            this.off("packetreceived", listener);
-
-            if (cmdIn === ResponseCommandId.In_PrintError) {
-              Validators.u8ArrayLengthEquals(pktIn.data, 1);
-              const errorName = PrinterErrorCode[pktIn.data[0]] ?? "unknown";
-              reject(new PrintError(`Print error ${pktIn.data[0]}: ${errorName}`, pktIn.data[0]));
-            } else if (cmdIn === ResponseCommandId.In_NotSupported) {
-              reject(new PrintError("Feature not supported", 0));
-            } else {
-              resolve(pktIn);
-            }
-          }
-        };
-
-        timeout = setTimeout(() => {
-          this.off("packetreceived", listener);
-          reject(new Error(`Timeout waiting response (waited for ${Utils.bufToHex(packet.validResponseIds, ", ")})`));
-        }, timeoutMs ?? 1000);
-
-        this.on("packetreceived", listener);
-      });
-    });
+    return this.port !== undefined && this.writer !== undefined && this.reader !== undefined;
   }
 
   public async sendRaw(data: Uint8Array, force?: boolean) {
     const send = async () => {
-      if (this.writer === undefined) {
-        throw new Error("Port is not writable");
+      if (!this.isConnected()) {
+        throw new Error("Port is not readable/writable");
       }
       await Utils.sleep(this.packetIntervalMs);
-      await this.writer.write(data);
+      await this.writer!.write(data);
       this.emit("rawpacketsent", new RawPacketSentEvent(data));
     };