MicroPython的午睡(44) MQTTでJSON-OBJ送信、M5ATOMLite

Joseph Halfmoon

前回 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)
~略~

やっていることは簡単です。

    1. ujsonモジュールをインポートする
    2. json化するオブジェクトは、MicroPythonの辞書オブジェクトにいたしました。Python辞書のキーがJavaScript(Node-RED)側に渡った後、オブジェクトのプロパティとして見えるので処理しやすいかと思います。とりあえず、実験用にキー”A”に整数、キー”B”に浮動小数の値を「テキトーに」入れてあります。
    3. json文字列化したオブジェクトをトピック “ATOMLite/Json”に向けてMQTT Publish
Node-RED側の設定

Node-RED側のATOMLiteタブに、先頭のアイキャッチ画像のようなフローを追加いたしました。やっていることはこれまた簡単。

    1. mqtt inノードで、トピック ATOMLite/Json に到来するオブジェクトを受け取って次ノードに流す。
    2. 次のfunctionノードで、取り出すべきプロパティ(Python辞書オブジェクトのキー)を調べ、該当のプロパティが有ったら対応する出力用のダッシュボードにその内容を送る(本来なら何か具体処理をするのですが、何もしません。)
    3. ダッシュボードではtextノードで送られてきたデータを表示

まずは先頭の mqtt in ノードの設定がこちら。

mqttinJSON出力は、ドロップダウン・メニューから指定できるので、念のためJSONオブジェクト指定としています。多分自動判定のままでもJSONと判定してくれるのだと思います(Node-REDのバージョンによる、多分。)送られてきたJSONオブジェクトがmsg.payloadに搭載されて送り出されます。

次のfunctionノードは、出力を2つとしました。JSONのプロパティ(Python辞書のキー毎に、別々のノードに接続したかったためです。functionノードの出力を分ける件はこちら

functionJSON今回、JSONから取り出したデータは何もせず、Node-REDダッシュボード上に表示するだけです。2個の表示用textを用意し、プロパティ(Python辞書キー)”A”と、プロパティ(Python辞書キー)”B”の値をそのまま表示しています。

前回の設定でMicroPython側では10秒毎にStatusを送信しています。今回、Status送信に引き続きJSONオブジェクトを送るように追加をしました。そこで、JSON-A、JSON-Bともに約10秒毎に値が更新されます。今回送信してくるのはテスト用の単なるカウンタ起源の値ですが。

uJsonDashboardこれで、上り方向が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()