MicroPython的午睡(3) bytesからstr変換につまづく

Joseph Halfmoon

Raspberry Pi Picoが買えなかった「衝撃」が後を引いております。が、気をとりなおして? MicroPython。しかしこちらでも「衝撃」が。普段、普通にできていることができないとダメージが大きい。なんでもコロナのせい、にしたくなりますが、MicroPythonとPython3の「微妙な」違い。知っていればなんてこともないんだ、なんてことも。。。

※「MicroPython的午睡」投稿順 Indexはこちら

このところ作業中の「システム」、2台のmicro:bitとRaspberry Piで構成しております。2台のmicro:bitの制御ソフトおよびラズパイ上で走っているmicro:bitとのインタフェースソフトは全てPythonで記述しています。緑色の方のmicro:bitが電池で動く「先っぽ」で、センサの情報を無線で送ってまいります。それを赤色の方のmicro:bitが受信してuart経由でラズパイ上のPythonスクリプトに報告する(その後MQTT経由でNode-RED)仕組み。「センサ・ノード」から「サーバ」方向への「上り」は動作しているので、今回は「下り」を実装することにいたしました。micro:bitの機能を「活かし」(お手軽だから)てラズパイの指示のもと音楽を鳴らそうという目論見です。最近のmicro:bit v2であればスピーカも搭載されているようですが、そうでないので外付けです(アイキャッチ画像ご参照ください。)

問題はね、無線でもない追加のスピーカでもない、micro:bit赤とラズパイ間のUartでおきました。ラズパイからUart経由でmicro:bitにコマンド文字列を送信するとエラー発生、こんな感じ。

uPython, no decode() Uart経由で通信した結果は、bytes型の「文字列」オブジェクトになります。ちょっと見普通の文字列に見えるけれど頭にbがついている奴。まあUARTは8ビット(または7ビット)単位での通信なのでRaspbian OS上で走っているPython3でもMicroPythonでもbytes型であることは変わりないです。そのままだと普通の文字列str型と混在させて扱いずらいので、Python3では、

decode(‘utf-8’)

メソッドを使って、bytes型からstr型に変換して使っていました。なおどちらもオブジェクトとしてはイミュータブルであります。PC上のPython3のREPLでの動作のキャプチャですが、Python3でのbytes型のdecode()はこんな感じ。

decode() runs on python3しかし、同じことを micro:bit上のMicroPythonのREPLで実行してみると、制御のスクリプト同様エラーとなります。decode() causes error in uPython

普段やっていることができないとパニックになります。decode()使えなかったらどうしたらよいの?一瞬放心状態(大袈裟か。)

結構時間を使って試行錯誤、とりあえず変換する方法その1を見つけたのがこちら。Yet another bytes str conversion

上の青い方がPC上のPython3、下の黒いバックがMicroPython。

Yet Anather is OKしかし、上の方法はかなりマドロッコしい。一端リスト(こちらはミュータブルなオブジェクト)作ってそこからstr型の文字列に戻すなど負荷きっと大。それに1byte毎にchr()にかけているのが心配。もっとよい方法ないものか。

ところが別件(UARTのタイムアウト時間)を探していたら「ソリューション」をヒョッコリ見つけましたぜ。

UART

まずは探していたタイムアウトの件を引用させていただくと、

すべての UART 読込みのタイムアウトはボーレートに依存していて、Pythonからは変更できません。タイムアウト値は次の式でミリ秒単位で決まります:

書いてあった式にボーレートを代入して計算してみると1.1m秒くらい。タイムアウト早すぎ、どうしようもないけど。ま、この件は納得できたところで引用場所のふと上をみると bytesからstrへの変換らしきものがかかれています。(ただ残念なことに、例題で使われている変数名が間違っているような気がするですが。)

とりあえず、PC上のPython3で実行。変換OK.

Str method with UTF-8MicroPythonでもやってみます。

str() method works on uPythonあれま、出来てしまった。簡単。decode()使えないとパニックっていたのがウソのよう。一番最初にstrにbytes型を食わしてみていたのだけれど、その時は第2引数に’UTF-8’など与えていなかったです。分かってみたら超簡単、拍子抜け。

そういう分けで、

結局のmicro:bit REDの本日のコード
import radio
from microbit import *

radio_on = False

def receiveCommand():
    temp = uart.readline()
    if temp is not None:
        temp = str(temp, 'UTF-8')
    return temp

def receivePacket():
    dataStr = None
    try:
        dataStr = radio.receive()
    except ValueError:
        display.show(Image.SAD)
        dataStr = None
    if dataStr is not None:
        display.show(Image.HEART)
        uart.write(dataStr)

def sendPacket(payload):
    radio.send(payload)

while True:
    if button_a.was_pressed():
        radio.on()
        radio_on = True
    if button_b.was_pressed():
        radio.off()
        radio_on = False
    if radio_on:
        comStr = receiveCommand()
        if comStr is not None:
            uart.write(">{0}<\r\n".format(comStr))
            if comStr == "1":
                sendPacket("1:MUSIC:1")
            elif comStr == "2":
                sendPacket("1:MUSIC:2")
            elif comStr == "3":
                sendPacket("1:MUSIC:3")
            else:
                sendPacket(comStr)
            uart.write("sendPacket\r\n")
        receivePacket()
    else:
        display.show(Image.TRIANGLE)
    sleep(100)
    display.clear()
    sleep(500)
もう一方、micro:bit GREENのコード
import radio
import utime
import music

from microbit import *

radio.on()
packetNum = 0
tempExt = -999
tempInt = -999
uartLog = False
interval = 6

def measureTemperature():
    global tempExt, tempInt
    tempInt = temperature()  # microbit module, internal temp sensor.
    tempExt = pin2.read_analog()

def sendPacket(opt):
    global packetNum, tempExt, tempInt
    packet = ":".join([str(packetNum), str(tempExt), str(tempInt)]) + "\r\n"
    radio.send(packet)
    if opt:
        uart.write(packet)

def receivePacket():
    dataStr = None
    try:
        dataStr = radio.receive()
    except ValueError:
        display.show(Image.SAD)
        dataStr = None
        sleep(200)
        display.clear()
    return dataStr

def decodeCommand(comStr):
    keyStr = None
    valStr = None
    if comStr is not None:
        com = comStr.split(":")
        if len(com) == 3:
            _recNum = com[0]
            keyStr = com[1]
            valStr = com[2]
    return (keyStr, valStr)

def musicCommand(nMusic):
    if type(nMusic) is int:
        if nMusic == 1:
            music.play(music.RINGTONE)
        elif nMusic == 2:
            music.play(music.JUMP_UP)
        elif nMusic == 3:
            music.play(music.JUMP_DOWN)        

def tryIntParse(intStr):
    try:
        result = int(intStr)
    except ValueError:
        result = None
    return result

while True:
    comTPL = decodeCommand(receivePacket())
    if comTPL[0] == "MUSIC":
        musicCommand(tryIntParse(comTPL[1]))
    if button_a.was_pressed():
        uartLog = True
        music.play(music.NYAN)
    if button_b.was_pressed():
        uartLog = False
        music.play(music.PYTHON)
    measureTemperature()
    sendPacket(uartLog)
    display.scroll(str(packetNum))
    packetNum += 1
    utime.sleep(interval)

これにてRED機からの「無線コマンド」に反応し、GREEN機のスピーカが「鳴る」ようになりました。結構聞こえやすい音だ。

MicroPython的午睡(2) ラズパイ上のMuエディタつづき

MicroPython的午睡(4) micro:bitとWio Terminalの比較 へ進む