LoginSignup
9
7

More than 1 year has passed since last update.

M5GO(M5Stack)をWeb Serverにして環境センサー値をGoogle Chartsで表示

Last updated at Posted at 2018-10-14

【2022/03/02追記】
最近 LGTM を頂いたので最新の M5Stack の UI.Flow Firmware (v1.9.2) で動くかどうか確認しました。
そのまま動かそうとすると以下の問題があります。

  • from microWebSrv import MicroWebSrv でエラー ImportError: no module named 'microWebSrv'
    「microWebSrv」というモジュールは存在しますが MicroWebSrv 配下に移動しているためエラーになってしまいます。また、microWebSrv 内で microWebSocket というモジュールもインポートしているため、単に
from MicroWebSrv.microWebSrv import MicroWebSrv

としても microWebSrv 内で microWebSocket を参照することができません。対応としてはプログラムの先頭で以下のように MicroWebSrv 配下をライブラリのパスに追加することで参照可能となります。

import sys
sys.path.append('MicroWebSrv')

使用されている microWebSrv.py のソースは以下になります。

  • import logging でエラー ImportError: no module named 'logging'
    Firmware のバージョンアップで logging モジュールが無くなってしまいました。単純な確認用のログ出力に使っているだけなので、print() での置き換えで充分でしょうし、logging モジュールを使いたい場合は以下の logging.py 追加するということも可能です。

【2020/02/16更新】最新 Firmware(M5 UI.Flow v1.4.4)の MicroPython に合わせて修正

  • wifisetup → wifiCfg (Firmware v1.3で変更)
  • 各関数内で追加の import や global 宣言が不要になっていたので、関連するコメントおよび宣言を削除(Firmware v1.1.2で変更)

概要

  • M5GO(M5Stack)のクラウド環境M5Flow経由でプログラムを実行する
  • M5GO付属の環境センサー(DHT12、BMP280)から気温・湿度・気圧を取得
  • M5GOの液晶画面にQRコードでアクセス用URLを表示する
  • M5GO(M5Stack)をWeb Serverとして使用しWeb Socketで取得値を返す
  • ブラウザ側ではGoogle Chartsを使用して気温・湿度・気圧をそれぞれメータ表示

環境

  • M5GO (Firmware M5 UI.Flow v1.4.4)
  • M5Flow を使ってクラウド側から実行

実行結果

M5GOの画面上に用事されたアクセス用QRコードを使ってWebページを開くとGoogle Chartsを使って気温・湿度・気圧が表示される。

IMG_0113-1.png

プログラム(含indexページ)

特記事項

  • M5Flow上でMicroPythonのプログラムを書く際、各関数内で使用するグローバル変数は global によって明記する必要がありました。(ただし、APP.LISTに登録して実行する場合はその制約は無し)
  • M5GO付属の環境センサーはDHT12(気温・湿度)とBMP280(気温・気圧)でどちらも気温を測定できますが、値を比較するとBMP280の方がより実際の気温に近い値が得られているようなので、BMP280の気温を使用しています。
  • 使用した Web Server/Web Socket のライブラリは複数スレッドでの使用が可能なものですが、スレッドを有効にして実行すると落ちてしまうので、スレッドを無効にしています。
main.py
from m5stack     import lcd
from microWebSrv import MicroWebSrv
from dht12       import DHT12
from bmp280      import BMP280
from machine     import Timer
import network
import i2c_bus
import json
import logging

def _httpHandlerIndexGet(httpClient, httpResponse):
    httpResponse.WriteResponseOk(headers		 = None,
                                 contentType	 = "text/html",
                                 contentCharset  = "UTF-8",
                                 content 		 = index_content)

def _acceptWebSocketCallback(webSocket, httpClient) :
    logger.debug("WS ACCEPT")
    webSocket.RecvTextCallback   = _recvTextCallback
    webSocket.ClosedCallback 	 = _closedCallback
    ws_list.append(webSocket)

def _recvTextCallback(webSocket, msg) :
    logger.debug("WS RECV TEXT : %s" % msg)

def _closedCallback(webSocket) :
    logger.debug("WS CLOSED")
    ws_list.remove(webSocket)

def send_env_data(timer):
    env_data = {}  # Store data in dict
    try:
        # DHT12から気温と湿度を取得
        # ただし気温に関してはBMP280の方がより正しい値が取得できているようなので
        # DHT12の気温は使用していない
        dht12.measure()
        dht12_h = dht12.humidity()
        dht12_t = dht12.temperature()

        # BMP280から気温と気圧を取得
        bmp280_t, bmp280_p = bmp280.values

        # 液晶にも表示する
        lcd.print('{:.2f}C  '.format(bmp280_t),   5, 20) # BMP280の気温
        lcd.print('{:.2f}%  '.format(dht12_t),    5, 50) # DHT12の湿度
        lcd.print('{:.2f}hPa  '.format(bmp280_p), 5, 80) # BMP280の気圧

        env_data['temperature'] = bmp280_t
        env_data['humidity']    = dht12_t
        env_data['pressure']    = bmp280_p
        logger.debug(env_data)
        for ws in ws_list:
            if not ws.IsClosed():
                # ブラウザへはjson形式にて送信
                if not ws.SendText(json.dumps(env_data)):
                    logger.error('webSocket.SendText error')
    except Exception as e:
        # 時々I2C bus error が起きる。
        logger.error('Exception: ', e)
    gc.collect()


# M5 UI.Flow からの実行ではなく、APP.LIST に登録して実行する場合は、
# プログラム内でネットワーク接続を行う必要がある。
# Connect network
# 
# UI.Flow 1.2 以前は以下
# import wifisetup
# wifisetup.auto_connect()
#
# UI.Flow 1.3 以降は以下
import wifiCfg
wifiCfg.autoConnect(lcdShow=True)

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

lcd.setBrightness(255)
lcd.font(lcd.FONT_DejaVu18)
lcd.clear()

i2c    = i2c_bus.get(i2c_bus.M_BUS)
dht12  = DHT12(i2c)
bmp280 = BMP280(i2c)

sta_if = network.WLAN(network.STA_IF)
url = 'http://' + sta_if.ifconfig()[0] # ip address
logger.info(url)
lcd.qrcode(url, 135, 20, 175)

ws_list = [] # Active WebSocket list
timer = Timer(1)
timer.init(period=1000, mode=Timer.PERIODIC, callback=send_env_data)

routeHandlers = [("/", "GET", _httpHandlerIndexGet)]

srv = MicroWebSrv(routeHandlers=routeHandlers)
srv.MaxWebSocketRecvLen     = 256
# スレッドを使用するとエラーが発生するため使用しない
srv.WebSocketThreaded       = False
srv.AcceptWebSocketCallback = _acceptWebSocketCallback

index_content = """\
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8"/>
        <title>気温・湿度・気圧計 - Google Charts</title>
        <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
        <script type="text/javascript">
            google.charts.load('current', {'packages':['gauge', 'corechart']});
            google.charts.setOnLoadCallback(chartSetup);
            
            var optionGaugeT = {
                    width: 250, height: 250,
                    min: -10, max: 50,
                    majorTicks: ["-10", "0", "10", "20", "30", "40", "50"],
                    minorTicks: 5
                };
            var optionGaugeH = {
                    width: 250, height: 250,
                    min: 0, max: 100,
                    majorTicks: ["0", "20", "40", "60", "80", "100"],
                    minorTicks: 4
                };
            var optionGaugeP = {
                    width: 250, height: 250,
                    min: 940, max: 1060,
                    majorTicks: ["940", "960", "980", "1000", "1020", "1040", "1060"],
                    minorTicks: 4
                };
                
            var dataGaugeT;
            var dataGaugeH;
            var dataGaugeP;
            
            var chartGaugeT;
            var chartGaugeH;
            var chartGaugeP;
            
            var formatterT;
            var formatterH;
            var formatterP;
                
            function chartSetup() {
                dataGaugeT = google.visualization.arrayToDataTable([
                        ['Label', 'Value'],
                        ['気温', 0]
                    ]);
                dataGaugeH = google.visualization.arrayToDataTable([
                        ['Label', 'Value'],
                        ['湿度', 0]
                    ]);
                dataGaugeP = google.visualization.arrayToDataTable([
                        ['Label', 'Value'],
                        ['気圧', 0]
                    ]);
                
                chartGaugeT = new google.visualization.Gauge(document.getElementById('gauge_chart_temperature'));
                chartGaugeH = new google.visualization.Gauge(document.getElementById('gauge_chart_humidity'));
                chartGaugeP = new google.visualization.Gauge(document.getElementById('gauge_chart_pressure'));
                
                formatterT = new google.visualization.NumberFormat({suffix:'', fractionDigits:1});
                formatterH = new google.visualization.NumberFormat({suffix:' %', fractionDigits:1});
                formatterP = new google.visualization.NumberFormat({suffix:' hPa', fractionDigits:1});
                
                updateCharts(undefined, undefined, undefined);
            }
            
            function updateCharts(t, h, p) {
                serverTime = new Date();
                document.getElementById('datetime').innerHTML=serverTime.toLocaleString();
                
                if (t !== undefined) {
                    dataGaugeT.setValue(0, 1, t);
                    dataGaugeH.setValue(0, 1, h);
                    dataGaugeP.setValue(0, 1, p);
                }
                formatterT.format(dataGaugeT, 1);
                formatterH.format(dataGaugeH, 1);
                formatterP.format(dataGaugeP, 1);
                
                chartGaugeT.draw(dataGaugeT, optionGaugeT);
                chartGaugeH.draw(dataGaugeH, optionGaugeH);
                chartGaugeP.draw(dataGaugeP, optionGaugeP);
            }

            var websocket;
            var sendTimer;
            function dummySend() {
                if (websocket.readyState === WebSocket.OPEN) {
                    console.log("send test");
                    websocket.send("test");
                }
            }
            function connectM5GO()
            {
                var wsUri           = "ws://" + window.location.hostname;
                console.log("Connection to " + wsUri + "...");
                websocket           = new WebSocket(wsUri);
                websocket.onopen    = function(event) { onOpen    (event) };
                websocket.onclose   = function(event) { onClose   (event) };
                websocket.onmessage = function(event) { onMessage (event) };
                websocket.onerror   = function(event) { onError   (event) };
            }
            function onOpen(event)
            {
                console.log("on Open");
                document.getElementById('websocket_status').innerHTML = "M5GO接続状態接続";

                sendTimer = setInterval(function() {
                    dummySend();
                }, 10000);
            }
            function onClose(event)
            {
                clearInterval(sendTimer);
                console.log("on Close");
                document.getElementById('websocket_status').innerHTML = "M5GO接続状態切断";
            }
            function onMessage(event)
            {
                console.log("on Message: " + event.data);
                var env_data = JSON.parse(event.data);
                updateCharts(Number(env_data.temperature), Number(env_data.humidity), Number(env_data.pressure));
            }
            function onError(event)
            {
                console.log("on Error: " + event.data);
            }
            window.addEventListener("load", connectM5GO, false);
       </script>
    </head>
    <body>
        <div style="width: 1000px; margin: 0 auto;">
            <div id="datetime" style="text-align: center; font-size: 300%; font-family: sans-serif;"></div>
            <div style=" display:flex;justify-content: center;">
                <div id="gauge_chart_temperature"></div>
                <div id="gauge_chart_humidity"></div>
                <div id="gauge_chart_pressure"></div>
            </div>
            <div id="websocket_status" style="text-align: center; font-family: sans-serif;">M5GO接続状態:切断</div>
        </div>
    </body>
</html>
"""

# スレッドを使用するとエラーが発生するため使用しない
srv.Start(threaded=False)
9
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
7