Bottle.pyでRaspberry PiをWebサーバにしてLINEと連携させてみた

以前の記事でRaspberry Piをスマートリモコンにすることができたので,最終的にはそれをLINEから動かせたら良いなと思い,やってみました. 要するに,LINEから自室の電気をつけたりエアコンの電源を入れたりできるようにしました,ということです.

何をするのか

具体的には以下のようなステップでLINE↔Raspberry Piの連携を行いたいです. あまり詳しくないなりに知識を絞り出して細かめにステップを分けました.

system.png

  1. LINEのBotに自分のLINEアカウントからメッセージを送信する
  2. WebhookでRaspberry Pi上のWebサーバにhttpリクエストをPOSTする
  3. Webサーバ上でhttpリクエストを受け取り,リクエストの内容に応じてローカルのシェルを実行する
  4. WebサーバからLINE messaging APIの返信用URLにhttpリクエストをPOSTする
  5. Botが自分に何をしたのか報告する

正直WebhookとかWebサーバとかなんとなくでしかわからなかったので始める前にいろいろ読みました.

WebAPIについての説明

Webhookとは?

それではやっていきましょう. やることは大まかに分けて

  • Raspberry PiにWebサーバ構築
  • LINE Botの作成・Webサーバとの連携

です. 自分の勉強も兼ねているので以下は時間軸で書いてあります.一つ一つやっているので長くなってますが悪しからず.

#用意するもの

  • Raspberry Pi 3 model B
    • 手元にあったのがこれなだけでほかのモデルでもOKです.
  • Macbook (作業用PC)
    • あれば何でもOKです.あったほうがLINE developper登録やファイルの編集がスムーズに行えると思います.
  • RaspberryPi内の何らかのシェルスクリプト
    • 僕の場合は以前作ったスマートリモコン操作スクリプトですが,これは何でも良いと思います.なくてもLINEの返信までは実装できます.

使ったものまとめ

  • python 3.5.3
  • bottle 0.12
  • ngrok 2.3.25
  • LINE messaging API

Raspberry PiにWebサーバ構築

テストサーバの作成

さて,Raspberry Pi上にWebサーバを構築する方法はこの世の中に数多に存在します. しかし,私はそのあたりの知識がまったくありません. そこで,タイトルにもあるようにBottleというPython用の軽量なWebアプリケーションフレームワークを使ってWebサーバーを作りたいと思います.どうやら簡単にWebサーバを構築できるみたいです. この記事を参考にとりあえず簡単なhttpリクエストを処理できるテストWebサーバを作ってみます.

以下,home/pi/smartController/というディレクトリで作業することにします.

wget https://bottlepy.org/bottle.py でsmartController/下にbottle.pyというファイルがダウンロードされます. 次にindex.pyというファイルを作ります. このファイル1つでサーバを立ち上げ,httpリクエストの処理を行わせることができます. まずは単純にhttpリクエストを処理させるだけのサーバを立ち上げてみましょう. nano index.pyより,以下のように編集します.

#!/bin/env python
# coding: utf-8

import json
from bottle import route, run, request, HTTPResponse, template, static_file

@route('/test', method='POST')
def test() :
    var = request.json
    retBody = {}
    if var["req"] == "0":
        retBody["res"] = "Good"
    elif var["req"] == "1":
        retBody["res"] = "Bad"
    else :
        retBody["res"] = "No request"
    
    r = HTTPResponse(status=200, body=retBody)
    r.set_header("Content-Type", "application/json")
    return r

def main():
    print('Server Start')
    run(host='0.0.0.0', port=8080, debug=True, reloader=True)
    # run(host='0.0.0.0', port=8080, debug=False, reloader=False)

if __name__ == '__main__':
    main()
  • main 関数
    • サーバを指定したポートで立ち上げます.
  • test 関数
    • httpリクエストを処理する関数
    • @routeによって公開されるAPIの処理を決めることができます.ここではエンドポイントのURLと種類(GET/POST)を指定しています
    • ここではテストとしてjsonを受け取り,中身のreq0であればGoodを,1であればBadを,それ以外であればNo requestをjsonのresに格納してレスポンスを返します.

フォルダ構成は以下のとおりです.

home/
 └ pi/
  └ smartController/
    ├ index.py
    └ bottle.py

それではWebサーバを起動してみます. Raspberry Pi上でpython3 index.pyを実行するとサーバが立ち上がります.

curl コマンドを使って今立てたサーバにhttpリクエストを送り,動いているか確認しましょう.(これはRaspberry Piからでも同一LAN内の作業用PCからでも良いです)

curl -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{"req":"0"}' http://Raspberry PiのIPアドレス(192.168.xxx.xxx):8080/test
curl -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{"req":"1"}' http://Raspberry PiのIPアドレス(192.168.xxx.xxx):8080/test
curl -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{"req":"2"}' http://Raspberry PiのIPアドレス(192.168.xxx.xxx):8080/test

を実行して,それぞれ

{"res": "Good"}
{"res": "Bad"}
{"res": "No request"}

が返ってくればOKです. WebAPIを作るのってハードル高いと思ってましたが,簡単なリクエスト処理するぐらいなら結構簡単にできるんですね.Bottleに感謝

Webサーバを外部からアクセスできるようにする

さて,同一LANからのhttpリクエストは処理できるようになりましたが,これではまだLINEのwebhook機能から呼ぶことはできません.外部のネットワークからもhttpリクエストを送れるようにしましょう. さらに,LINE BotのWebhook機能はSSLによる接続に限る(オレオレ証明書でもダメ)らしいです. 「こんなことのために証明書発行するのは面倒だな……簡単にhttps用意してくれるのないかなー」と思って調べているとngrokという超便利な無料で使えるサービスを発見しました.

ngrokが便利すぎる が簡単に説明してくれています 簡単にいうと、ローカルPC上で稼働しているネットワークサービスを外部公開できるサービス.しかもngrokはSSL認証の部分を肩代わりしてくれる!!これは便利ですね. ということで早速使ってみます.

ngrokのサイトに行き,Get started for Freeより登録します(GithubやGoogleのアカウントが使えます) Raspberry pi上で

$ wget https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-arm.zip
$ unzip ngrok-stable-linux-arm.zip
$ sudo mv ngrok /usr/local/bin/
$ ngrok version

を実行しましょう.versionが確認できればOKです.(今回は2.3.25) 登録すると一番最初の画面に出てくるauthtokenの紐づけも行っておくと良いでしょう.

$ ngrok authtoken <自分のauthtoken>

あとは簡単です.Webサーバを立ち上げているlocalhostの8080ポートを外部からアクセスさせたいので,

ngrok http 8080

と実行します. どこのアドレスにアクセスするとlocalhost:8080につながるかがForwardingの部分に出力されます. ちゃんとhttpとhttpsの両方ありますね. ちなみにsshログインが切れると実行したプログラムが止まってしまうという問題に対しては,screenという仮想端末作成コマンドで解決できます.以下の記事でサラッと述べられています. 【Node.js】 RaspberryPiのプログラムを自動起動・永続化・SSH ログアウト後もプロセスを残す

ではhttpリクエストを送ってみましょう.

curl -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{"req":"0"}' https://<ngrok起動時に表示されたアドレス>/test
{"res": "Good"}

が返ってくればOKです.

LINE Botの作成・Webサーバとの連携

LINE Botを作る

といってもこれはLINE Botのwebhook機能を使えば簡単です. Google Apps Scriptを使ってLINE Messaging APIでオウム返しをする を参考にデベロッパー登録・プロバイダ作成・チャンネル作成・自分のアカウントと友達登録までをしてみてください.

Webサーバと連携させる

上で作成したチャンネルの設定のところから, - アクセストークンの発行 - Webhookの利用:「する」に設定 - Webhook URL :先程ngrokで表示されたhttpsのアドレスを設定(httpではダメ) しましょう.

LINEからのwebhookに対応するために最初に書いたindex.pyを以下のように書き換えます.

#!/bin/env python
# coding: utf-8

import json
from bottle import route, run, request, HTTPResponse, template, static_file,ServerAdapter
import os
import pandas as pd
import requests

@route('/static/:path#.+#', name='static')
def static(path):
    return static_file(path, root='static')

# チャンネルのアクセストークン
ACCESS_TOKEN = "<自分のチャンネルのアクセストークン>"
# 応答メッセージ用のAPI URL
url = 'https://api.line.me/v2/bot/message/reply'

@route('/')
def root():
    return template("index")

@route('/', method='POST')
def handleEvent() :
    req = request.json["events"][0]
    # WebHookで受信した応答用Token
    replyToken = req["replyToken"]
    # ユーザーのメッセージを取得
    userMessage = req["message"]["text"]

    replyText = controllSmartRemoteController(userMessage)
    requestForReply(replyToken,replyText)
    r = HTTPResponse(status=200, body={})
    return r

def requestForReply(replyToken,replyText) :
    postData = {
      'replyToken': replyToken,
      'messages': [{
        'type': 'text',
        'text': replyText,
      }],
    }
    headers = {
        'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + ACCESS_TOKEN,
    }
    result = requests.post(url,data=json.dumps(postData).encode("utf-8"),headers=headers)

# return response message to me
def controllSmartRemoteController(commandtext):
    #対応する命令を取得
    command = ""
    actionData = pd.read_csv("data/actionData.csv")
    for i,value in actionData.iterrows():
        if commandtext == value[0]:
            reply = value[1]
            command = "sh " + value[2]
    
    if not command:
        return "他の命令をしてください!"
    result = os.system(command)
    if result == 0:
        return reply
    else :
        return "失敗しました....." 

def main():
    print('Server Start')
    run(host='0.0.0.0', port=8080, debug=True, reloader=True)
    # run(host='0.0.0.0', port=8080, debug=False, reloader=False)

if __name__ == '__main__':
    main()

関数の説明

  • handleEvent
    • LINE messaging APIからのリクエストを受け取って返す.
  • controllSmartRemoteController
    • ユーザのメッセージに対する実行コマンドを探してRaspberry Pi上で実行
  • requestForReply
    • ユーザ(自分)に返信を行うためのhttpリクエスト実行

controllSmartRemoteControllerdata/action.csvにアクセスしていますがこのファイルは

メッセージ 対応するリプライ 実行するシェルのパス
おやすみ おやすみなさい.電気を消します. /home/pi/smartController/shell/sample.sh

のような形式のcsvファイル(ヘッダーあり)です. 自分の出したい指令とそれに対する応答,実行したいシェルのパスをじゃんじゃん追加していきましょう. ちなみにRaspberry Piから電気を消す等のshellについてはこの記事に乗せているのでそちらも参考にしてみてください. 最終的なフォルダ構成はこんな感じですね.

home/
  └ pi/
    └ smartController/
       ├ index.py
       ├ bottle.py
       ├ data
       |   └ actionData.csv
       └ shell
           └ sample.sh

これでLINEに追加したSmartControllerくんにメッセージを送るだけで自宅の電気を消せます!ちゃんとメッセージも返ってくるのでOKですね(画面キャプチャからではわかりませんがメッセージが帰ってくるときに部屋の電気も消えています!笑)

あとはactionData.csvに好きなコマンドを随時追加して,いろんなものをコントロールできるようにしてみましょう. まだ春ですが夏は暑くなるので,自宅に帰る前にLINEからエアコンの電源を入れておけたりするのは良いですよね.

今回やらなかったこと

  • 最低限のロジックの実装しかしていないためセキリュティやエラー処理をしていない
  • 指示を出したら即実行だが,予約もできるようにしたい
    • httpリクエストを受け取ったらcronにタスクを登録する

まとめ

bottleは簡単 ngrokは便利 IoTは楽しい

反省

LINEのwebhookがSSL認証のみだと知り, LINE messaging API → Google Apps Script → Web サーバという経路を最初は実装していた.ngrokという超便利ツールを知るのが遅かった(調査不足)

Takeru HASHIMOTO
Master degree Student of the University of Tokyo

My research interests include Human-Computer Interaction and Human-Augumentaion.

Related