29
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Pythonからサーマルプリンタ「PAPERANG」の印刷できました(Windows10、Python3.6)

Last updated at Posted at 2020-02-11

お待たせしました!
Pythonからサーマルプリンタ「PAPERANG」の印刷できました。

サンプルコード

ここからダウンロードしてください

環境構築

$ conda create -n paperang python=3.6
$ conda activate paperang
$ cd miaomiaoji-tool
$ pip install PyBluez-win10
$ pip install opencv-python

実行

$ python message_process.py

解説

ここからフォークしたものを変更してます。

message_process.pyを実行してみるとfilter関数でエラーが出ます。47行目と64行目のfilter関数をlistでくくります。Python3におけるmap/filterの使い方

valid_devices = list(filter(lambda d: len(d) == 2 and d[1] in valid_names, nearby_devices))
・・・
valid_service = list(filter(
    lambda s: 'protocol' in s and 'name' in s and s['protocol'] == 'RFCOMM' and s['name'] == 'SerialPort',
    service_matches
))

pybluezだと63行目のfind_serviceのところでOSErrorが出るため、PyBluez-win10を用いています。※OSError when program try to use functions from bluetooth._msbt #279

service_matches = find_service(uuid=self.uuid, address=self.address)

サービス名が見つからないエラーが出るので、service_matchesをprintして表示してみましょう。

[{'host': '00:15:83:B7:11:AF', 'name': b'SerialPort', 'description': '', 'port': 1, 'protocol': 'RFCOMM', 'rawrecord': b'5J\t\x00\x00\n\x00\x01\x00\x0f\t\x00\x015\x03\x19\x11\x01\t\x00\x045\x0c5\x03\x19\x01\x005\x05\x19\x00\x03\x08\x01\t\x00\x055\x03\x19\x10\x02\t\x00\x08\x08\xff\t\x00\t5\x085\x06\x19\x11\x01\t\x01\x02\t\x01\x00%\nSerialPort', 'service-classes': [b'1101'], 'profiles': [(b'1101', 258)], 'provider': None, 'service-id': None, 'handle': 65551}, {'host': '00:15:83:B7:11:AF', 'name': b'WeChat', 'description': '', 'port': 8, 'protocol': 'RFCOMM', 'rawrecord': b'5T\t\x00\x00\n\x00\x01\x00\x0e\t\x00\x015\x11\x1c\xe5\xb1R\xedkF\t\xe9Fxf^\x9a\x97,\xbc\t\x00\x045\x0c5\x03\x19\x01\x005\x05\x19\x00\x03\x08\x08\t\x00\x055\x03\x19\x10\x02\t\x00\x08\x08\xff\t\x00\t5\x085\x06\x19\x11\x01\t\x01\x02\t\x01\x00%\x06WeChat', 'service-classes': ['E5B152ED-6B46-09E9-4678-665E9A972CBC'], 'profiles': [(b'1101', 258)], 'provider': None, 'service-id': None, 'handle': 65550}]

'name': b'SerialPort'になっているため、65行目の'SerialPort'にbを付けます。

lambda s: 'protocol' in s and 'name' in s and s['protocol'] == 'RFCOMM' and s['name'] == b'SerialPort',

あとservice_matchesをprintしたときに、下記のstruct.packのエラーが出ます。

 File "message_process.py", line 88, in packPerBytes
    result += struct.pack('<i', self.crc32(bytes))
struct.error: argument out of range

88行目の <i を <I に変更しましょう。struct — Interpret bytes as packed binary data

result += struct.pack('<I', self.crc32(bytes))

次にencodeのエラーが出るため、

File "message_process.py", line 110, in recv
    logging.info("Recv: " + raw_msg.encode('hex'))
AttributeError: 'bytes' object has no attribute 'encode'

110行目と122行目の.encode('hex')を.hex()に変えます。How do I fix AttributeError: 'bytes' object has no attribute 'encode'?

logging.info("Recv: " + raw_msg.hex())
・・・
, self.payload_length, self.payload.hex()

image_process.pyでmap関数のエラーが出るため、32行目のmap関数をlistでくくります。

File "\miaomiaoji-tool\image_process.py", line 20, in frombits
    for b in range(len(bits) / 8):
TypeError: object of type 'map' has no len()

また、20行目のfor文のrangeの範囲がfloatになっているので、intにキャストします。

for b in range(int(len(bits) / 8)):

153行目のstruct.packでエラーが出るので、コメントアウトしてOKです。

# msg = struct.pack("<%dc" % len(binary_img, *binary_img)

300行のラインを印刷するには、バイナリにする必要があります。sendImageToBtの中身はmsgにそのままバイナリの文字列を入れています。

def sendImageToBt(self, binary_img):
    self.sendPaperTypeToBt()
    # msg = struct.pack("<%dc" % len(binary_img, *binary_img)
    msg = binary_img
    self.sendToBt(msg, BtCommandByte.PRT_PRINT_DATA, need_reply=False)
    self.sendFeedLineToBt(self.padding_line)

# Print a pure black image with 300 lines
img = b'\xff' * 48 * 300
mmj.sendImageToBt(img)

印刷できました。
86242649_182835276314024_4717528697094012928_n.jpg

実は、self.sendToBtには各lineごとに送信する必要があります。

def sendImageToBt(self, binary_img):
    self.sendPaperTypeToBt()
    # msg = struct.pack("<%dc" % len(binary_img, *binary_img)
    msgs = [binary_img[x: x+192] for x in range(0, len(binary_img), 192)] # 4*48
    for msg in msgs:
        self.sendToBt(msg, BtCommandByte.PRT_PRINT_DATA, need_reply=False)
    self.sendFeedLineToBt(self.padding_line)

これが本当の300行のラインです。sendBinaryToBtという関数名に変えました。

86189532_333920134236910_8458868322023243776_n.jpg

\xffが黒で、\x00が白になります。\xffを二進数にするとわかりやすいのですが、\xffは0b11111111となり8本の黒い縦のラインが引かれることになります。なので、8ビットで画像の8ピクセル分を表現することになります。横のラインの幅は'\xff'が48個分までです。つまり、画像幅は8×48=384ピクセルになります。

画像をリサイズ、二値化し、[0,1]の値に変換、8ピクセルずつ16進数に変換し、各行ごとにプリントします。

def sendImageToBt(self, binary_img):
    self.sendPaperTypeToBt()
    height, width = binary_img.shape[:]
    for line in range(height):
        bits = [0 if x > 0 else 1 for x in binary_img[line]]
        bits = [bits[x:x+8] for x in range(0, len(bits), 8)]
        msg = ''
        for bit in bits:
            bin = '0b'+''.join(str(x) for x in bit)
            msg += '{:02x}'.format(int(bin, 0))
        msg = bytes.fromhex(msg)
        self.sendToBt(msg, BtCommandByte.PRT_PRINT_DATA, need_reply=False)
    self.sendFeedLineToBt(self.padding_line)

・・・
# Print an existing image(need opencv):
img = cv2.imread('kumamcn.png', 0)
ret, binary_img = cv2.threshold(img,127,255,cv2.THRESH_BINARY)
height, width = binary_img.shape[:]
binary_img = cv2.resize(binary_img, (384, int(height*384.0/width)), cv2.INTER_AREA)
mmj.sendImageToBt(binary_img)

完成です。これで任意の画像を印刷できます!
86259996_614941075716724_5170025822672650240_n.jpg

この記事が役に立ったらガチ本に生牡蠣をおごってください。

29
24
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
29
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?