M5stackで温度・湿度をGoogleスプレッドシートに記録してみませんか?(Arduino IDE)

M5stackで温度・湿度をGoogleスプレッドシートに記録してみませんか?(Arduino IDE)

こんにちは。

かわいい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を作るので、「ツール」→「スクリプトエディタ」からスクリプトの画面を開きます。

以下のようなスクリプトを書きます。
とりあえずシート名はスクリプトプロパティから取得するようにしています。

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()していないからのような話をネットで見つけて解決しました。
M5stack側のスケッチです。

/*
  Program for logging temperature and humidity on Google Spreadsheet.
*/
#include <Arduino.h>
#include <M5Stack.h>
#include "utility/DHT12.h"
#include <Wire.h>     //The DHT12 uses I2C comunication.

#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.

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;

}

こんな感じに表示されます。

M5Stackカテゴリの最新記事