【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を使って気温・湿度・気圧が表示される。
プログラム(含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)