MicroPython的午睡(41) MQTTでPublish、M5ATOM Lite

Joseph Halfmoon

超小型デバイスM5 ATOM Liteに、MicroPythonのESP32向けgenericポートを書き込んで動かしております。前回は nptサーバに接続してRTCに時刻を設定しました。今回はMQTTブローカにPublishして、Node-REDのダッシュボードにATOM LiteのRTCの時刻を表示させてみます。

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

(末尾に実験に使用したスクリプト全文を掲げました。)

実験用のNode-REDとMQTTブローカ(Mosquitto)は、Raspberry Pi 3機で動かしています。昨日、別件でNode-REDのバージョンUP、ちょっとトラブリました。 本日は更新したばかりのNode-REDに向けて M5 ATOMLite から文字列を送り込んで表示させたいと思います。なお、M5 ATOMLiteとNode-REDの間の通信は、以下の投稿でC/C++処理系を使って一度行っており、既にM5 ATOMLite用のフローもダッシュボードも準備済。ありものに乗っかってMicroPythonでのMQTT Publishを確認するのみ。

IoT何をいまさら(83) M5Stack ATOMLite、MQTTでnode-RED接続

今回使用する MicroPython のモジュール

MicroPythonのESP32向けgenericポートにデフォルトで入っているモジュールを確認してみると、以下の2つがMQTTのモジュールであるようです。

    • umqtt/robust
    • umqtt/simple

2つも入っているので特に自力で何かインストールする必要は無さそうです。どうせ端から動かしてみるつもりなのですが、まず今回は 「simple」で行きたいと思います。実用上、通信はrobustである方が良いに決まっているけれど、なるべくお手軽なものから行きたい。。。

umqtt/simpleに関しては以下の立派なドキュメントがあります。

umqtt.simple — MQTT client function

モジュール自体はPyPIからダウンロード可能みたいです(今回使用のMicroPython実装にはデフォで入っているのでやらないケド。)

PyPI micropython-umqtt.simple 1.3.4

ありがちなこととは言え、個別の実装ではドキュメントで記述されている機能のサブセットという事態はままあるので、例によってMicroPythonのREPLから実装状況を確認してみます。こんな感じ。

>>> import umqtt.simple
>>> help(umqtt.simple)
object <module 'umqtt.simple' from 'umqtt/simple.py'> is of type module
  socket -- <module 'usocket'>
  MQTTClient -- <class 'MQTTClient'>
  struct -- <module 'ustruct'>
  __name__ -- umqtt.simple
  __file__ -- umqtt/simple.py
  hexlify -- <function>
  MQTTException -- <class 'MQTTException'>
>>> help(umqtt.simple.MQTTClient)
object <class 'MQTTClient'> is of type type
  set_last_will -- <function set_last_will at 0x3ffe7470>
  _recv_len -- <function _recv_len at 0x3ffe7430>
  ping -- <function ping at 0x3ffe7490>
  __qualname__ -- MQTTClient
  subscribe -- <function subscribe at 0x3ffe74f0>
  __init__ -- <function __init__ at 0x3ffe73e0>
  set_callback -- <function set_callback at 0x3ffe7410>
  disconnect -- <function disconnect at 0x3ffe74d0>
  _send_str -- <function _send_str at 0x3ffe73a0>
  connect -- <function connect at 0x3ffe7450>
  publish -- <function publish at 0x3ffe74b0>
  wait_msg -- <function wait_msg at 0x3ffe7510>
  __module__ -- umqtt.simple
  check_msg -- <function check_msg at 0x3ffe7520>

見たところ(ざっとですが)、当方で使いそうな機能は実装されているみたい。

Node-REDのノードの設定を確認

さてM5 ATOMLiteからのMQTTパケットを受け止めて表示する側のNode-REDのフローをアイキャッチ画像に掲げました。3つあるフローの先頭のノード2つだけのフローがそれです。ぶっちゃけ

    • mqtt-inノードでブローカさんからパケット受領、ペイロードを流す
    • Node-REDダッシュボードのtextノードで流れてきたペイロードをダッシュボード上に表示する

これだけ。いちおう2つのノードの設定をNode-RED上で確認しておきます。

mqtt-inノード

ATOMLiteStatus

Dashboardのtextノード

ATOMLiteStatusDashboard

MicroPythonスクリプトの概要

末尾のスクリプトは、前回作成した 「ntp 使って M5ATOM LiteのRTCに時刻を設定」するスクリプトの「チョイ変」です。前回、ntp使用するためにWiFi経由でネットワークに接続できるようになっているので、追加すべきはMQTTに関する操作のみです。

    1. MQTTクライアントを作る
    2. MQTTクライアントをブローカに接続する
    3. 接続できたら、MQTTブローカにPublishする
    4. MQTT接続を切る

Publishする際には、上記のmqtt-inノードで設定している「トピック」を指定します。また、Publishする内容は、折角RTCに時刻を設定しているので、RTCから読み出した現在時刻を文字列にしたものとします。

今回は、1発Publishした後「逃げて」しまうので、最後に接続は切っています。Subscribeはまた次回ということで(進捗遅いな。C/C++では1回で済ませたのに。)

MicroPythonスクリプトの実行結果

M5 ATOMLite上でスクリプトを実行させた後、PC上のブラウザからRaspberry Pi 3 上のNode-REDダッシュボードに接続して見えたのが以下です。

ATOMLite PublishPublishは成功。次回は、下にあるColorを使って、ダッシュボードからの命令をM5 ATOMLite側に「伝達」してみる予定です。

MicroPython的午睡(40) まずはネット接続してntp、M5ATOM Lite に戻る

MicroPython的午睡(42) array.array、意外に小さい使える領域 へ進む

実験に使用したMicroPythonスクリプト全文
import network, ntptime, machine, time
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 #JST

def connectMqttBroker(bAdr, bPort, clientId):
    global client
    try:
        client = umqtt.simple.MQTTClient(clientId, bAdr, port=bPort)
        client.connect()
    except:
        return False
    return True

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 main():
    if do_connect(ssid, password, 10):
        initRTC(tz)
        if connectMqttBroker(brokerAdr, brokerPort, "ATOMLite"):
            client.publish("ATOMLite/Status", "FROM ATOMLite: " + currentTIME(tz))
            client.disconnect()
        else:
            print("ERROR: MQTT Broker connection.")
    else:
        print("ERROR: WiFi connection.")
            
if __name__ == "__main__":
    main()