0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Mac から bleak を利用した Bluetooth プリンタへの出力

0
Posted at

前置き

以前 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.

neko.jpg

プログラム

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 規格上の本質的な問題であるということが分かって、心のわだかまりは成仏できた。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?