Raspberry Piから58mmサーマルレシートプリンタ「MJ-5890K」にPythonで印刷する

Raspberry Piに58mmのサーマルレシートプリンタ「MJ-5890K」をUSB接続し、Pythonから印刷できる環境を構築してみました。

最終的には、単純なテキストだけでなく、

  • QRコード
  • 日本語
  • 罫線付きの表
  • フォントサイズの自動調整

まで印刷できました。

今回はそのセットアップ手順をまとめます。

構成

今回使用した構成は以下です。

  • Raspberry Pi
  • 58mm Thermal Receipt Printer MJ-5890K
  • USB接続
  • Python 3
  • python-escpos
  • Pillow
  • qrcode

最終的な印刷の流れは、

Raspberry Pi
    ↓
Python
    ↓
Pillow / qrcode
    ↓
レシート画像を生成
    ↓
python-escpos
    ↓
/dev/usb/lp0
    ↓
MJ-5890K

という構成にしました。

日本語や表などは、プリンタへ文字として送るのではなく、レシート全体を画像として生成して印刷します。

この方法なら、プリンタ側の日本語フォントや文字コードへの依存をかなり減らせます。

Raspberry PiにUSB接続

まずMJ-5890KをRaspberry PiにUSB接続します。

認識状態を確認します。

lsusb

今回の環境では以下のように認識されました。

Bus 001 Device 004: ID 0416:5011 Winbond Electronics Corp. Virtual Com Port

VID/PIDは、

VID: 0416
PID: 5011

です。

ここで「Virtual Com Port」と表示されたので、最初はシリアルデバイスかと思いました。

しかし、

ls -l /dev/ttyUSB* /dev/ttyACM*

を確認してもデバイスがありません。

そこでカーネルの認識状態を確認しました。

sudo dmesg | grep -Ei '0416|5011|Winbond|usblp|ttyUSB|ttyACM'

結果は、

usb 1-1.3: New USB device found, idVendor=0416, idProduct=5011
usblp 1-1.3:1.0: usblp0: USB Bidirectional printer dev 4 if 0 alt 0 proto 2 vid 0x0416 pid 0x5011
usbcore: registered new interface driver usblp

となっていました。

つまりシリアルポートではなく、LinuxのUSBプリンタとして認識されています。

デバイスは、

/dev/usb/lp0

です。

lsusbに表示される製品名だけでは判断せず、dmesgまで確認するのがポイントでした。

まずは直接印刷してみる

Pythonを使う前に、Linuxから直接データを送って印刷できるか確認します。

printf '\x1b\x40Hello Raspberry Pi!\nTest Print\n\n\n' | sudo tee /dev/usb/lp0 > /dev/null

無事、

Hello Raspberry Pi!
Test Print

と印刷されました。

これで、

Raspberry Pi → /dev/usb/lp0 → MJ-5890K

の経路が正常に動いていることが確認できます。

Python環境を作る

今回はシステムのPython環境を直接変更せず、venvを使用しました。

mkdir -p ~/thermal-print
cd ~/thermal-print

python3 -m venv .venv
source .venv/bin/activate

必要なライブラリをインストールします。

pip install "qrcode[pil]" python-escpos

Pythonから印刷するとPermission denied

Pythonから/dev/usb/lp0を開こうとすると、

PermissionError: [Errno 13] Permission denied: '/dev/usb/lp0'

となりました。

デバイスの権限を確認します。

ls -l /dev/usb/lp0

今回の環境では、

crw-rw---- 1 root lp 180, 0 ... /dev/usb/lp0

となっていました。

そこで、実行ユーザーをlpグループへ追加します。

sudo usermod -aG lp $USER

確認します。

getent group lp

グループ追加後は、一度ログアウトしてログインし直します。

groups

lpが含まれていれば、sudoを使わずにPythonからプリンタへアクセスできます。

QRコードを印刷する

まずQRコードだけ印刷してみます。

今回は「なっぷ」のURLをQRコードにしました。

import qrcode
from escpos.printer import File

URL = "https://www.nap-camp.com"
PRINTER = "/dev/usb/lp0"

qr = qrcode.QRCode(
    version=None,
    error_correction=qrcode.constants.ERROR_CORRECT_M,
    box_size=8,
    border=4,
)

qr.add_data(URL)
qr.make(fit=True)

img = qr.make_image(
    fill_color="black",
    back_color="white"
)

img.save("/tmp/qr.png")

printer = File(PRINTER)

printer.set(align="center")
printer.image("/tmp/qr.png")
printer.text("\n\n\n")

printer.close()

実行します。

python3 print_qr.py

QRコードも問題なく印刷でき、スマートフォンから読み取ることができました。

日本語を印刷する

次は日本語です。

プリンタ側の日本語フォントや文字コードに依存する方法ではなく、Pillowで日本語を画像化して印刷することにしました。

日本語フォントとしてNoto Sans CJKをインストールします。

sudo apt update
sudo apt install -y fonts-noto-cjk
fc-cache -fv

フォントの場所を確認します。

fc-match "Noto Sans CJK JP" -f '%{file}\n'

今回使用したフォントは、

/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc
/usr/share/fonts/opentype/noto/NotoSansCJK-Bold.ttc

です。

Pillowからこのフォントを指定すれば、日本語を画像として描画できます。

58mm幅のレシートを画像として作る

今回のプリンタでは、レシート画像を384px幅で作成しました。

WIDTH = 384

例えば、

       キャンプ場検索

        [QRコード]

   QRコードを読み取って
   キャンプ場を検索できます

      www.nap-camp.com

というレイアウト全体をPillowで1枚の画像として生成します。

その画像を、

printer.image("/tmp/receipt.png")

で印刷します。

この方式にすると、HTML/CSSで画面を作るのに近い感覚でレシートのレイアウトを組めます。

表形式も印刷できる

Pillowで罫線を描画すれば表も作れます。

今回は4列×5行、

┌──────┬────────┬──────┬────────┐
│ A    │ 値A    │ F    │ 値F    │
├──────┼────────┼──────┼────────┤
│ B    │ 値B    │ G    │ 値G    │
├──────┼────────┼──────┼────────┤
│ C    │ 値C    │ H    │ 値H    │
├──────┼────────┼──────┼────────┤
│ D    │ 値D    │ I    │ 値I    │
├──────┼────────┼──────┼────────┤
│ E    │ 値E    │ J    │ 値J    │
└──────┴────────┴──────┴────────┘

という形式を作りました。

構造としては、

ヘッダ | 値 | ヘッダ | 値

です。

384pxの幅に対して、

col_widths = [
    72,     # ヘッダ
    104,    # 値
    72,     # ヘッダ
    104     # 値
]

としました。

長い文字は自動的にフォントを小さくする

表形式では、データによって文字数が変わります。

そこでセル幅を超える場合は、自動的にフォントサイズを小さくするようにしました。

def fit_font(draw, text, font_path, max_width,
             start_size=20, min_size=10):

    text = str(text)

    for size in range(start_size, min_size - 1, -1):

        font = ImageFont.truetype(
            font_path,
            size
        )

        bbox = draw.textbbox(
            (0, 0),
            text,
            font=font
        )

        text_width = bbox[2] - bbox[0]

        if text_width <= max_width:
            return font

    return ImageFont.truetype(
        font_path,
        min_size
    )

例えば、

12,345円

なら大きな文字のまま表示し、

ABC-123456789

のような長い文字列なら、セルに収まるまでフォントサイズを小さくします。

ここまででできたこと

今回の検証では、以下まで確認できました。

  • Raspberry PiからMJ-5890KをUSB認識
  • /dev/usb/lp0への直接印刷
  • Pythonからの印刷
  • sudoなしでの印刷
  • QRコード印刷
  • 日本語印刷
  • QRコード+日本語のレイアウト
  • 4列×5行の表
  • 長い文字列のフォントサイズ自動調整

安価な58mmサーマルプリンタですが、Pythonと組み合わせるとかなり自由度の高い出力ができそうです。

特に、プリンタの文字コードやフォント機能に依存せず、Pillowで「レシート全体を画像として作る」という方法は扱いやすいと感じました。

今後は、Webシステムやkintoneなどのデータと連携して、Raspberry Piを小型のレシート印刷端末として使うところまで試してみたいと思います。