前回 MQTT Subscribe ができたので、MicroPythonで動いているM5ATOM LiteとNode-REDを動かしているRaspberry Pi 3機で上り下りの通信がOKとなりました。今回は、通信内容をJSON化して後で拡張しやすいようにしてみたいと思います。まずはPublish側から。使用するMicroPythonモジュールはujsonです。
※「MicroPython的午睡」投稿順 Indexはこちら
(末尾に実験に使用したMicroPythonスクリプト全文を掲げました。)
M5ATOM Lite機側のスクリプトの追加部分
JSON形式は、各種のデータ交換、蓄積などに使われています。当然ながら MicroPythonにもこれに対応するモジュールが存在します。CPythonのjsonモジュールに相当するMicroPython版のモジュール名は ujson です(頭の u は必須だと思います。)公式ドキュメントへのリンクは以下です。
ujson – JSON encoding and decoding
上記のリンクは、v1.16版に向いています。ページの左下の方でバージョン指定できるようになっているので他の版のMicroPythonにも切り替え可能です。なお、現状、手元のM5ATOM Liteに書き込んであるESP32 “generic” port 版はv1.16です。
今回も前回スクリプトのチョイ直しなので、以下に追加部分をまとめてみました(全文は末尾に。)
~略~ import ujson ~略~ def makeOBJ(num): workDic = dict() workDic['A'] = num workDic['B'] = num * 0.123 return ujson.dumps(workDic) def main(): ~略~ loopCounter = 0 while( loopCounter < 360 ): loopCounter += 1 ~略~ jsonSTR = makeOBJ(loopCounter) client.publish("ATOMLite/Json", jsonSTR) ~略~
やっていることは簡単です。
-
- ujsonモジュールをインポートする
- json化するオブジェクトは、MicroPythonの辞書オブジェクトにいたしました。Python辞書のキーがJavaScript(Node-RED)側に渡った後、オブジェクトのプロパティとして見えるので処理しやすいかと思います。とりあえず、実験用にキー”A”に整数、キー”B”に浮動小数の値を「テキトーに」入れてあります。
- json文字列化したオブジェクトをトピック “ATOMLite/Json”に向けてMQTT Publish
Node-RED側の設定
Node-RED側のATOMLiteタブに、先頭のアイキャッチ画像のようなフローを追加いたしました。やっていることはこれまた簡単。
-
- mqtt inノードで、トピック ATOMLite/Json に到来するオブジェクトを受け取って次ノードに流す。
- 次のfunctionノードで、取り出すべきプロパティ(Python辞書オブジェクトのキー)を調べ、該当のプロパティが有ったら対応する出力用のダッシュボードにその内容を送る(本来なら何か具体処理をするのですが、何もしません。)
- ダッシュボードではtextノードで送られてきたデータを表示
まずは先頭の mqtt in ノードの設定がこちら。
出力は、ドロップダウン・メニューから指定できるので、念のためJSONオブジェクト指定としています。多分自動判定のままでもJSONと判定してくれるのだと思います(Node-REDのバージョンによる、多分。)送られてきたJSONオブジェクトがmsg.payloadに搭載されて送り出されます。
次のfunctionノードは、出力を2つとしました。JSONのプロパティ(Python辞書のキー毎に、別々のノードに接続したかったためです。functionノードの出力を分ける件はこちら。
今回、JSONから取り出したデータは何もせず、Node-REDダッシュボード上に表示するだけです。2個の表示用textを用意し、プロパティ(Python辞書キー)”A”と、プロパティ(Python辞書キー)”B”の値をそのまま表示しています。
前回の設定でMicroPython側では10秒毎にStatusを送信しています。今回、Status送信に引き続きJSONオブジェクトを送るように追加をしました。そこで、JSON-A、JSON-Bともに約10秒毎に値が更新されます。今回送信してくるのはテスト用の単なるカウンタ起源の値ですが。
これで、上り方向がJSON化できたので、M5ATOMLite側から何か計測データを送る、いった場合、辞書オプジェクトに追加するだけで簡単に送り出せるじゃないかと思います。次回は下り方向だな。設定値や指令をホスト側から送ってくる想定ですかね。
MicroPython的午睡(43) MQTTでSubscribe、M5ATOM Lite へ戻る
MicroPython的午睡(45) MQTTでJSON-OBJ受信、M5ATOM Lite へ進む
実験に使用したMicroPythonスクリプト
動作確認した処理系のバージョンは以下です。ESP32用の”generic” ポートをM5ATOM Liteに書き込んで使用。
MicroPython v1.16 on 2021-06-23; ESP32 module with ESP32
import network, ntptime, machine, time, sys import ujson import umqtt.simple global wlan, mqttBroker, client ssid = "Your SSID" password = "Your Password" ntpAdr = "Your NTP server" brokerAdr = "Your MQTT broker address" brokerPort = 1883 tz = 9 def callbackColor(topic, msg): topicS = topic.decode('utf-8') msgS = msg.decode('utf-8') print(topicS, msgS) if topicS=="ATOMLite/Color": print("Message: ", msgS) def connectMqttBroker(bAdr, bPort, clientId): global client try: client = umqtt.simple.MQTTClient(clientId, bAdr, port=bPort) client.set_callback(callbackColor) client.connect() client.subscribe("ATOMLite/Color") except: return False return True def setupSubscriber(): global client def do_connect(ssid, pw, timeout, opt=True): global wlan wlan = network.WLAN(network.STA_IF) wlan.active(True) if not wlan.isconnected(): print('connecting to network...') wlan.connect(ssid,pw) while (not wlan.isconnected()) and (timeout > 0): start = time.ticks_ms() while time.ticks_diff(time.ticks_ms(), start) < 1000: pass timeout -= 1 if opt: print('timeout: ', timeout) print('network config: ', wlan.ifconfig()) return wlan.isconnected() def currentTIME(timZone): t0 = machine.RTC().datetime() return "{0}/{1:02d}/{2:02d} {3:02d}:{4:02d}:{5:02d}".format(t0[0],t0[1],t0[2],t0[4]+timZone,t0[5],t0[6]) def initRTC(timZone, opt=True): ntptime.host = ntpAdr ntptime.settime() if opt: print(currentTIME(timZone)) def makeOBJ(num): workDic = dict() workDic['A'] = num workDic['B'] = num * 0.123 return ujson.dumps(workDic) def main(): if not do_connect(ssid, password, 10): print("ERROR: WiFi connection.") sys.exit(1) initRTC(tz) if not connectMqttBroker(brokerAdr, brokerPort, "ATOMLite"): print("ERROR: MQTT Broker connection.") sys.exit(1) #loop loopCounter = 0 while( loopCounter < 360 ): loopCounter += 1 client.publish("ATOMLite/Status", "FROM ATOMLite: " + currentTIME(tz)) jsonSTR = makeOBJ(loopCounter) client.publish("ATOMLite/Json", jsonSTR) client.check_msg() time.sleep(10) client.publish("ATOMLite/Status", "ATOMLite: out of service.") client.disconnect() if __name__ == "__main__": main()