MicroPython的午睡(55) ATOMLite、NodeREDにCdSセル結果報告

Joseph Halfmoon

前回 M5ATOMLiteのADCにCdSセルを接続し、値を読み取ることができました。今回は測定値をサーバ機Raspberry Pi3上のNodeREDに報告です。DHT11、BMP280と「既に通ってきた道」なので以下同文でOK、っと思ったら温度、湿度グラフのところにバグが。。。まあついでに修正。

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

(末尾に今回実験に使用したMicroPythonスクリプトの全文を掲げました。なお使用しているMicroPythonは、ESP32用 “generic” ポートです。)

改訂したMicroPythonスクリプト

以前に作製したものを改訂しつつ使っておりますスクリプトの追加部分の説明です。

前回作成した LiteCds.py スクリプト、単独で使えばCdSセルの読み取りテストに使え、上位のスクリプトから呼べば測定用のモジュールに見えるものです。これをM5ATOMLite上のローカルファイルシステム上にコピーしてあります。そこで今回は、それをimportすれば良いっと。こんな感じ。

from LiteCds import LiteCDS

また、頻繁にとったり付けたりしている外付け回路の在、不在を管理するために変数を導入済でした。それに第3のデバイスとしてCDSを追加しました。今回は、3デバイス全て取り付けてあるのでこんな感じ。

DHT11_ON = 0x1
BMP280_ON = 0x2
CDS_ON = 0x4
extDevControl = BMP280_ON | DHT11_ON | CDS_ON

外付けデバイスの初期化コードは、extInit()に置く方針です。CDSについてはデバイスのインスタンスを作成するだけ。

def extInit():
    global DHT11, BMP280, CDS
    if (extDevControl & DHT11_ON) != 0:
        DHT11 = LiteDHT11()
        print("Device: DHT11, active.")
    if (extDevControl & BMP280_ON) != 0:
        BMP280 = LiteBMP280()
        BMP280.setup()
        print("Device: BMP280, active.")
    if (extDevControl & CDS_ON) != 0:
        CDS = LiteCDS()
        print("Device: CDS, active.")

測定の実体は、asyncio使って周期的に呼び出されるタスク内から呼ばれる以下の関数です。CDS追加したところが以下に。

def makeOBJ(num):
    global DHT11, BMP280, CDS
    workDic = dict()
    if (extDevControl & DHT11_ON) != 0:
        tpl = DHT11.measure()
        workDic['TEMP'] = tpl[0]
        workDic['HUMI'] = tpl[1]    
    if (extDevControl & BMP280_ON) != 0:
        tpl = BMP280.measure()
        workDic['BMP280_T'] = tpl[0]
        workDic['BMP280_P'] = tpl[1]
    if (extDevControl & CDS_ON) != 0:
        tpl = CDS.measure()
        workDic['CDS_RAW'] = tpl[0]       
    workDic['A'] = num
    workDic['B'] = LiteNet.currentTIME()
    return ujson.dumps(workDic)

CdSセルの測定については、生のADC測定値、その移動平均、電圧換算、抵抗換算と計算していますが、照度のような人に分かり易い単位に換算できるわけでないので、NodeREDには、生のADC値をそのまま報告することにいたしました。暗い(入射する光のエネルギーが小さい。人の目に感じる周波数以外にも感度があるので明るさとは言い切れない)と小さくなり、明るいと大きくなる数字。12ビットのADCを余裕な9ビットで使っているので0-511の範囲です。

NodeREDの修正

NodeRED側の修正は、まずCdSに対する追加です。

    1. ダッシュボード上に4番目のグラフを追加。CdSセル生測定値用とする
    2. JSONオブジェクトを仕分けるためにfunctionノードを改造、出口を+1(CdS用)した。

さらに以下、2点もBUGがありFIXしました。多分、グラフをコピペしたときに間違えたです。コピペは危険。要注意。

    1. DHT11HUMIグラフには複数系列の入力ないのに凡例アリになっていた、凡例ナシに改めた
    2. TEMPグラフには、DHT11からの温度とBMP280からの温度の2系列が入る筈だったのに、凡例ナシになっていたのを凡例アリに改めた
    3. 上記のTEMPグラフの構造上、2つ別Topicのmsgが到来しないとならないが、functionノードでの仕分けをバグっていて1方しか到来していなかったので修正(この件でも出口を+1。)

やっつけでご乱心な作業の結果であります。修正したM5ATOMLite用のフローの全体が以下に。

CDSreportFlow

動作結果

NodeREDダッシュボード上ATOMLiteタブの、測定開始(M5ATOM上のスクリプト起動)からほぼ1時間を経過した時点での、接続デバイスの測定値のグラフ表示は以下です(PC上のChromeからNodeREDが走っているRaspberry Pi3機に接続してみています。)

CDSreportDashBoardC

左上のCDSとあるのがCdSセルの生測定値です。開始後急に0まで落ちているのは、そこで黒のフェルトをセンサに被せてほぼ真暗にしたためです。その直後にフルスケールまで振れているのは、LED照明をCdSセルの直上数cmから照らして明るくしたためです。その後、室内の通常の照明にもどった後は安定した値を示しています。

BUG-FIX後の温度センサのグラフは、DHT11とBMP280の2系統が表示されるようになりました。ほとんど同じ場所で温度を測っているので、測定値の差は極めて微妙。まあ乖離していたらそれはそれで問題だけれども。

MicroPython的午睡(54) ATOMLite、ADCにCdSセルを接続 へ戻る

MicroPython的午睡(56) ATOMLite、パッシブ・ブザー接続 へ進む

実験に使用したMicroPythonコード、最上位スクリプト全文
import sys
import ujson
import uasyncio
from LiteOnBoard import LiteOnBoard
from LiteNetwork import LiteNetwork
from LiteDHT11 import LiteDHT11
from LiteBMP280 import LiteBMP280
from LiteCds import LiteCDS

ledColor = (0, 0, 0)
OnBoardDevice = LiteOnBoard()
LiteNet = LiteNetwork()
DHT11 = None
BMP280 = None
CDS = None
DHT11_ON = 0x1
BMP280_ON = 0x2
CDS_ON = 0x4
extDevControl = BMP280_ON | DHT11_ON | CDS_ON

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):
    global DHT11, BMP280, CDS
    workDic = dict()
    if (extDevControl & DHT11_ON) != 0:
        tpl = DHT11.measure()
        workDic['TEMP'] = tpl[0]
        workDic['HUMI'] = tpl[1]    
    if (extDevControl & BMP280_ON) != 0:
        tpl = BMP280.measure()
        workDic['BMP280_T'] = tpl[0]
        workDic['BMP280_P'] = tpl[1]
    if (extDevControl & CDS_ON) != 0:
        tpl = CDS.measure()
        workDic['CDS_RAW'] = tpl[0]       
    workDic['A'] = num
    workDic['B'] = LiteNet.currentTIME()
    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 extInit():
    global DHT11, BMP280, CDS
    if (extDevControl & DHT11_ON) != 0:
        DHT11 = LiteDHT11()
        print("Device: DHT11, active.")
    if (extDevControl & BMP280_ON) != 0:
        BMP280 = LiteBMP280()
        BMP280.setup()
        print("Device: BMP280, active.")
    if (extDevControl & CDS_ON) != 0:
        CDS = LiteCDS()
        print("Device: CDS, active.")

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)
    extInit()
    uasyncio.run(mainLoop(1000))
            
if __name__ == "__main__":
    main()