以前の記事でRaspberry Piをスマートリモコンにすることができたので,最終的にはそれをLINEから動かせたら良いなと思い,やってみました.
要するに,LINEから自室の電気をつけたりエアコンの電源を入れたりできるようにしました,ということです.
何をするのか
具体的には以下のようなステップでLINE↔Raspberry Piの連携を行いたいです.
あまり詳しくないなりに知識を絞り出して細かめにステップを分けました.
- LINEのBotに自分のLINEアカウントからメッセージを送信する
- WebhookでRaspberry Pi上のWebサーバにhttpリクエストをPOSTする
- Webサーバ上でhttpリクエストを受け取り,リクエストの内容に応じてローカルのシェルを実行する
- WebサーバからLINE messaging APIの返信用URLにhttpリクエストをPOSTする
- 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を受け取り,中身の
req
が0
であれば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リクエスト実行
controllSmartRemoteController
でdata/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からエアコンの電源を入れておけたりするのは良いですよね.今回やらなかったこと
- 最低限のロジックの実装しかしていないためセキリュティやエラー処理をしていない
- X-line-sigunitureによるチェック等が必要
- 指示を出したら即実行だが,予約もできるようにしたい
- httpリクエストを受け取ったらcronにタスクを登録する
まとめ
bottleは簡単
ngrokは便利
IoTは楽しい
反省
LINEのwebhookがSSL認証のみだと知り,
LINE messaging API → Google Apps Script → Web サーバという経路を最初は実装していた.ngrokという超便利ツールを知るのが遅かった(調査不足)