Help us understand the problem. What is going on with this article?

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

お待たせしました!
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

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

SatoshiGachiFujimoto
高専で制御を学び、大学でセンシングを学び、次は脳みそ。SLAMに関連したロボット/ドローンやMRの研究開発をしています。
https://www.gachimoto.com
knowledgecommunication-inc
クラウドインテグレーター、AI・VRの分野で様々なソリューションを展開
http://www.knowledgecommunication.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした