スマートプランター製作ログ (2) ~GAS編~

LINEボットを作成する

LINE社の無料で使えるサービスで簡単にbotを作成できます。https://developers.line.biz/ja/

ちょっとした飲食店なんかの公式LINEはこれで作られていると思います。

自分のLINEアカウントでログイン

LINEアカウントを持っている方は「LINEアカウントでログイン」からログインします。無い人はこれを機にLINEを始めましょう。

ビジネスアカウントからログインではありません。

botを通じて自分のアカウントが開示されたりはしませんので安心して次へ進みます。

ログイン出来たら、プロバイダーを適当な名前で作成します。

すると下の画面になります。

ここで、チャンネル設定でMessaging APIを選択し、botのアカウントを作っていきます。

一つのプロバイダーのもとにチャンネルは複数作成可能です。

作成すると詳細を入力していく画面になります。

適宜アイコンや説明、あなたのメールアドレスなどを追加します。

業種についてはなんかちょうどいいのがありました。

利用規約に同意して作成すると、晴れてbotが公開されます。

Messaging API設定へ進むと、botのIDやQRコードなどのアカウント情報が表示されます。このQRコードを読み取ると、友達追加することができます。

このページを主に使って作業していきます。

まずは「応答メッセージ」を、編集から無効にします。ここまでで作成したbotには既に、ユーザーからの質問などに自動で返答する機能が備わっています。

今回はその機能を使わず、GASで自作するので無効にしておきます。

次に、「チャンネルアクセストークン」の横にあるを発行をクリックし、トークンを発行します。これは、GASからLINEbotへメッセージをやり取りする際に使う鍵みたいなものです。

ここまで出来たら、LINEでの作業はひとまず終了です。

GASでAPIを公開する

GAS (GooleAppsScript) は、ExcelでいうところのVBAでやっていたもののスプレッドシート版というイメージなのですが、スプレッドシートだけでなく様々なgoogleのサービスを横断的に使用できたり、作ったスクリプトをWebAPIとして簡単に公開したりできるものです。

言語はJavaScriptらしいですが、最新ではないそうです。JavaScriptを触るのはこれが初めてなのでよく知りません。

スクリプトエディタを開く

GASはスクリプトエディタと呼ばれるプラットフォームに書いていきます。

このスクリプトエディタを開く方法はいくつかありますが、最初はまずGASをインストールするところからはじめましょう。

Google Driveを開いて何もないところで右クリック、アプリの追加からgoogle apps scriptを検索してインストールします。

インストールできたら、同じようにまた右クリックからGoogleAppsScriptを選択するとスクリプトエディタを起動できます。

ほかの方法として、例えばスプレッドシートから「ツール」→「<>スクリプトエディタ」で開く方法があります。

どちらの方法でも同じスクリプトエディタの画面が開きますが、少し違う点もあります。

スクリプトエディタのツールバーの、時計みたいなボタンをクリックすると、トリガーという機能を設定できます。これは、このスクリプトを一時間に一回実行したり、といったアクションを設定する機能です。

以下の画像はGoogleDriveから直接開いたスクリプトエディタで起動したトリガー設定画面と、スプレッドシートから開いたものです。

ちょっと違いますね。

スプレッドシートから開いた追加画面には、「イベントのソースから」に独自のトリガーが追加されています。

スプレッドシートのセルの変更を検知してスクリプトを実行したり、起動と同時に実行したりといった機能が使えます。

ドライブから開くとこれは表示されません。

今回はこのトリガー機能は使わないので蛇足だったかもしれませんが、あまり初心者向けのサイトでは解説されておらず、私は小一時間悩んでしまったので一応紹介しておきます。

スクリプトを書く

実際にESP8266からデータを受け取り、格納する部分とLINEのメッセージをやり取りする部分を書いていきます。

前回作成したESP8266のプログラムは、センサデータをこのGASに数秒毎にPOSTします。ここでは、そのPOSTされたデータからセンサーの情報を抽出する部分を書く必要があるのですが、LINE Messaging APIに関しても、ユーザーからbotへメッセージが送信されるとそのメッセージ内容をPOSTしてきます。

すなわち、どちらのPOSTか判別して分岐する必要があります。

GASは、POSTされた時、doPostという関数を実行します。

条件分岐とPOST内容の抽出はこの中に書いていきます。

実際のコードです。断っておきますが、とても褒められたコードじゃないので骨組みだけ載せます。

function doPost(e) 
{
  var parsedData;
  try { parsedData = JSON.parse(e.postData.contents); } 
  catch(f){ return ContentService.createTextOutput("JSONデータの解釈が出来ません。: " + f.message); }
  if ( parsedData.command !== "Update" ){
    reply(e);
    SpreadsheetApp.flush();
  }else{
 SpreadsheetApp.flush();
    //////////////////////////////////////////////////////急激な変化のアクション/////////////////////////////////////////////
    if((parsedData.Moisture - cellDataGet("B3"))>25){
      pushMessage("急激な水分上昇");
      waterlingTimer(true);
      cellUpdate("B4",4);
      SpreadsheetApp.flush();
    }else if((parsedData.Moisture - cellDataGet("B3"))<-80){
      pushMessage("急激な水分減少");
    }
    if((parsedData.Temperture - cellDataGet("B2"))>3){
      pushMessage("急激な温度上昇");
    }else if((parsedData.Temperture - cellDataGet("B2"))<-3){
      pushMessage("急激な温度上昇");
    }
   
//センサデータ更新
    SpreadsheetApp.flush();
    cellUpdate("B1",parsedData.Humidity);
    cellUpdate("B2",parsedData.Temperture);
    cellUpdate("B3",parsedData.Moisture);
    SpreadsheetApp.flush();
  
    return ContentService.createTextOutput( "Success" );
  }
}
var spreadSheetID = SpreadsheetApp.openById('スプレッドシートのID');
var sheet         = spreadSheetID.getSheetByName('シート名');
function cellUpdate(cell_pos, sence_val){
   var range = sheet.getRange(cell_pos).setValue(sence_val);
}
function cellDataGet(cell_pos){
  var range = sheet.getRange(cell_pos);
  return range.getValue();
}

parsedData.commandのところでUpdateじゃなかったらLINEのメッセージだと判別します。

前回、ESP8266が送信するデータはcommandを”Update”にしたからです。

LINEのメッセージがPOSTされた場合は、replyというユーザー定義の関数で返信を行います。

また、センサデータだった時はデータを前回の値と比較し、更新します。

急激な変化があればそれぞれのメッセージをpushというユーザー定義の関数で送ります。

センサデータはスプレッドシートのセルを書き換えて保管するので、スプレッドシートを作成してそのIDとシート名を定義しておきます。

IDは、スプレッドシートのURLの、

/spreadsheets/d/~~ID~~/edit#gid=0

~~ID~~  の部分の文字列です。

シート名は、左下の[シート1]というやつです。日本語だと都合が悪いので、sheet1とか適当な名前に変更しておきます。

以下にpushMessage関数(GASからLINEで自分からメッセージを送る関数)とreply関数(ユーザーからメッセージが送られてきたときのみ返信できる関数)を載せます。

var CHANNEL_ACCESS_TOKEN = 'トークン';
//ID
var USER_ID = 'botのID';
function pushMessage(postmessage) {
  var postData = {
    "to": USER_ID,
    "messages": [{
      "type": "text",
      "text": postmessage,
    }]
  };
  var url = "https://api.line.me/v2/bot/message/push";
  var headers = {
    "Content-Type": "application/json",
    'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN,
  };
  var options = {
    "method": "post",
    "headers": headers,
    "payload": JSON.stringify(postData)
  };
  var response = UrlFetchApp.fetch(url, options);
}
function reply(e) {
  // WebHookで受信した応答用Token
  var replyToken = JSON.parse(e.postData.contents).events[0].replyToken;
  // ユーザーのメッセージを取得
  var userMessage = JSON.parse(e.postData.contents).events[0].message.text;
  // 応答メッセージ用のAPI URL
  var url = 'https://api.line.me/v2/bot/message/reply';
  
  var replyMessage = "";
  //////////////////////////////////////////呼びかけ反応///////////////////////////////////////////
  if(userMessage == '大丈夫?寒くない?'){
    var temp = cellDataGet("B2");
    if(temp > 27){
      replyMessage = "むしろ暑いわボケ";
    }else if(temp < 18){
      replyMessage ="寒い。しんでしまう";
    }else{replyMessage = "いや~快適!あざっすあざっす";}
    
  }else if(userMessage == '大丈夫?暑くない?'){
    var temp = cellDataGet("B2");
    if(temp < 18){
      replyMessage = "むしろ寒いんだが?";
    }else if(temp > 27){
      replyMessage = "暑い~死ぬ~!死ぬ~!";
    }else{
      replyMessage = "いや~快適!あざっすあざっす";
    }
  }else if(userMessage == '今そっちはどんな感じ?'){
    var temp = cellDataGet("B2");
    var hum = cellDataGet("B1");
    var moi = cellDataGet("B3");
    
    replyMessage = '気温は大体'+temp+'度くらい、湿度は'+hum+'%ぐらいかな。';
    if(moi < 180 && moi > 150){
      replyMessage = replyMessage + 'ちょっと土乾いてきたかも。';
    }else if(moi < 149 && moi >100){
      replyMessage = replyMessage + 'ついでに言うと喉乾いて死にそうかな。';
    }else if(moi <100){
      replyMessage = '水...水...';
    }
  }
  /////////////////////////////////////////呼びかけ反応ここまで/////////////////////////////////////////
  
  UrlFetchApp.fetch(url, {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN,
    },
    'method': 'post',
    'payload': JSON.stringify({
      'replyToken': replyToken,
      'messages': [{
        'type': 'text',
        'text': replyMessage,
      }],
    }),
    });
  return ContentService.createTextOutput(JSON.stringify({'content': 'post ok'})).setMimeType(ContentService.MimeType.JSON);
}

CHANNEL_ACCESS_TOKENには、先ほどbotを作成したときに発行したトークンをコピペします。

USER_IDには、botのQRコードなどが載っているページの、下のほうにある「あなたのユーザーID」という部分の文字列をコピペします。

これで、スクリプトは完成です。

条件分岐を増やせば、もっと多彩な返答ができます。

ほかのAPIやサービスを組み合わせれば、天気予報を取得して雨をトリガーにすることなんかもできるでしょう。

また、ここではdoPost関数を使っていますが、doGet関数というのもあり、これはGASからESP8266へデータを送信できます。

LINEで呼びかけたら自動給水やLEDが光る、などなど色々な工夫ができそうですね。

スクリプトを公開

最後の作業として、これまでで作ったスクリプトをWebAPIとして公開し、LINEとESP8266に関連付けます。

スクリプトエディタの上のほうにある「公開」を押し、「ウェブアプリケーションとして導入」を押します。

Execute the app as: をMeにし、who has access to the app: をAnyoneにします。

上のURLがこのwebAPIのアドレスになりますのでコピーしておきます。IDの部分がユニークで、それ以外はどのスクリプトでも同じです。

ちなみに、スクリプトを編集すると前に公開したURLは無効になります。

厳密には微妙に使えちゃったりするのですが、正常に動作しません。

スクリプトを更新したらProject versionを開いて番号をnewにしてもう一度更新します。

LINEのwebHOOKにURLをコピペ

botのQRコードなどが載っているページで、Webhook設定というところを編集します。先ほど公開してコピーしたURLを貼り付けます。

検証というリンクを押し、問題が出なければ成功です。

ESP8266のスケッチにIDをコピペ

前回作成したスケッチのGScriptIdの部分に先ほどのURLのIDをコピペします。

const char* host = "script.google.com";
const int httpsPort = 443;
const char *GScriptId = "~~~~ここ!~~~";
//送信テキスト
const String url = String("/macros/s/") + GScriptId+ "/exec?value=Hello";
const String payload_base_cell =      "{\"command\": \"Update\", \"Humidity\": \"";
const String payload_base_Temperture =    "\",\"Temperture\": \"";

これですべての作業が完了です。

完成

完成したらスプレッドシートを見てみます。

セルの値が数秒毎に自動更新されるはずです。

私は水分の急激な上昇を判別して時刻を記録し、水やりの経過時間も更新するようにしました。

経過時間が大きくなっていくと健康Lvが下がり、それに応じて命乞いをします。

セルを単純に上書きするのではなく、ほかのスプレッドシートに追加していけば、その日一日の気温や湿度の変化をグラフにしたりもできそうですね!

handaru

handaru.net

handaru(はんだる)です。 工作が趣味です。

コメント

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です