PythonでTENORI-ONを動かすblog(仮)

PythonでMIDIを作ってYAMAHAのTENORI-ONをごにょごにょするよ。

GASで作る貧乏IoT(1) ESP32からPOSTして、レスポンスを取得する。

2021.05.30追記

どうもGCP(Google Cloud Platform)のアカウントを持っていると、GCP側で別の設定をしないと動かないようです。

`GCPの画面からGASのプロジェクト作る -> OAuth設定 -> GASファイル作成` という手順が必要っぽい。

Google Apps ScriptのログをGoogle Cloud Platformで確認する方法 – hidetoshl.com

-----

例によって久々の更新です。

近況

  • AWS色々覚えたものの、諸事情により、課金ダメージを受けた。(※1)
  • なので個人でIoT周りやるなら、請求が発生し得るものはしばらく触りたくないというお気持ちに。

で、あんまり好きじゃないけど、一時期仕事で触っていたGoogle Apps Script(以下GAS)やるか、、となったものの、相変わらずトラップが多いので、備忘録的にまとめようと思いました。

 

というわけで「GASで作る貧乏IoT」です

内容は主に電子工作趣味のハードウェア、組み込みの人向けに必要そうなことだけ。

気が向いたら何回か書くと思いますが、GASの文法とかWebのお作法などは特に触れません。


GAS(Google Apps Script)について雑に書く

  • Googleが提供するJavaScript互換のスクリプト環境。
  • 2020年2月からはEcmaScript対応になり、モダンな書き方ができるようになったらしい。(※2)
  • Googleフォーム、スプレッド、ドキュメント、メール、カレンダー連携には強い
  • 正確な時間ではないが、一応、定期実行もできる。
  • 6分を超える処理はできない。(タイムアウト扱いになって色々トラブルが起きる)
  • IDE上でデバッグやステップ実行できる
  • 無料で使える。(色々不満はあるけど、これ超重要)

 

今回のお題 「ESP32からデータをPOSTして、レスポンスを取得する。」

注意!!!) doPost関数は業務利用は、ほぼ無理と思います。

趣味の範疇でのみ利用可能と捉えてください。(※3)

●概要

  1. ESP32(Arduino)からGASにデータをPOSTする
  2. POSTした結果をスプレッドに記入。
  3. 処理終了後にレスポンスを返す。

重要)スプレッドに書くだけならさして苦労はないものの、レスポンスを返す場合はライブラリ「HttpRedirect」を使う必要があります。(※4)

動作確認はM5Stackを利用。たぶんESP8266でも動くはず。

●ざっくりした流れ

1)GASのプロジェクトを用意し、Webアプリケーションとして公開する
2)Arduinoに書き込みを行う。

 

GAS側やること


スクリプトエディタを立ち上げる


方法1) スプレッドシートも使いたい場合
・スプレッドを起動。メニューの<ツール> - <スクリプトエディタ>を開く

 

方法2) 独立したプログラムファイルを作りたい場合
Googleドライブの新規作成で、"Google Apps Script"を選択


●大まかな書き方

  • "function doPost(e) {}"の中に処理を書く
  • "e.JSON.parse(e.postData.contents);"で、ESP32から送ったJSON風のデータを受ける。
  • "return ContentService.createTextOutput( 文字列 )"; でESP32にレスポンスを返す。

●コード例

 ファイル"GAS_sample.gs"

var sheet_id="*****";//スプレッドのID。https://docs.google.com/spreadsheets/d/{この部分}/edit#gid=2037815417

//デバイスからのPOSTで実行される関数
function doPost(e) {
  
  //POSTのpayloadを格納
  var parsed_data;
  try { 
    parsed_data = JSON.parse(e.postData.contents); 
  } catch(_exception){ 
    // JSON以外がPOSTされたら例外を返して終了
    return ContentService.createTextOutput("JSONデータの解釈が出来ません。: " + _exception.message); 
  }
  
  //GASデバッグ用なら↑をコメントアウトしてこういう書き方でOK
  //var parsed_data=JSON.parse('{"values_01":"10.0", "values_02":"20.5"}');
  
  var spread_file_object = SpreadsheetApp.openById(sheet_id);
  var sheet_name = "log";   
  var sheet_object = spread_file_object.getSheetByName(sheet_name);
  
  // スプレッドに行を追記
  //sheet_object.appendRow([new Date(), parsed_data.values_01, parsed_data.values_02]);
  
  //sheet_object.getRange("A1").setValue(parsed_data.values_01);
  SpreadsheetApp.flush(); //描かなくても動く。一応、遅延対策。
  
  //ESP32にレスポンスを返す
  return ContentService.createTextOutput( "success");
  
}

特定のセル1つに値を入れる場合はこう書く。

sheet_object.getRange("A1").setValue(parsed_data.values_01);


複数セルの場合はsetValues()になるので若干処理が変わる。

 

●アクセスできるようにする設定


1)スクリプトエディタのメニュー"公開" -> "webアプリケーションして導入" を開く

f:id:bastardeyes:20200606171617p:plain



Current web app URL:
-> 変更する部分はなし。URLはArduinoのコードに記載するので、コードの上の方にでも書いておくのがベター。
/macros/s/●●/exec というルール。Arduinoのファイルに書くScriptIDはこれを使う。

Project version
-> Newを選択
※コードの変更を反映する度に都度更新が必要。GASを書き換えてctrl+Sだけだと、最新に反映されない。

Execute the app as:
-> 自分のGoogleアカウントを選択

WHo has access to the app
-> Anyone,even anymous を選択

 

色々許可を求められます。

f:id:bastardeyes:20200606171933p:plain

f:id:bastardeyes:20200606171833p:plain

 

2)アクセスの許可
IDE上の虫マーク横の関数「doPost」を選択して、虫マークをクリックして実行。
途中、実行の許可を求められるのでOKする。

注意!!!)一度実行の許可をして動かさないと利用できない。

 

f:id:bastardeyes:20200606172056p:plain

f:id:bastardeyes:20200606172119p:plain

ESP32(Arduino側)

●やること
1)下記のGitHubからHTTPSRedirect.cppとHTTPSRedirect.hとDebugMacros.hをコピーする

タブ追加でファイルを追加してコピペで良い。
 

2)HTTPSRedirect.cppの89行目。 //stop();の//を消して実行できるようにする。

case 301:
case 302:
{
Serial.print(""piyo"");
// Get re-direction URL from the 'Location' field in the header
if (getLocationURL()){
stop(); // may not be required

※コメントアウトしたままだと、スプレッドは更新されるがESP32側が停止する。
※ESP32での動作確認時に変更。もしかしたら8266は変更不要かもしれない。

参考)https://github.com/electronicsguy/ESP8266/issues/91

 

コード例

ファイル名「ESP32_TO_GAS.ino」

 

 

//#include <ESP8266WiFi.h> //ESP8266なら<WiFi.hの代わりにこちら>
#include <WiFi.h> // ESP32用
#include "HTTPSRedirect.h"

// Fill ssid and password with your network credentials
const char* ssid = "****"; // TODO)入力
const char* password = "****"; // TODO)入力

const char* host = "script.google.com"; 
const int httpsPort = 443;
// Replace with your own script id to make server side changes

//TODO) google scriptのscriptIDを入力
const char *GScriptId = "*****"; 

const String url = String("/macros/s/") + GScriptId+ "/exec";
// https://script.google.com/macros/s/{この部分}/exec


HTTPSRedirect* client = nullptr;

void setup() {
  Serial.begin(115200);
  Serial.print("Connecting to wifi: ");  Serial.println(ssid);
  Serial.flush();

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("IP address: ");  Serial.println(WiFi.localIP());
}

int count;

void loop() {
  do_post();
  delay(60000);
}

void do_post(){
    
  bool flag;
  char stringCount[10];
    sprintf( stringCount, "%d", count);
    count++;
    // Use HTTPSRedirect class to create a new TLS connection
    client = new HTTPSRedirect(httpsPort);
    client->setPrintResponseBody(true);
    client->setContentTypeHeader("application/json");
    Serial.print("Connecting to ");  Serial.println(host);
    // Try to connect for a maximum of 5 times
    flag = false;
    for (int i=0; i<5; i++){
      int retval = client->connect(host, httpsPort);
      if (retval == 1) {
         flag = true;
         break;
      }
      else
        Serial.println("Connection failed. Retrying...");
    }
    if (!flag){
      Serial.print("Could not connect to server: ");
      Serial.println(host);
      Serial.println("Exiting...");
      return;
    }

    float sensor_value_01=10.0; //TODO)任意のセンサ値を設定
    float sensor_value_02=20.0; //TODO)任意のセンサ値を設定

    String payload = "{";
    payload += "\"values_01\": \"" + String(sensor_value_001) +"\"";
    payload += "\"values_02\": \"" + String(sensor_value_002) +"\",";
    payload += "}";

    
    Serial.println("==POSTCOMMAND==============================");
    Serial.println( payload );
    client->POST(url, host, payload, false);
    Serial.println("==POSTCOMMAND==============================");
    
    Serial.print( "Status      :");  Serial.println( client->getStatusCode() );
    Serial.print( "reasonPhrase:");  Serial.println( client->getReasonPhrase() );
    Serial.print( "body        :");  Serial.println( client->getResponseBody() ); //GASでreturnした値
    
    // delete HTTPSRedirect object
    delete client;
    client = nullptr;
  
  }

以上。

GAS側のレスポンスは適当な文字列にしていますが、レスポンスによってESP32で分岐を加えると色々遊べると思います。

 

ぶっちゃけAWS S3にデータ置く方がはるかに楽だし、マイコンよりもラズパイ使った方が情報も多くて簡単。
しかし、ハードを小さく安価に。ソフトもお金をかけたくない。Ambientのようなサービスを使わず自前で用意したいという場合は、もう一周回ってGASでいいやと思いました。

 

次回は時間があれば、M5CameraかMaixduinoの画像をGoogleドライブにアップロードなど。

 

ちなみにGET/POSTがどうとか、Webのお作法的なものを基礎から押さえたいという方は「Webを支える技術」あたり読むのが良いかもしれません。

 

 

※1) 仕事都合により個人で使ってたAWS RDSが、ほんのちょっとしか使ってないのに請求$400overした。
  カードの不正利用疑いで一瞬止められてたのもあり、深夜に4-50万と見間違えて死ぬかと思った。

※2) JavaScriptに対する愛が薄いので、突っ込んだ話はしません。詳しく知りたい場合は検索「EcmaScript GAS」を推奨。

※3) 多くの企業は情シスが社内ドメイン以外からのGsuiteアカウントへのアクセスに制限をしているはず。

※4) GASの仕様でdoPost関数使うとリダイレクトが発生するが、ライブラリ「WiFiClientSecure」はリダイレクトに追従しない。
  そのため、レスポンスを取ろうとすると、レスポンスコード302で詰まる。

※4) 書籍もいくつか出ているけれど、特に買う必要はないと思う。仕事のために数日で覚えるために買ったけれど、あまり必要なかった。