ESC/POSでレシートプリンターを動かそう
はじめに
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側で設定からプリンターを登録しておきます。手順は以下の通りです。
- 設定 → Bluetooth とデバイス → プリンターとスキャナー を開く
- デバイスの追加 をクリックし、しばらく待ってから 手動で追加 をクリック
- ローカル プリンターまたはネットワーク プリンターを手動設定で追加する を選択して 次へ
- 既存のポートを使用 で
USB00x (USB の仮想プリンター ポート)を選択して 次へ- USB ポートが複数ある場合は、管理者 PowerShell の
Get-PrinterPort | Where-Object Name -Match '^USB'で対象ポートを確認できます
- USB ポートが複数ある場合は、管理者 PowerShell の
- ドライバーの選択で、製造元 Generic、プリンター Generic / Text Only を選択して 次へ
- プリンター名に
TR-QT2など分かりやすい名前を付けて 次へ - 共有は このプリンターを共有しない を選択して登録を完了
2. サンプルプロジェクトをcloneする
まず、サンプルプロジェクトをcloneします。
git clone https://github.com/iniad-embedded/escpos-builder-ts-example.git
cd escpos-builder-ts-example依存パッケージをインストールします。
pnpm installTypeScriptをビルドします。
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での印字を利用するため、このサンプルではencodingにcp932を指定しています。
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()へ先ほど読み込んだ画像を渡します。ditherにfloyd-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/printersのprintBytes()を使って印刷データを送信します。
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などになっているか
- プリンターがエラー状態になっていないか
このサンプルでは、PortNameがUSB*のプリンターを探しています。そのため、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から動かすときの基本的な流れは、次の通りです。
- escpos-builder-tsでESC/POSデータを生成する
- 生成したUint8Arrayを環境に応じた方法でプリンターへ送信する
- 文字コード・紙幅・カット動作を確認する
サンプルプロジェクトは以下で公開しています。
https://github.com/iniad-embedded/escpos-builder-ts-example
まずは短い文字列の印字から試し、次に日本語、明細、QRコード、カット処理を追加していくと、問題を切り分けながら実装できます。
今後は、画像印刷、ロゴ印字、バーコード印字、Webアプリケーションとの連携などにも応用できます。Node.jsから実機のレシートプリンターを制御できるようになると、POSシステムや受付端末、整理券発行機、展示用デバイスなど、さまざまな組み込み系の制作に活用できます。