前置き
以前 Mac からの Bluetooth Thermal Printer への出力の原理検証を行い、これを元により複雑な場合も容易なはずだと書いたが、実際は困難があった。
https://qiita.com/cure_honey/items/e82a85ef8ca8ed26210a
記事中 3 番目の内容。
困難な点は、Mac が Bluetooth LE(BLE)しか利用できないが、BLE は機器制御のような、短いデータのやり取りのための規格で、プリンタに画像を送るような多量のデータ転送には向いていない点にあった。規格上では一回に送れるパケット長さは 517 byte 程度にとどまり、さらに実際の機器ではそれより小さいこともある。以前利用した Phomemo M02S プリンタは 180 byte 程度になっている。
これらを考慮し、Mac の最新 OS に対応した版を、AI に作ってもらった。
Python Bleak 利用
実行結果
% python3 MyProg.py cat.jpeg
Connecting to B3A6BB31-E696-6BCD-B97D-E6E7CBF7BE2A...
Connected: True
MTU payload size: 182 bytes
Initializing printer...
Loading image: /cat.jpeg
Formatting data...
Data sending (55296 bytes)...
Data send complete.
Done. Disconnecting.
プログラム
import asyncio
import sys
from PIL import Image
from bleak import BleakClient
async def run(address, fn):
print(f"Connecting to {address}...")
async with BleakClient(address) as client:
x = client.is_connected # awaitと()を削除します
print(f"Connected: {x}")
# MTU(1回に送れる最大バイト数)の確認
mtu = getattr(client, "mtu_size", 23) or 23
chunk_size = max(mtu - 3, 20)
print(f"MTU payload size: {chunk_size} bytes")
# --- UUIDs ---
CHAR_WRITE = "0000ff02-0000-1000-8000-00805f9b34fb"
CHAR_NOTIFY = "0000ff03-0000-1000-8000-00805f9b34fb" # または ff01
# --- Commands ---
ESC = b"\x1B"
GS = b"\x1D"
US = b"\x1F"
INIT = ESC + b"@"
BM = GS + b"v" + b"0" + b"\x00"
# プリンタの初期化・濃度設定
print("Initializing printer...")
await client.write_gatt_char(CHAR_WRITE, INIT, response=True)
await client.write_gatt_char(CHAR_WRITE, US + b"\x11\x37" + b"\x96", response=True)
await client.write_gatt_char(CHAR_WRITE, US + b"\x11\x02" + b"\x03", response=True)
# --- Image Processing ---
print(f"Loading image: {fn}")
image = Image.open(fn)
if image.width > image.height:
image = image.transpose(Image.ROTATE_90)
width = 576 # M02S default
IMAGE_WIDTH_BITS = width
IMAGE_WIDTH_BYTES = IMAGE_WIDTH_BITS // 8
image = image.resize(size=(IMAGE_WIDTH_BITS, int(image.height * IMAGE_WIDTH_BITS / image.width)))
image = image.convert(mode="1")
# --- Header ---
await client.write_gatt_char(CHAR_WRITE, BM, response=True)
await client.write_gatt_char(CHAR_WRITE, IMAGE_WIDTH_BYTES.to_bytes(2, byteorder="little"), response=True)
await client.write_gatt_char(CHAR_WRITE, image.height.to_bytes(2, byteorder="little"), response=True)
# --- Formatting Data ---
print("Formatting data...")
image_bytes = b""
for iy in range(image.height):
for ix in range(int(image.width / 8)):
byte = 0
for bit in range(8):
if image.getpixel((ix * 8 + bit, iy)) == 0:
byte |= 1 << (7 - bit)
image_bytes += byte.to_bytes(1, "little")
# --- Data Sending (Chunking) ---
print(f"Data sending ({len(image_bytes)} bytes)...")
# BLEは一度に大量のデータを送れないため、chunk_sizeごとに分割して送信
for i in range(0, len(image_bytes), chunk_size):
chunk = image_bytes[i : i + chunk_size]
await client.write_gatt_char(CHAR_WRITE, chunk, response=True)
# 最後に用紙送り
await client.write_gatt_char(CHAR_WRITE, ESC + b"d" + b"\x03", response=True)
print("Data send complete.")
print("Done. Disconnecting.")
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python3 script.py <image_path>")
sys.exit(1)
fn = sys.argv[1]
address = "B3A6BB31-E696-6BCD-B97D-E6E7CBF7BE2A" # あなたのMacでのアドレス
# 最近のPythonにおける標準的なasyncioの実行方法
try:
asyncio.run(run(address, fn))
except KeyboardInterrupt:
print("Interrupted by user.")
まとめ
パケット長の短さやプリンタバッファの小ささから、blocking mode でデータを送らねばならないが、Classical Bluetooth のシリアル出力にくらべてどうしてもデータ転送が遅くなって、プリンタの熱ヘッダの温度ムラが出やすくなってしまう。
気になっていたところを AI に解決してもらったが、Bluetooth LE 規格上の本質的な問題であるということが分かって、心のわだかまりは成仏できた。
