ブロックを積みながら(21) MakeCode、Compilation failed

Joseph Halfmoon

前回、ラズパイPythonからBluetooth UARTサービスを使って「コマンド」をmicro:bitに送り込み、それに反応させることができました。今回はmicro:bitから処理結果をラズパイに返信するとともに、各種コマンド大拡充、と目論んだのですが障害あり。MakeCodeのコンパイル時エラー発生。

※「ブロックを積みながら」投稿順 index はこちら

まずはコンパイル時エラーが発生せず、正常に動作する版の micro:bit のJavaScriptコードと ラズパイ上のPython3 コードを末尾に掲げておきます。

今回は、久しぶりにMakeCodeエディタで「ブロックを積んで」みたのでそちらは以下に掲げます。本当は、BLE UARTからのコマンドで各種機能を制御できるようにブロックを大量に積んでみたのです。すると Compilation failed 発生してしまいました。以下は、failが発生しないところまでブロックを毟りとったコードです。動作はしますが、機能は限定。

動作OK版 micro:bitブロックコード

Block p0on startブロックでは、bluetooth uart serviceのみ起動しています。foreverブロックでは、100ミリ秒毎にLEDマトリックスの左上隅のLED1個を「生きているぞ」ということを示すために点滅させています。

処理の本体は、bluetooth on data received ブロックです。改行までの「1行」を待ち受けて、文字 “:” で文字列分割し、commandという名の配列に格納しています。その後 ackVという変数に0を代入していますが、これは処理された(非0)、処理されなかった(0)を保持するための変数です。

Block P1command配列は2要素からなることを想定しており、そうでない場合は、BLE UARTを通じて “ERROR:CMD”を返します。command配列の1番目要素[0]が ”music” ならば、メロディを奏でるための関数 musicStartにcommand配列の2番目要素を引数として呼び出します。musicStartが返してくる値を checkResult関数で確認し、正常に実行されれば、”ACK:MUSIC”, そうでなければ “NAK:MUSIC”をBLE UART経由で返します。ここまでの処理の中でコマンド定義されたコマンド(今回は music 1種しかありません)の処理を通過しておれば、その結果にかかわらず ackV には非0の値が入るようになっています。初期値0のままであれば、対応するコマンドが無かったということなので BLE UART経由で “NAK:CMD” を返します。

Block p2checkResult関数は、実際にコマンドを処理する関数の戻り値を判定してBLE UART経由のお返事をセットするための補助的な関数です。

Block P3

引き続く musicStart関数は、引数に与えられた文字列と一致する場合に、指定のメロディを奏でます。一致するものが無ければ0を、そうでなければ1を返します。

実際にRaspberry Pi  3 model B+上のpython3から後に掲げたコードを走らせたといの様子は以下に。micro:bit がメロディを奏でると同時に、返信文字列 “ACK:MUSIC” が到来し、そのハンドル 44 も捕捉されています。

$ python3 microbitBLEutil03.py --N 1 --SEND music:blues
microbitBLEutil v0.3
CONNECTED: F7:FB:7A:03:96:AA
SEND: b'music:blues\n'
Wait for Notification
hdl=44, dat=b'ACK:MUSIC'
DISCONNECTED.
Compilation failed

さて上記のコードは動作したので、もう一つメロディを追加しようと思いました。末尾のJavaScript表現では、//でコメントアウトされている2行に相当します。この部分を追加して「ビルド」してみると、アイキャッチ画像に掲げたようなポップアップが表示されます。あれっと思ってみるのです。エラー箇所が一瞬見えた気がするのですが、直ぐに消えてしまいます。

MakeCode上のエラーの確認方法

まずもって、MakeCode上の「ビルド時」エラーの確認方法、よくわかっていないな、ということに気づきました。まず、JavaScript表現に切り替えたときに現れるExplorerから、built、output.txt とたどって確認できる output.txt ファイルが基本となるようです。こんな感じ。

EverythingSeemdsFine

“Everythings seems fine!” だと!

役に立たん!しかし、よく見るとビルド後、Explore画面上に一瞬エラーが表示されるようなのです。しかし直ぐに消えます。その理由は

シミュレータが動作している

ことでした。MakeCode画面の左側で実機のように動き続けているアレです。HEXコードのビルドには失敗するくせに、シミュレータは動作してしまうみたいなのです。常時動作しているシミュレータのために処理した結果、直前のエラーが消えてしまっているみたい。そこでシミュレータの動作を止める ■ のアイコンを押しました。

シミュレータ止めるとようやく、エラーメッセージを捕捉することができました。こんな感じ。ExpError

実際のエラーメッセージはこちら

funcErrorまた、シミュレータを止めてしまえば、ブロック表現でも問題箇所には!マークがついて理由を読むことができました。こんな感じ。でも意味は何じゃらほい。

blockError

まあね、今回

ビルド時のエラーメッセージを読む方法は分かった

でもね、まったく解決にはなってません。該当関数をばらしてみたり、いろいろ変更してみたりしたのだけれど、どうもあるところを超える簡単なブロックを1個追加するだけでエラーになります。ネットを検索すると同じようなエラーで文句垂れている人が結構いるみたい。再現性ないらしい。また今度だな~。

ブロックを積みながら(20) micro:bit、BLE UartへPython書き出し へ戻る

ブロックを積みながら(22) MakeCode、いつの間にmicro:bit v2対応? へ進む

micro:bit側からのBLE UART送信に対応したPython3スクリプト
#! /usr/bin/python3
import argparse
import bluepy
import sys
import time

versionSTR = "microbitBLEutil v0.3"

class microbitBLE:

    def __init__(self, adr):
        self.devadr = adr
        self.pobj = None
        self.uartOBJ = None

    def conPobj(self):
        try:
            self.pobj = bluepy.btle.Peripheral()
            self.pobj.connect(self.devadr, bluepy.btle.ADDR_TYPE_RANDOM)
        except:
            print("ERROR: connect.")
            return False
        return True

    def listCharacteristics(self):
        chrLis = self.pobj.getCharacteristics()
        for item in chrLis:
            print(" UUID: {0} Handle: 0x{1:04x} Prop: {2}".format(item.uuid, item.getHandle(), item.propertiesToString()))

    def getDeviceInfo(self):
        work = dict()
        work['DeviceName'] = str(self.pobj.readCharacteristic(0x0003), 'utf-8')
        modelNumCH = self.pobj.getCharacteristics(uuid="00002a24-0000-1000-8000-00805f9b34fb")[0]
        serialNumCH = self.pobj.getCharacteristics(uuid="00002a25-0000-1000-8000-00805f9b34fb")[0]
        firmRevCH = self.pobj.getCharacteristics(uuid="00002a26-0000-1000-8000-00805f9b34fb")[0]
        work['ModelNumber'] = str(self.pobj.readCharacteristic(modelNumCH.getHandle()), 'utf-8')
        work['SerialNumber'] = str(self.pobj.readCharacteristic(serialNumCH.getHandle()), 'utf-8')
        work['FirmwareRevision'] = str(self.pobj.readCharacteristic(firmRevCH.getHandle()), 'utf-8')
        return work

    def getServiceInfo(self):
        return self.pobj.getServices()

    def getDescInfo(self):
        return self.pobj.getDescriptors()

    def setupUart(self):
        chTX = self.pobj.getCharacteristics(uuid="6E400002-B5A3-F393-E0A9-E50E24DCCA9E")[0]
        ch_cccd=chTX.getDescriptors(forUUID=0x2902)[0]
        ch_cccd.write(b"\x03\x00", False)
        chRX = self.pobj.getCharacteristics(uuid="6E400003-B5A3-F393-E0A9-E50E24DCCA9E")[0]
        self.uartOBJ = bleDelegate(chTX, chRX)
        self.pobj.withDelegate( self.uartOBJ )

    def sendStrUart(self, arg):
        if self.uartOBJ is None:
            self.setupUart()
        barg = bytes(arg + '\n', 'utf-8')
        if len(barg) > 20:
            return None
        self.pobj.writeCharacteristic(0x002a, barg, False)
        return barg

    def uartInfoTX(self):
        uartTX = self.pobj.getCharacteristics(uuid="6E400002-B5A3-F393-E0A9-E50E24DCCA9E")[0]
        print("TX Handle: 0x{0:04x} Prop: {1}".format(uartTX.getHandle(), uartTX.propertiesToString()))
        descTX = uartTX.getDescriptors()
        for item in descTX:
            print(str(item))

    def uartInfoRX(self):
        uartRX = self.pobj.getCharacteristics(uuid="6E400003-B5A3-F393-E0A9-E50E24DCCA9E")[0]
        print("RX Handle: 0x{0:04x} Prop: {1}".format(uartRX.getHandle(), uartRX.propertiesToString()))
        descRX = uartRX.getDescriptors()
        for item in descRX:
            print(str(item))

    def wfn(self, tout):
        return self.pobj.waitForNotifications(tout)

    def close(self):
        self.pobj.disconnect()

class bleDelegate(bluepy.btle.DefaultDelegate):
    def __init__(self, TXD, RXD):
        bluepy.btle.DefaultDelegate.__init__(self)
        self.TX = TXD
        self.RX = RXD

    def handleNotification(self, hdl, dat):
        print("hdl={0}, dat={1}".format(hdl, dat))

def tryIntParse(argstr):
    try:
        return int(argstr)
    except ValueError:
        return None

def main():
    parser = argparse.ArgumentParser(description='microbitBLEutil.')
    parser.add_argument('--ADR', nargs=1, help='BLE address.')
    parser.add_argument('--N', nargs=1, help='0) Gv1.5 1) Gv2 2) Rv1.5')
    parser.add_argument('--SEND', nargs=1, help='Send string via UART service.')
    parser.add_argument('-uartinfo', dest='uartinfo', help='uart Info.', action='store_true', default=False)
    parser.add_argument('-info', dest='info', help='device Info.', action='store_true', default=False)
    parser.add_argument('-service', dest='service', help='available service list.', action='store_true', default=False)
    parser.add_argument('-desc', dest='descriptor', help='available descriptor list.', action='store_true', default=False)
    parser.add_argument('-uuid', dest='uuid', help='list all UUIDs.', action='store_true', default=False)
    parser.add_argument('-V', dest='VERSION', help='Show Version, then exit', action='store_true', default=False)
    args = parser.parse_args()

    print(versionSTR)
    if args.VERSION:
        sys.exit(0)

    devLis = ["F5:D1:5F:02:68:25", "F7:FB:7A:03:96:AA", "F8:5C:FE:9B:B0:86"]
    devadr =  devLis[0]
    if args.ADR is not None:
        devadr = args.ADR[0]

    if args.N is not None:
        idx = tryIntParse(args.N[0])
        if (idx is not None) and (idx < len(devLis)):
            devadr = devLis[idx]

    mb = microbitBLE(devadr)

    if mb.conPobj():
        print("CONNECTED: ", devadr)
    else:
        sys.exit(1)

    if args.uuid:
        mb.listCharacteristics()

    if args.info:
        for key, value in mb.getDeviceInfo().items():
            print(key, "=", value)

    if args.service:
        for srv in mb.getServiceInfo():
            print(str(srv))

    if args.descriptor:
        for desc in mb.getDescInfo():
            print(str(desc))

    if args.uartinfo:
        mb.uartInfoTX()
        mb.uartInfoRX()

    if args.SEND is not None:
        print("SEND:", mb.sendStrUart(args.SEND[0]))

    print("Wait for Notification")
    if not mb.wfn(10):
        print("NO NOTIFICATION within 10 seconds.")
    mb.close()
    print("DISCONNECTED.")

if __name__ == "__main__":
    main()
JavaScript形態 micro:bit用コード
function checkResult (result: number, okText: string, ngText: string) {
    if (result == 1) {
        bluetooth.uartWriteString(okText)
    } else {
        bluetooth.uartWriteString(ngText)
    }
    ackV = 1
}
bluetooth.onUartDataReceived(serial.delimiters(Delimiters.NewLine), function () {
    command = bluetooth.uartReadUntil(serial.delimiters(Delimiters.NewLine)).split(":")
    ackV = 0
    if (command.length != 2) {
        bluetooth.uartWriteString("ERROR:CMD")
    }
    if (command[0] == "music") {
        checkResult(musicStart(command[1]), "ACK:MUSIC", "NAK:MUSIC")
    }
    if (ackV == 0) {
        bluetooth.uartWriteString("NAK:CMD")
    }
})
function musicStart (arg: string) {
    if (arg == "blues") {
        music.startMelody(music.builtInMelody(Melodies.Blues), MelodyOptions.Once)
    } else if (arg == "funk") {
        music.startMelody(music.builtInMelody(Melodies.Funk), MelodyOptions.Once)
//    } else if (arg == "dadadum") {
//        music.startMelody(music.builtInMelody(Melodies.Dadadadum), MelodyOptions.Once)
    } else {
        return 0
    }
    return 1
}
let command: string[] = []
let ackV = 0
bluetooth.startUartService()
basic.forever(function () {
    led.toggle(0, 0)
    basic.pause(100)
})