こんにちは。
かわいいM5stack
今日はInterface2018年9月号で特集された「IoTマイコンESP32」を使ったM5stackという5.4cm×5.4cmのかわいい機器を使って環境温度湿度をGoogleスプレッドシートに記録するお話です。
中国のスタートアップの会社さんが作っている製品のようです。
この夏の猛暑で自宅の環境温度を記録してみようかな?と温度ロガーをいろいろと物色したのですが、結構なお値段でUSBでPCで繋いでデータを管理するようなものばかりで何となく二の足を踏んでいました。
Interfaceさんの記事を読んで、M5stackに温度湿度センサーをつないで、ESP32の機能のWi-Fiを使ったらInternet上にデータが記録できるんじゃないか?と思っていろいろ調べてたらM5stackの派生機器のM5GOを使って温度などを記録した記事なども見つけてどうにも欲しくなってきました。
M5GOはM5stack本体とすぐに繋げるセンサー類がセットになった商品(多少M5stack本体の構成もちがうようです)なのでいろいろ試せそうなので欲しかったのですが品切れ状態。
なので、日本でM5stackを扱っているスイッチサイエンスさんがAmazonで出品してる商品の中から、「M5Stack Gray」と「M5Stack用プロトキット(温湿度センサ付き)」を購入することにしました。
「M5Stack用プロトキット(温湿度センサ付き)」には温湿度センサーDHT12と、M5Stackを接続するためのGROVEケーブルもついているので素人の私でもはんだ付けとか加工をしなくても使えるので便利そうだなって思ったのです。
到着とテスト
で、注文から2日後、郵便受けに入るネコポスで届きました。
開封して、自宅にあった携帯電話用の外部電池(500mA入力ですが、電池は1Aの出力だったので大丈夫かな?と思って)につないで起動してみたら、自己診断とABCボタンの動作確認プログラムが入っていたので簡単にボタンの動作確認をしました。(後で、家に500mAの出力のコンセントに挿すUSB電源をみつけて、温度ロガーテスト時に使用していたのですが、500mA丁度の給電では時々電源が切れるようなので使った電源が悪かったのかもしれませんが1Aのほうがいいのかもしれません。)
DHT12のテストもしないといけないので、M5Stack用Arduino IDE 1.8.5をスイッチサイエンスさんからDLして、USBのドライバーもダウンロードしてArduino開発環境でDHT12のスケッチ例をコンパイルしてM5stackに送って動作確認OKでした。
次にWi-Fiのテスト。今回はM5stack側を家にあるWi-Fiのアクセスポイントにつなぎに行くWi-Fiステーションとして利用するので、とりあえずWi-Fiの動作確認にWiFiScanのスケッチ例を使ってテストしてOKでした。
(Wi-Fiの機能は生きているのですが、このあと自作のプログラムでWi-Fiにつなぎに行こうとすると延々と接続待ちになる謎の状況でかなりハマりました。内容については後で書きます。)
受け側のGoogleスプレッドシートのGoogleAppsScriptの作成
ハマってる間に温湿度データを受ける側のGoogleスプレッドシートとGoogleAppsScriptを準備しました。
M5stack側が温度湿度のJSONデータを投げて、GoogleAppsScriptのdoPost()で受けて、Googleスプレッドシートに1行ずつ追加するだけの簡単なスクリプトです。
以下のようなJSONデータをhttpPOSTで投げておくだけで、GoogleAppsScriptは別に「contentType: ‘application/JSON’」など指定しなくてもe.postData.getDataAsString()で拾って JSON.parse()するだけで扱ってくれるらしいので、JSONデータをPOSTする方は簡単です。
{"temp":27.25,"humid":65.15}
このような行見出しのスプレッドシートを作って、名前をつけて保存。
で、このシートを操作するGoogleAppsScriptを作るので、「ツール」→「スクリプトエディタ」からスクリプトの画面を開きます。
以下のようなスクリプトを書きます。
とりあえずシート名はスクリプトプロパティから取得するようにしています。
//MIT License // //Copyright (c) 2018 @telomere0101 // //Permission is hereby granted, free of charge, to any person obtaining a copy //of this software and associated documentation files (the "Software"), to deal //in the Software without restriction, including without limitation the rights //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell //copies of the Software, and to permit persons to whom the Software is //furnished to do so, subject to the following conditions: // //The above copyright notice and this permission notice shall be included in all //copies or substantial portions of the Software. // //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE //SOFTWARE. var prop = PropertiesService.getScriptProperties(); // SHEET_NAMEプロパティに登録しておく var sheetName = prop.getProperty('SHEET_NAME'); function doPost(e) { // シート取得 var ss = SpreadsheetApp.openById(SpreadsheetApp.getActiveSpreadsheet().getId()); var sheet = ss.getSheetByName(sheetName); var postjsonString = e.postData.getDataAsString(); var postdata = JSON.parse(postjsonString); var temp = null; var humid = null; var date = null; var date_time = null; // データ入力 temp = postdata.temp; humid = postdata.humid; date = new Date(); date_time = Utilities.formatDate(date, 'JST', 'yyyy年M月d日 H時m分s秒') sheet.appendRow([date_time,temp,humid]); var output = ContentService.createTextOutput(); output.setMimeType(ContentService.MimeType.JSON); output.setContent(JSON.stringify({ message: "success!" })); return output; }
本来はM5stack側をタイムサーバと同期させて、温湿度測定の時間もJSONに入れて送るべきなのですが、テスト的に実装したいので簡単に温湿度だけ受けるようにしておきました。
GoogleAppsScriptのテスト
このGoogleAppsScriptを「ウェブアプリケーションとして導入」(テスト的に使用するので匿名アクセスを許すように公開してます)して、承認して(結構設定画面がやっかい)公開できました。
この公開したURLに向けて、JSONデータをPOSTすればGoogleAppsScriptのテストはできるので、Postmanというプログラムを使用してテストします。
M5stack側のスケッチ
そうこうしているうちに、Wi-Fiアクセスポイントにステーションとして接続できない原因が最初にdisconnect()していないからのような話をネットで見つけて解決しました。
(2018/11/14更新:M5stackのライブラリは、Ver.0.2.4で再度コンパイルして動作確認しました。)
M5stack側のスケッチです。
(「カスタムライブラリのスケッチ例」のM5stack->Modules->DHT12のスケッチ例を読み込んで別名保存してから、以下のソースを上書きしています)
/* Program for logging temperature and humidity on Google Spreadsheet. MIT License Copyright (c) 2018 @telomere0101 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include <M5Stack.h> #include "DHT12.h" #include <WiFi.h> #include <HTTPClient.h> DHT12 dht12; //Preset scale CELSIUS and ID 0x5c. const char* ssid = "SSID"; // your network SSID (name of wifi network) const char* password = "Password"; // your network password //setup start void setup() { M5.begin(); Wire.begin(); //Initialize serial and wait for port to open: Serial.begin(115200); delay(100); Serial.print("Attempting to connect to SSID: "); WiFi.mode(WIFI_STA); WiFi.disconnect(); Serial.println(ssid); WiFi.begin(ssid, password); // attempt to connect to Wifi network: while (WiFi.status() != WL_CONNECTED) { Serial.print("."); // wait 1 second for re-trying delay(1000); } Serial.print("Connected to "); Serial.println(ssid); } //loop endless void loop() { float temp, humid; temp = (float)dht12.readTemperature(); humid = (float)dht12.readHumidity(); //fill Screen black for clear screen M5.Lcd.fillScreen(BLACK); M5.Lcd.setCursor(0, 0); M5.Lcd.setTextSize(6); //Read temperature with preset scale Serial.printf("Temp: %2.2f", temp); M5.Lcd.printf("Temp:\r\n %2.2f\r\n", temp); //Read humidity Serial.printf("Humid: %2.2f", humid); M5.Lcd.printf("Humid:\r\n %2.2f\r\n", humid); //make JSON char json[100]; sprintf(json, "{\"temp\": %2.2f , \"humid\": %2.2f }", temp, humid); //HTTPClient code start HTTPClient http; Serial.print("[HTTP] begin...\n"); // configure traged server and url http.begin("your_google_apps_script_published_url"); //HTTP Serial.print("[HTTP] POST...\n"); // start connection and send HTTP header int httpCode = http.POST(json); // httpCode will be negative on error if (httpCode > 0) { // HTTP header has been send and Server response header has been handled Serial.printf("[HTTP] GET... code: %d\n", httpCode); // file found at server if (httpCode == HTTP_CODE_OK) { String payload = http.getString(); Serial.println(payload); } } else { Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); } http.end(); //delay 5 minutes delay(300000); }
Googleスプレッドシートはこんな感じになります。
夏休みのガジェット作成やお子さんの自由研究にM5stack使ってみてはいかがでしょうか?
IoTの勉強やプログラミングの実践的な学習にM5stackは最適だと思います。
M5GOではscratchのような画面で開発もできると聞いているのでM5GOやFIREなどの派生製品も興味があります。
実際、今回のテストプログラムはM5Stack用Arduino IDE 1.8.5についていたスケッチ例(DHT12とBASICHTTPClient)を参考にハマった時間と初期動作確認テストを含めてもも2日間。実質10時間程度で出来上がっています。
スケッチ例が豊富なのでいろんなことが試せる気がします(BlueToothなど)
先人の方々のおかげだと思います。
後記:
購入後、ネットを見ていたら「BTC ticker」というDHT12とUSB-typeCの電源ケーブル付きのかわいいM5stack専用台をみつけて、「おお!!」っと思いました。かわいく据え置きで温度湿度のログが取れますね。Bitcoinはよくわかりませんが、BTC ticker日本でも扱ってくれないでしょうか??
私のブログのこの記事の一番上の写真は、現在CheeroのPower Plus 3につないで電池消費量のランニングテストをしている様子です。M5stackの大きさの比較のためにiQOSのタバコの箱を横に置いています。
スマホのテザリングのWi-FiやポケットWi-Fiなどにつないで工場やオフィスなどの環境温度湿度のログをGoogleスプレッドシートに記録する用途などにも利用できるのではないかと思って電池の減り具合をテストしてます。
今現在12時間以上運用していますが、フル充電のPower Plus 3の電池残量LEDが1目盛りも減ってない状況です。
追記:doGet()を追加して最新データをスマホで確認できるようにしました
Googleスプレッドシートの最新のデータをスマホで見てみたくなったので、GoogleAppsScriptを以下のように変更しました。
doGet()を追加して、公開URLにブラウザでアクセスすると、最新データを表示するようにしました。
Google Apps Scriptはセキュリティを考慮して、公開URLをリダイレクトします。以下下記URLの文書の引用です。
https://developers.google.com/apps-script/guides/content
For security reasons, content returned by the Content service isn’t served from script.google.com, but instead redirected to a one-time URL at script.googleusercontent.com. This means that if you use the Content service to return data to another application, you must ensure that the HTTP client is configured to follow redirects. For example, in the cURL command line utility, add the flag -L. Check the documentation for your HTTP client for more information on how to enable this behavior.
//MIT License // //Copyright (c) 2018 @telomere0101 // //Permission is hereby granted, free of charge, to any person obtaining a copy //of this software and associated documentation files (the "Software"), to deal //in the Software without restriction, including without limitation the rights //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell //copies of the Software, and to permit persons to whom the Software is //furnished to do so, subject to the following conditions: // //The above copyright notice and this permission notice shall be included in all //copies or substantial portions of the Software. // //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE //SOFTWARE. var prop = PropertiesService.getScriptProperties(); // SHEET_NAMEプロパティに登録しておく var sheetName = prop.getProperty('SHEET_NAME'); function doGet(e){ // シート取得 var ss = SpreadsheetApp.openById(SpreadsheetApp.getActiveSpreadsheet().getId()); var sheet = ss.getSheetByName(sheetName); var temp = null; var humid = null; var date_time = null; var range = sheet.getRange(sheet.getLastRow(), 1, sheet.getLastRow(), 3); var date_time = range.getCell(1,1).getValue(); var temp = range.getCell(1,2).getValue(); var humid = range.getCell(1,3).getValue(); var s = ""+ date_time +" \r\n"+temp+"℃ \r\n"+humid + "%"; var output = ContentService.createTextOutput( s); return output; } function doPost(e) { // シート取得 var ss = SpreadsheetApp.openById(SpreadsheetApp.getActiveSpreadsheet().getId()); var sheet = ss.getSheetByName(sheetName); var postjsonString = e.postData.getDataAsString(); var postdata = JSON.parse(postjsonString); var temp = null; var humid = null; var date = null; var date_time = null; // データ入力 temp = postdata.temp; humid = postdata.humid; date = new Date(); date_time = Utilities.formatDate(date, 'JST', 'yyyy年M月d日 H時m分s秒') sheet.appendRow([date_time,temp,humid]); var output = ContentService.createTextOutput(); output.setMimeType(ContentService.MimeType.JSON); output.setContent(JSON.stringify({ message: "success!" })); return output; }
こんな感じに表示されます。