MicroPython的午睡(49) ATOMLite、温湿度を測ってNode-RED報告

Joseph Halfmoon

今回からようやく外付けのデバイスをM5ATOMLiteに接続して、ラズパイで走っているNode-REDとの間を繋ぐところに入ってまいります。その1は、ありがちな「温度、湿度」のご報告です。ここまでの準備で、回路も簡単、ソフトも簡単、ほとんど何もやることがないです。本当か?

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

さて、今回温湿度の測定に使うのは「Arduino業界のド定番」DHT11センサであります。そして、ありがたいことに ESP32用の “generic” ポートのMicroPythonでもDHT11センサ用のライブラリ・モジュールは最初から含まれております。日本語版のクイックリファレンスはこちら

完全ソフト処理なので、接続に使う端子(DHT11はたった1本で接続できます)を自由に選べます。またDHT11は、Arduino Unoであると5V電源で使いますが、3.3V電源からでも動作するようなので、3.3V電源を与えて使用します。

現物は下の写真です。左端の青色のデバイスがDHT11です。電源、GNDとDATA線の3本を接続します。DATA線は5kΩでプルアップせよ、と書いてあったのですが、手持ちの4.7kΩで代用してます。このくらい大丈夫だろ~。右がM5ATOMLiteをお手製の治具に載せたものです。M5ATOMLiteの裏面端子を接続しやすくしただけのもの。

ALite_DHT11自分でも端子がどうなっているのか忘れていたので、上記の全回路図を一応下記に掲げます。上の方にPCA9306が居ますが、今回は5V系のデバイスを接続しているわけでないので使ってないです(上記写真の治具の右端に載っている8ピンのDIP化ボードがAE-PCA9306です。)
ATOMLite_DHT11Schematic

MicroPythonのコード(追加部分)

前回の「リファクタリング」もどきで、新規追加の機能はすべからく別ファイルの class として追い出すことになったので、DHT11からの温度、湿度読み出し機能について以下のファイルを追加しました。DHT11とインタフェースするための「中のメンドイ」ことはMicroPythonのライブラリモジュールが皆やってくれるので、使用は簡単明快です。わざわざ屋上屋を重ねるようなクラスは要らない感じなのですが、他との釣り合いにより、ラッパ的なものを重ねましたです。ま、ハードウエア端子を隠蔽する役割ですかね。なお、単独ファイルで実行すればDHT11の読み取りテストができます。

import dht
import machine, time

class LiteDHT11:
    """DHT11 on G22 pin

    """
    def __init__(self):
        self.dht11 = dht.DHT11(machine.Pin(22))
        self.TEMP = None
        self.HUMI = None
    
    def measure(self):
        self.dht11.measure()
        self.TEMP = self.dht11.temperature()
        self.HUMI = self.dht11.humidity()
        return (self.TEMP, self.HUMI)

def main():
    dht11 = LiteDHT11()
    
    loopCounter = 0
    while( loopCounter < 60 ):
        loopCounter += 1
        tpl = dht11.measure()
        print("TEMPERATURE: ", tpl[0])
        print("HUMIDITY   : ", tpl[1])        
        time.sleep(60)
            
if __name__ == "__main__":
    main()

上記のファイル追加により、メインのスクリプトは以下のようになりました。すでに作ってあったJSONオブジェクトをMQTTで送り出す仕組みの中にDHT11の温度と湿度を入れ込んだので、変更箇所はごくわずかです。一番の変更は、JSONオブジクトを送信する非同期タスクの周期を60秒としたことですかね。気温や湿度などはそれほど急激に変わらないだろ~ということで。

import sys
import ujson
import uasyncio
from LiteOnBoard import LiteOnBoard
from LiteNetwork import LiteNetwork
from LiteDHT11 import LiteDHT11

ledColor = (0, 0, 0)
OnBoardDevice = LiteOnBoard()
LiteNet = LiteNetwork()
DHT11 = LiteDHT11()

def colorChange(msg):
    global ledColor
    if msg=="R":
        ledColor = (255, 0, 0)
    elif msg=="G":
        ledColor = (0, 255, 0)
    elif msg=="B":
        ledColor = (0, 0, 255)

def callbackSub(topic, msg):
    topicS = topic.decode('utf-8')
    msgS = msg.decode('utf-8')
    print(topicS, msgS)
    if topicS=="ATOMLite/Color":
        print("ATOMLite/Color Message: ", msgS)
        colorChange(msgS)
    if topicS=="ATOMLite/Settings":
        msgJ = ujson.loads(msgS)
        print("ATOMLite/Settings: ")
        for item in msgJ:
            for k,v in item.items():
                if k=="key":
                    ky = v
                if k=="value":
                    vl = v
            print(ky, " = ", vl)

def makeOBJ(num):
    tpl = DHT11.measure()
    workDic = dict()
    workDic['A'] = num
    workDic['B'] = LiteNet.currentTIME()
    workDic['TEMP'] = tpl[0]
    workDic['HUMI'] = tpl[1]    
    return ujson.dumps(workDic)

async def reportStatus(period_s):
    while True:
        LiteNet.client.publish("ATOMLite/Status", "ATOMLite Running: " + LiteNet.currentTIME())
        await uasyncio.sleep(period_s)

async def sendJson(period_s):
    sendCounter = 0
    while True:
        jsonSTR = makeOBJ(sendCounter)
        LiteNet.client.publish("ATOMLite/Json", jsonSTR)
        sendCounter += 1
        await uasyncio.sleep(period_s)

async def ledAndUserButton(period_s):
    global OnBoardDevice
    ledFlag = True
    buttonFlag = False
    while True:
        if ledFlag:
            OnBoardDevice.M5ATOMLiteLED(ledColor)
            ledFlag = False
        else:
            OnBoardDevice.M5ATOMLiteLED( (0, 0, 0) )
            ledFlag = True
        if OnBoardDevice.M5ATOMLiteUserButton():
            print("Button ON")
            LiteNet.client.publish("ATOMLite/Button", "Button ON")
            buttonFlag = True
        elif buttonFlag:
            buttonFlag = False
            LiteNet.client.publish("ATOMLite/Button", "Button OFF")            
        await uasyncio.sleep(period_s)

async def mainLoop(period_ms):
    global OnBoardDevice
    taskStatus = uasyncio.create_task(reportStatus(60))
    taskJson = uasyncio.create_task(sendJson(60))
    taskATOMhard = uasyncio.create_task(ledAndUserButton(1))
    while( OnBoardDevice.buttonCounter < 6 ):
        LiteNet.client.check_msg()
        await uasyncio.sleep_ms(period_ms)
    try:
        taskStatus.cancel()
    except:
        print("Exception at taskStatus:", sys.exc_info()[0])
    try:       
        taskJson.cancel()
    except:
        print("Exception at taskJson:", sys.exc_info()[0])
    try:       
        taskATOMhard.cancel()
    except:
        print("Exception at taskATOMhard:", sys.exc_info()[0])
    LiteNet.client.publish("ATOMLite/Button", "N/A")            
    LiteNet.client.publish("ATOMLite/Status", "ATOMLite: out of service.")
    LiteNet.close()
    print("disconnect.")
    
def main():
    if not LiteNet.do_connect():
        print("ERROR: WiFi connection.")
        sys.exit(1)
    LiteNet.initRTC()
    if not LiteNet.connectMqttBroker(callbackSub):
        print("ERROR: MQTT Broker connection.")
        sys.exit(1)
    uasyncio.run(mainLoop(1000))
            
if __name__ == "__main__":
    main()
Node-RED側の修正

Raspberry Pi 3 で動作しているMQTTブローカについては何も修正の必要はありません。Node-REDのフローについては、MQTTのトピック “ATOMLite/Json” を扱うフローにグラフ表示用のダッシュボードを2つ追加しています。温度グラフと湿度グラフ用です。

大分以前にここのフローを書いたときに、JSONオブジェクトの分解仕分けにfunctionノードを使ってしまいました。他のノードで仕分けた方がカッコ良いようにも思うのですが、今回はMicroPythonスクリプトメインなので、そのままの方法としました。

ATOMLite_DHT11FLOW上記のフローをデプロイした後のNode-REDダッシュボードの様子をアイキャッチ画像に掲げました。グラフが2つ加わって、いかにもIoTっぽくなってきた?感じ。数時間後の温度湿度のグラフは以下です。

ATOMLite_DHTgraph

 

今回1ピンだけでインタフェースがとれるデバイスだったから、次回は2ピンか?

MicroPython的午睡(48) 中途半端なリファクタリング。組み込みには組み込みの? へ戻る

MicroPython的午睡(50) ATOMLite、やらかしTZ、カレンダ変換 へ進む