ESC/POSでレシートプリンターを動かそう

· Updated: · たっつぁん / やまどり

はじめに

INIAD組み込み研究会では、レシートプリンターを扱うために、escpos-builder-tsというTypeScript向けパッケージを作成しました。

https://github.com/iniad-embedded/escpos-builder-ts

レシートプリンターは、一般的なプリンターのようにPDFや画像を印刷するというよりも、ESC/POSと呼ばれるコマンド体系を使って制御します。ESC/POSでは、文字を印字する、中央揃えにする、太字にする、紙を送る、QRコードを印字する、最後に用紙をカットする、といった命令をバイト列としてプリンターに送ります。

しかし、ESC/POSのコマンドを毎回バイト列で直接書くのは少し大変です。そこで、escpos-builder-tsを使うことで、TypeScriptのメソッドチェーンでレシートの内容を組み立てられるようにしました。

今回は、東芝TECのレシートプリンターTR-QT2を対象に、Node.jsからescpos-builder-tsを使って印刷データを生成し、プリンターへ送信する流れをまとめます。

サンプルプロジェクトは以下のリポジトリで公開しています。

https://github.com/iniad-embedded/escpos-builder-ts-example

この記事では、サンプルプロジェクトをもとに、TR-QT2でレシート印刷を行うまでの基本的な手順を説明します。

本記事はWindows環境を対象としています。

手順

1. 必要環境を用意する

今回のサンプルは、WindowsでUSB接続のレシートプリンターを動かすことを想定しています。

必要なものは以下です。

  • Node.js 18+
  • pnpm
  • Windows
  • USB接続のレシートプリンター(TR-QT2など)

TR-QT2はUSBで接続しておきます。Windows側で設定からプリンターを登録しておきます。手順は以下の通りです。

  1. 設定Bluetooth とデバイスプリンターとスキャナー を開く
  2. デバイスの追加 をクリックし、しばらく待ってから 手動で追加 をクリック
  3. ローカル プリンターまたはネットワーク プリンターを手動設定で追加する を選択して 次へ
  4. 既存のポートを使用USB00x (USB の仮想プリンター ポート) を選択して 次へ
    • USB ポートが複数ある場合は、管理者 PowerShell の Get-PrinterPort | Where-Object Name -Match '^USB' で対象ポートを確認できます
  5. ドライバーの選択で、製造元 Generic、プリンター Generic / Text Only を選択して 次へ
  6. プリンター名に TR-QT2 など分かりやすい名前を付けて 次へ
  7. 共有は このプリンターを共有しない を選択して登録を完了

2. サンプルプロジェクトをcloneする

まず、サンプルプロジェクトをcloneします。

git clone https://github.com/iniad-embedded/escpos-builder-ts-example.git
cd escpos-builder-ts-example

依存パッケージをインストールします。

pnpm install

TypeScriptをビルドします。

pnpm build

このプロジェクトでは、package.jsonに次のスクリプトを用意しています。

{
  "scripts": {
    "build": "tsc",
    "list": "node dist/main.js list",
    "print": "node dist/main.js"
  }
}

それぞれの役割は次のとおりです。

  • pnpm build: TypeScriptをコンパイルする
  • pnpm list: Windowsに登録されたプリンターを一覧表示する
  • pnpm print: レシートを印刷する

このサンプルで主に使用しているパッケージは次の3つです。

  • escpos-builder-ts: ESC/POSデータの生成
  • @printers/printers: Windowsの印刷スプーラーへのRAWデータ送信
  • sharp: ロゴ画像のリサイズとグレースケール化

3. 接続中のプリンターを確認する

印刷する前に、WindowsからTR-QT2が見えているか確認します。

pnpm run list

実行すると、Windowsに登録されているプリンターの一覧が表示されます。

Name                      PortName  PrinterStatus
----                      --------  -------------
TR-QT2                    USB001                3

このサンプルでは、PowerShellからGet-Printerを呼び出して、プリンター名、ポート名、ステータスを表示しています。

function runPowerShell(command: string): string {
  return execSync(`powershell -NoProfile -Command "${command}"`, {
    encoding: "utf8",
  }).trim();
}

function listPrinters(): void {
  console.log(
    runPowerShell(
      "Get-Printer | Select-Object Name, PortName, PrinterStatus | Format-Table -AutoSize",
    ),
  );
}

ここでTR-QT2が表示されれば、Windows側からプリンターが認識されています。

4. レシートの内容を組み立てる

レシートの内容は、main.tsの中でEscPosBuilderを使って組み立てています。

import { EscPosBuilder } from "escpos-builder-ts";

今回のサンプルでは、printReceipt()関数の中でEscPosBuilderを作成しています。

async function printReceipt(): Promise<void> {
  const builder = new EscPosBuilder({ encoding: "cp932", width: 35 });

TR-QT2ではCP932での印字を利用するため、このサンプルではencodingcp932を指定しています。

widthには35を指定しています。これはleftRight()rule()で行幅を計算するときに使います。実際の印字幅はプリンターの設定や用紙幅によって変わるので、必要に応じて調整します。

レシート本体は次のようにメソッドチェーンで書いています。

builder
  .align("center")
  .image(logo, { dither: "floyd-steinberg" })
  .textLine("------------------------------")
  .align("left")
  .leftRight("カーボン抵抗 *50", "¥10000")
  .newline(1)
  .leftRight("サンドイッチ *4", "¥580")
  .newline(1)
  .leftRight("3mm砲弾型赤色LED *500", "¥5000")
  .newline(1)
  .leftRight("Raspberry Pi 5 16GB *1", "¥60390")
  .newline(1)
  .leftRight("メモリサーチャージ", "¥5000")
  .rule()
  .bold(true)
  .leftRight("合計", "¥80970")
  .bold(false)
  .newline(2)
  .align("center")
  .qrcode("https://iniad-es.org", { size: 6 })
  .newline(3)
  .cut();

ここでは、ロゴ、区切り線、商品行、合計、QRコード、カットまでを1つのレシートとして組み立てています。

画像を印刷する

.image(logo, { dither: "floyd-steinberg" })

image()へ先ほど読み込んだ画像を渡します。ditherfloyd-steinbergを指定することで、グレースケール画像を白黒のドットへ変換するときに、濃淡を表現しやすくしています。

商品名と金額を左右に配置する

align("center")で中央揃え、bold(true)で太字、size(2, 2)で文字サイズを大きくしています。

.leftRight("カーボン抵抗 *50", "¥10000")

leftRight()は、左側に商品名、右側に金額を配置します。配置の計算には、ビルダー作成時の`width`が使われます。

QRコードを印刷する

QRコードはqrcode()で追加しています。

.qrcode("https://iniad-es.org", { size: 6 })

URLをQRコードとして追加します。`size`を変更すると、QRコードのセルサイズを調整できます。

用紙を送ってカットする

最後に、数行改行してからカットします。

.newline(3)
.cut();

ESC/POSのコマンドを直接書くと、中央揃えやQRコード、カットだけでもかなり読みにくくなります。escpos-builder-tsを使うことで、レシートの構造をそのままTypeScriptのコードとして書けるようにしています。

5. ESC/POSデータを生成する

レシートの内容を組み立てたら、build()を呼び出してESC/POSのバイト列を生成します。

const data = builder.build();

このdataが、プリンターへ送る実際の印刷データです。

生成される`data`は`Uint8Array`です。文字列へ変換すると、日本語や画像、QRコード、カットなどのコマンドが壊れる可能性があるため、バイト列のまま扱います。

現在のサンプルでは、@printers/printersprintBytes()を使って印刷データを送信します。

async function sendRaw(
  printerName: string,
  data: Uint8Array,
): Promise<void> {
  const jobId = await printBytes(printerName, data, {
    jobName: "ESC/POS",
  });
  console.log(
    `印刷ジョブ ${jobId} を送信しました (${data.byteLength} bytes)。`,
  );
}

6. USB接続のプリンター名を取得する

このサンプルでは、USB接続されているプリンターを自動で探すようにしています。

function getPrinterName(): string {
  const printerName = runPowerShell(
    "Get-Printer | Where-Object { $_.PortName -like 'USB*' } | Select-Object -First 1 -ExpandProperty Name",
  );

  if (!printerName) {
    throw new Error(
      "USB プリンターが見つかりません。\n" +
        "プリンターが接続・電源オンになっているか確認してください。\n" +
        "`pnpm run list` で利用可能なプリンターを確認できます。",
    );
  }

  return printerName;
}

printReceipt()の最後では、プリンター名を取得してから送信処理を待ちます。

const data = builder.build();
const printerName = getPrinterName();
console.log(`プリンター: ${printerName}`);
await sendRaw(printerName, data);

7. 実際に印刷する

準備ができたら、次のコマンドで印刷します。

pnpm build
pnpm print

うまくいくと、次のような出力になります。

プリンター: TR-QT2
印刷ジョブ 1000 を送信しました (4082 bytes)。

8. よくあるトラブル

pnpm listでTR-QT2が表示されない

pnpm listでTR-QT2が表示されない場合は、Windows側でプリンターが認識されていない可能性があります。

次の点を確認します。

  • TR-QT2の電源が入っているか
  • USBケーブルが接続されているか
  • Windowsの「プリンターとスキャナー」に表示されているか
  • ポート名がUSB001などになっているか
  • プリンターがエラー状態になっていないか

このサンプルでは、PortNameUSB*のプリンターを探しています。そのため、USB以外のポートとして登録されている場合は、自動検出できません。

USBプリンターが見つからない

USBプリンターが見つからない場合、getPrinterName()で次のエラーを出すようにしています。

USB プリンターが見つかりません。
プリンターが接続・電源オンになっているか確認してください。
`pnpm list` で利用可能なプリンターを確認できます。

この場合は、まずpnpm listを実行して、Windowsからどのように見えているか確認します。

pnpm run list

日本語が文字化けする

日本語が文字化けする場合は、encodingを確認します。

このサンプルでは、cp932を指定しています。

const builder = new EscPosBuilder({ encoding: "cp932", width: 35 });

また、プリンターが日本語印字に対応しているか、送信データを途中で文字列変換していないかも確認します。

印字位置がずれる

明細の金額が右端に揃わない、罫線の長さが合わない、といった場合は、widthを調整します。

const builder = new EscPosBuilder({ encoding: "cp932", width: 35 });

この値は、leftRight()rule()の幅計算に使われます。用紙幅やフォント設定によって見え方が変わるので、実機で印字しながら調整します。

カットされない

印字はできるのにカットされない場合は、cut()が呼ばれているか確認します。

.newline(3)
.cut();

それでもカットされない場合は、プリンター側のカッター設定やエラー状態を確認します。

おわりに

今回は、INIAD組み込み研究会で作成したescpos-builder-tsを使って、Node.jsから東芝TECのレシートプリンターTR-QT2を制御する方法をまとめました。

ESC/POSは、レシートプリンターを細かく制御できる一方で、コマンドをバイト列として扱う必要があるため、最初は少しとっつきにくい部分があります。escpos-builder-tsを使うことで、文字印字、中央揃え、太字、罫線、QRコード、紙送り、カットといった処理をTypeScriptから扱いやすく記述できます。

TR-QT2をNode.jsから動かすときの基本的な流れは、次の通りです。

  1. escpos-builder-tsでESC/POSデータを生成する
  2. 生成したUint8Arrayを環境に応じた方法でプリンターへ送信する
  3. 文字コード・紙幅・カット動作を確認する

サンプルプロジェクトは以下で公開しています。

https://github.com/iniad-embedded/escpos-builder-ts-example

まずは短い文字列の印字から試し、次に日本語、明細、QRコード、カット処理を追加していくと、問題を切り分けながら実装できます。

今後は、画像印刷、ロゴ印字、バーコード印字、Webアプリケーションとの連携などにも応用できます。Node.jsから実機のレシートプリンターを制御できるようになると、POSシステムや受付端末、整理券発行機、展示用デバイスなど、さまざまな組み込み系の制作に活用できます。