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を小型のレシート印刷端末として使うところまで試してみたいと思います。
