2016/4/16

LINE BOT API で IoT

どうも、クラゲです。
LINE BOT API + Heroku + ESP8266 + 圧力センサ でIoTやってみました!
前半は語尾に"ござる"を付加するオウム返しチャットで、後半は圧力を受ける度に強さに応じたコメントを通知するアプリです。

入出力インターフェースがチャットであることと、かなりのユーザーがインストール済みであろうLINEアプリを使ってPUSH通知が行えるので、IoTとして期待できますね。将来的には人工知能や音声認識などと連携できると面白そう!

ではでは、作り方を紹介します!

【 コツ 】

1.Herokuでクレジットカードの登録が必要
いつの間にか引き落とされるのが怖い
-> Vプリカを使うと安心

2.LINEの設定反映が遅いかも
-> 設定してから数時間寝かすのがポイント

3.LINE bot APIへのGET送信
-> バックエンド側でGETしたら、事前に設定済みのユーザーのMID IDへPOSTするのがポイント

【 必要なハード 】

【 必要なソフト/サービス 】

【 必要なスキル 】

  • PCとインターネット環境ある人
  • Arduinoプログラム経験ある人
  • 忙しくない人

【 おおまかな作業の流れ 】

  • LINE BOT API Trial Account登録
  • Heroku登録
  • heroku toolbeltインストール
  • Fixie add on
  • バックエンド側プログラム作成
  • LINE BOT API設定
  • 回路作成
  • ESP8266プログラム作成

【 詳細 】

LINE BOT API Trial Account登録

こちらから登録します
https://business.line.me/services/products/4/introduction

画像引用元:business.line.me

特に難しい箇所はないです。先着1万名なのでお早めに!

Heroku登録

Herokuは一言で言うとPHPやRubyやNode.jsなどバックエンドアプリを無料でも使えちゃうWEBサービス
LINE BOT APIはここからRubyで制御します
https://www.heroku.com/

画像引用元:heroku.com

名前とメアド登録で簡単にユーザー登録できます

heroku toolbeltインストール

シリアルコンソールでHerokuコマンドを使えるようにするツール
https://toolbelt.heroku.com/

画像引用元:toolbelt.heroku.com

DLして実行すればインストール完了
その後、Hello,world的なアプリの作り方はこちらを参考にどうぞ!
参考:http://www.tam-tam.co.jp/tipsnote/program/post5199.html
以下のコマンドは何かと色々使います

$ git add .
$ git commit -m 'init'
$ git push heroku master

Fixie add on

LINE BOT APIを使うためにはサーバー側のIPアドレスが固定している必要があります
参考:http://qiita.com/yuya_takeyama/items/0660a59d13e2cd0b2516
そこで、HerokuのIPアドレスを固定させるFixieをadd onします

画像引用元:elements.heroku.com

HerokuのDashboardのResoureceからAdd-onsの下のテキストボックスに"Fixie"と打ち込んで進めてください

しかし、ここで問題が!

Fixieの無料プランを使えばタダなのですが、無料であってもadd onをするためにはHerokuにクレジットカードを登録しろと出てきます。
しかし、いつの間にか知らない間に課金されてお金が引き落とされることを考えると不安ですよね。。。
そこで、便利なのがプリペイド式のクレジットカードです!
いろいろ種類があると思いますが、クラゲが今回Heroku登録に成功したのはVプリカです。
http://vpc.lifecard.co.jp/

画像引用元:vpc.lifecard.co.jp

このカードは審査不要で即発行で、支払いもネットバンキングを使えば、即座に完了。物理的なカードは存在しません。
最も安くて700円(内200円は手数料)で作成可能です。
ただし、使ってないと月額少しずつ引かれるっぽいです。正直クレジットカードとしては手数料高すぎ。
でも損害を受けてもMAX500円以上取られることはないため、安心してサーバー系の勉強にも使えそうですね

Rubyプログラム作成

先人の知恵を拝借して、Rubyプログラムを作成します
参考:https://github.com/yuya-takeyama/line-echo
ここからDownload ZIPをしてこれをベースに作成します。
ちなみに、ソースコードそのままでオウム返しを試してみたいって人は、Deploy to Herokuというボタンを押すと簡単にフォークできます

app.rbを以下のように改造しました。
かなり冗長かもしれませんが、Ruby初プログラミングなので許してください。

# Mostly taken from http://qiita.com/masuidrive/items/1042d93740a7a72242a3 
# Modified by https://monomonotech.jp/kurage/linebotapi 
 
require 'sinatra/base'
require 'json'
require 'rest-client'
 
class App < Sinatra::Base
    #追加 
    get '/linebot/callback/level1' do
      request_content = { 
        #to: [msg['content']['from']], 
        to:[ENV["LINE_USER_MID"]],
        toChannel: 1383378250, # Fixed  value 
        eventType: "138311608800106203", # Fixed value 
        #content: msg['content'] 
        content:{ 
          contentType:1,
          toType:1,
          text:"すごいプレッシャー!"
        }
      }
 
      endpoint_uri = 'https://trialbot-api.line.me/v1/events'
      content_json = request_content.to_json
 
      RestClient.proxy = ENV['FIXIE_URL'] if ENV['FIXIE_URL']
      RestClient.post(endpoint_uri, content_json, { 
        'Content-Type' => 'application/json; charset=UTF-8',
        'X-Line-ChannelID' => ENV["LINE_CHANNEL_ID"],
        'X-Line-ChannelSecret' => ENV["LINE_CHANNEL_SECRET"],
        'X-Line-Trusted-User-With-ACL' => ENV["LINE_CHANNEL_MID"],
      })
    end
 
    get '/linebot/callback/level2' do
      request_content = { 
        #to: [msg['content']['from']], 
        to:[ENV["LINE_USER_MID"]],
        toChannel: 1383378250, # Fixed  value 
        eventType: "138311608800106203", # Fixed value 
        #content: msg['content'] 
        content:{ 
          contentType:1,
          toType:1,
          text:"結構な圧力受けてます!!"
        }
      }
 
      endpoint_uri = 'https://trialbot-api.line.me/v1/events'
      content_json = request_content.to_json
 
      RestClient.proxy = ENV['FIXIE_URL'] if ENV['FIXIE_URL']
      RestClient.post(endpoint_uri, content_json, { 
        'Content-Type' => 'application/json; charset=UTF-8',
        'X-Line-ChannelID' => ENV["LINE_CHANNEL_ID"],
        'X-Line-ChannelSecret' => ENV["LINE_CHANNEL_SECRET"],
        'X-Line-Trusted-User-With-ACL' => ENV["LINE_CHANNEL_MID"],
      })
    end
 
    get '/linebot/callback/level3' do
      request_content = { 
        #to: [msg['content']['from']], 
        to:[ENV["LINE_USER_MID"]],
        toChannel: 1383378250, # Fixed  value 
        eventType: "138311608800106203", # Fixed value 
        #content: msg['content'] 
        content:{ 
          contentType:1,
          toType:1,
          text:"つ、潰される・・・"
        }
      }
 
      endpoint_uri = 'https://trialbot-api.line.me/v1/events'
      content_json = request_content.to_json
 
      RestClient.proxy = ENV['FIXIE_URL'] if ENV['FIXIE_URL']
      RestClient.post(endpoint_uri, content_json, { 
        'Content-Type' => 'application/json; charset=UTF-8',
        'X-Line-ChannelID' => ENV["LINE_CHANNEL_ID"],
        'X-Line-ChannelSecret' => ENV["LINE_CHANNEL_SECRET"],
        'X-Line-Trusted-User-With-ACL' => ENV["LINE_CHANNEL_MID"],
      })
    end
 
 
  post '/linebot/callback' do
    params = JSON.parse(request.body.read)
 
    params['result'].each do |msg|
      request_content = { 
        to: [msg['content']['from']],
        toChannel: 1383378250, # Fixed  value 
        eventType: "138311608800106203", # Fixed value 
        #content: msg['content'] 
        content:{ 
          contentType:1,
          toType:1,
          text:msg['content']['text']+"でござる"#+msg['content']['from'] 
        }
      }
 
      endpoint_uri = 'https://trialbot-api.line.me/v1/events'
      content_json = request_content.to_json
 
      RestClient.proxy = ENV['FIXIE_URL'] if ENV['FIXIE_URL']
      RestClient.post(endpoint_uri, content_json, { 
        'Content-Type' => 'application/json; charset=UTF-8',
        'X-Line-ChannelID' => ENV["LINE_CHANNEL_ID"],
        'X-Line-ChannelSecret' => ENV["LINE_CHANNEL_SECRET"],
        'X-Line-Trusted-User-With-ACL' => ENV["LINE_CHANNEL_MID"],
      })
    end
 
    "OK"
  end
end

内容は主に以下の4つになっています
'/linebot/callback/level1' というGET送信が来たら、"すごいプレッシャー!"というテキストをLINE_USER_MID宛にPOST送信
'/linebot/callback/level2' というGET送信が来たら、"結構な圧力受けてます!!"というテキストをLINE_USER_MID宛にPOST送信
'/linebot/callback/level3' というGET送信が来たら、"つ、潰される・・・"というテキストをLINE_USER_MID宛にPOST送信
''/linebot/callback'というPOST送信が来たら、元のメッセージ+"でござる"というテキスト送信元にPOST送信

このままだと環境変数の中身がないので、Heroku上で以下の環境変数を設定します。

  1. LINE_CHANNEL_ID
  2. LINE_CHANNEL_MID
  3. LINE_CHANNEL_SECRET
  4. LINE_USER_MID

1-3の値はLINE developersのChannelsから取得できます

4の値については、後で取得するので、一旦空白にします

HerokuのDashboardから作成したAppsのSettingsを開いてください
"Reveal Config Vars"というボタンをクリックします

ここで環境変数と値を対で入力すればOKです

LINE BOT API設定

LINE developersのChannelsにてCallback URL設定を行います

Herokuで作ったアプリ名を入力。色々なサイトに書いてありますが、"https"であること、":443"を付けるのがポイントです。

Server IP WHitelistの設定

HerokuのアドオンFixieのページから調べてOutbound IPsの2つを上記に書く

LINEでこのbotと友達になって、何か話しかけてください。多分最初だけすぐには返事が来ないと思います。
どうやらLINE側の設定が反映されるのが遅いようです。
クラゲもここでハマりました。
全ての設定で記入ミスが無いことを確認して、数時間程度待ちます

正しく設定されていると、忘れた頃にオウム返しメッセージが届いています。
ちゃんとオウム返しができるようになったかLINEで確認してください。
できたら、前述4のLINE_USER_MIDを得るために、以下のコメントアウトしている部分を有効化して実行するとLINE上で取得できます

text:msg['content']['text']+"でござる"#+msg['content']['from'] 

それをHeroku上の環境設定で入力すればOK!

回路作成


左から、スマホ、FSR圧力センサー、ブレッドボード、ESP8266、一番右はUSBモバイルバッテリーです。
ESP8266はESP-WROOM-02 Arduino互換ボードを使用しています。

部品は以下の通り

床に平行にFSRを置きたかったので、オスメスジャンパーを使いました。FSRをブレッドボードに直接接続すれば、オスメスジャンパー線は無くても作ることは可能です。
なお、オスメスジャンパー線にFSRを挿すときのコツとして、引っかかるように挿すとそこそこ外れなくなります。

下の画像はArduion UNOですが、ESP-WROOM-02 Arduino互換ボードに置き換えて見てください

ちなみにESP8266はアナログ入力範囲が0~1.0Vまでですが、Arduino互換ボードでは中で分圧しているため、0〜3.3V入力がそのまま可能になっています。

ESP8266プログラム作成

ESP8266を触るのが初めてって人は、こちらなどを参考に設定してください
参考URL先のボードはArduino互換ボードではないですが、内容は同じです。
参考:https://www.mgo-tec.com/blog-entry-ss-wroom-howto01.html

では、本題のプログラム
SSIDとPassword、及びhostアドレスは各自の環境に合わせて入力してください。

#include <ESP8266WiFi.h>
 
const char* ssid     = "**************";
const char* password = "*******";
const char* host     = "**********.herokuapp.com";
 
void setup() {
  Serial.begin(115200);
  delay(10);
 
  // We start by connecting to a WiFi network 
 
  Serial.println();
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
 
  WiFi.begin(ssid, password);
 
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
 
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}
 
int fsr_array[10];
int counter = 0;
 
void loop() {
  int fsr_ave = 0;
  int fsr = analogRead(A0);
 
  if (fsr <= 1000)
  {
    if (counter < 10)
      fsr_array[counter] = fsr;
 
    counter++;
 
    if (counter == 10)
    {
      for (int i = 0; i < 10; i++)
        fsr_ave = fsr_ave + fsr_array[i];
 
      fsr_ave = fsr_ave / 10;
      Serial.println(fsr_ave);
 
      String url = "";
      if (fsr_ave <= 300)
      url += "/linebot/callback/level3";
 
      else if (fsr_ave <= 600)
      url += "/linebot/callback/level2";
 
      else if (fsr_ave <= 1000)
      url += "/linebot/callback/level1";
 
      if (url != ""//ここでGET送信 
      {
        Serial.print("connecting to ");
        Serial.println(host);
 
        // Use WiFiClient class to create TCP connections 
        WiFiClient client;
        const int httpPort = 80;
        if (!client.connect(host, httpPort)) {
          Serial.println("connection failed");
          return;
        }
 
        Serial.print("Requesting URL: ");
        Serial.println(url);
 
 
        // This will send the request to the server 
        client.print(String("GET ") + url + " HTTP/1.1\r\n" +
                     "Host: " + host + "\r\n" +
                     "Connection: close\r\n\r\n");
        delay(10);
 
        // Read all the lines of the reply from server and print them to Serial 
        while (client.available()) {
          String line = client.readStringUntil('\r');
          Serial.print(line);
        }
 
        Serial.println();
        Serial.println("closing connection");
      }
 
    }
  }
  else
    counter = 0;
 
  delay(100);
}

プログラムの内容ですが、圧力センサの値をアナログで約100msec周期で入力しています。
ある一定以上の圧力が生じたら、1秒間で10回分の圧力A/D値を取得してその平均値を出します。
平均値が601〜1000の場合は"Level1"、301〜600の場合は"Level2"、0〜300の場合は"Level3"としてHerokuへGET送信しています。
(圧力A/D値の小さい方が圧力が高い。1001以上の場合は何もしない)

ちなみに、WiFiのGET送信部分はこちらのプログラムを参考にさせていただきました
参考:http://tomono.eleho.net/2015/07/26/5769/

以上、LINE BOT APIでIoTでした!