LINEでおうちの鍵を開け締めする

#背景
おうちハックの一つとして、スマートロックがあります。

**先人の例**
[Raspberry Piでスマートロックをつくった](https://qiita.com/undo0530/items/669b6ee6b2277ea64b33)

アキバに行ったときに900円でジャンク品のFelicaリーダ(SONY RC-S330)が売っていたので筆者も作ってみました。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">スマートロックシステムついに完成 <a href="https://t.co/vZSmr9icfz">pic.twitter.com/vZSmr9icfz</a></p>&mdash; ふな (@funaki_0814) <a href="https://twitter.com/funaki_0814/status/1036607319075782656?ref_src=twsrc%5Etfw">2018年9月3日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

ICカードを読み取り機にかざすことでサーボモータが回転して鍵が開け締めできるようになりました。

これだけだと面白くないのでアプリから操作できるようにしたいなーと思ったけどいちからアプリ作ると開発コストかかりそうなので身近なLINE使って操作できないかやってみました。

#使ったもの
##ハード
・Raspberry Pi 3 Model B
・サーボモータ(SG92R)
・LINEがインストールされたスマフォ
##ソフト
・Python3
・LINE Messaging API
・ngrok

#構成
全体の構成としてはこうなりました。
![smartlock.jpg](https://qiita-image-store.s3.amazonaws.com/0/160730/7ba31a5b-f0b5-dd33-781b-45545ea3bbec.jpeg)

#手順
##LINE Messaging APIのセットアップ
https://developers.line.me/ja/
にてLINEアカウントでログインします。

プロバイダーリストが出てくるので新規で適当に名前つけてプロバイダーを作成します。
![キャプチャ.JPG](https://qiita-image-store.s3.amazonaws.com/0/160730/2ff74065-91d6-7c64-d860-5fd3deafd026.jpeg)

新規チャネル→Messaging APIから操作用のLINEチャネルを作成します。
プランはdeveloper trialで良いかと
![キャプチャ.JPG](https://qiita-image-store.s3.amazonaws.com/0/160730/c689b94b-a6c3-a39a-9dda-a35719d46158.jpeg)

このような画面になります。下の方にあるQRコードで友だち追加出来ます。
Channel Secretとアクセストークンは後で使うのでメモしときます。

![キャプチャ.JPG](https://qiita-image-store.s3.amazonaws.com/0/160730/f4557581-a5b9-372d-95fb-963af563e434.jpeg)

##Raspberry Piのセットアップ

[LINE Messaging APIをngrok経由でRaspberry Pi 3で使ってみる（サンプルbot編）](https://hakengineer.xyz/2017/10/10/post-425/)
このサイトが詳しいので、Raspberry PiにLINE API側からアクセスするためのngrok、各種SDKをセットアップします。

セットアップ後、先程追加したアカウントに話しかけてみてオウム返しができればOKです。
![キャプチャ.JPG](https://qiita-image-store.s3.amazonaws.com/0/160730/eec35b66-7f56-93fb-799d-6b6f946ef641.jpeg)

##特定のキーワードに反応してサーボモータを回す。
https://github.com/line/line-bot-sdk-python/blob/master/examples/flask-echo/app.py
を少し改良して、特定のキーワードに反応してサーボモータを回転させるようにします。

```python:app.py

# -*- coding: utf-8 -*-

#  Licensed under the Apache License, Version 2.0 (the "License"); you may
#  not use this file except in compliance with the License. You may obtain
#  a copy of the License at
#
#       https://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#  License for the specific language governing permissions and limitations
#  under the License.

from __future__ import unicode_literals

import os
import sys
import function
from argparse import ArgumentParser

from flask import Flask, request, abort
from linebot import (
    LineBotApi, WebhookParser
)
from linebot.exceptions import (
    InvalidSignatureError
)
from linebot.models import (
    MessageEvent, TextMessage, TextSendMessage,
)

app = Flask(__name__)

# get channel_secret and channel_access_token from your environment variable
channel_secret = os.getenv('LINE_CHANNEL_SECRET', None)
channel_access_token = os.getenv('LINE_CHANNEL_ACCESS_TOKEN', None)
if channel_secret is None:
    print('Specify LINE_CHANNEL_SECRET as environment variable.')
    sys.exit(1)
if channel_access_token is None:
    print('Specify LINE_CHANNEL_ACCESS_TOKEN as environment variable.')
    sys.exit(1)

line_bot_api = LineBotApi(channel_access_token)
parser = WebhookParser(channel_secret)


@app.route("/callback", methods=['POST'])
def callback():
    signature = request.headers['X-Line-Signature']

    # get request body as text
    body = request.get_data(as_text=True)
    app.logger.info("Request body: " + body)

    # parse webhook body
    try:
        events = parser.parse(body, signature)
    except InvalidSignatureError:
        abort(400)

    # if event is MessageEvent and message is TextMessage, then echo text
    for event in events:
        if not isinstance(event, MessageEvent):
            continue
        if not isinstance(event.message, TextMessage):
            continue

        message = event.message.text
        reply = event.message.text
        if message.count('開錠'):
            function.unlock()
            reply = "開錠しました"
        elif message.count('施錠'):
            function.lock()
            reply = "施錠しました"

        line_bot_api.reply_message(
            event.reply_token,
            TextSendMessage(text=reply)
        )

    return 'OK'


if __name__ == "__main__":
    arg_parser = ArgumentParser(
        usage='Usage: python ' + __file__ + ' [--port <port>] [--help]'
    )
    arg_parser.add_argument('-p', '--port', type=int, default=8000, help='port')
    arg_parser.add_argument('-d', '--debug', default=False, help='debug')
    options = arg_parser.parse_args()

    app.run(debug=options.debug, port=options.port)
```

モータを回転させるための関数

```python:function.py
# -*- coding: utf-8 -*-
import RPi.GPIO as GPIO
import time

# 鍵をかけるようにモータを回転
def lock():
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(gp_out, GPIO.OUT)
    servo = GPIO.PWM(gp_out, 50)
    servo.start(0.0)
    servo.ChangeDutyCycle(9.5)
    time.sleep(0.5)
    GPIO.cleanup()


# 鍵をあけるようにモータを回転
def unlock():
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(gp_out, GPIO.OUT)
    servo = GPIO.PWM(gp_out, 50)
    servo.start(0.0)
    servo.ChangeDutyCycle(5.5)
    time.sleep(0.5)
    GPIO.cleanup()
```

これで再度app.pyを実行して、先程のアカウントに開錠or施錠と話すとモータが作動します。

##リッチメニューの作成
よくLINE＠のアカウントだとリッチメニューが使えたりします。
↓こんなの
![Screenshot_20180930-170206.png](https://qiita-image-store.s3.amazonaws.com/0/160730/1b8e7579-a323-bdb1-0d6c-831aed3586ea.png)

開錠、施錠っていちいち送るの面倒くさいのでワンタップで送れるようにします。

https://admin-official.line.me/
LINE@Managerにログインします。作ったアカウントが表示されています。

![キャプチャ.JPG](https://qiita-image-store.s3.amazonaws.com/0/160730/345da63d-07ff-701a-4047-7f2ca918d601.jpeg)

サイドメニューのリッチコンテンツ作成→リッチメニューから新規作成でリッチメニューを作成します。

![キャプチャ.JPG](https://qiita-image-store.s3.amazonaws.com/0/160730/0c5e968f-8c3e-09bb-c64c-5ff1b1ff86ac.jpeg)

ボタンの数を選択し、デザインガイドにそってメニューを作成します。
ボタンを押したときに送ってほしいテキストを設定します。
![キャプチャ.JPG](https://qiita-image-store.s3.amazonaws.com/0/160730/1c30fb2f-8a5a-515e-e636-1e9f18dd9419.jpeg)

とりあえずこんなの作りました。
![richmenu.jpg](https://qiita-image-store.s3.amazonaws.com/0/160730/c93160b3-a5a4-8540-2882-281f84c6e542.jpeg)

これで再度LINEを開くと、リッチメニューが表示されるようになります。
![Screenshot_20180930-170305.png](https://qiita-image-store.s3.amazonaws.com/0/160730/6038bd65-ec8b-a63f-949f-624d9f9b76c1.png)

#できたもの
<blockquote class="twitter-tweet" data-conversation="none" data-lang="ja"><p lang="ja" dir="ltr">LINEで操作できるようにした <a href="https://t.co/BtJk7rqfn1">pic.twitter.com/BtJk7rqfn1</a></p>&mdash; ふな (@funaki_0814) <a href="https://twitter.com/funaki_0814/status/1046253793841623041?ref_src=twsrc%5Etfw">2018年9月30日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


#まとめ
・LINE Messaging APIのサンプルが充実していたので思ったより簡単に出来た。
・ngrokのおかげでAPIからルータ超えしてRaspberry Piへアクセスしやすかった。

問題点としては友だち追加してれば誰でも鍵開けられるのと、ngrokのURLが起動するたびにかわるのでいちいちAPI側に設定し直さないといけないことですかね・・・
そのうち証明書取得してRaspberry Piにダイレクトにアクセスできるようにしたいです。