[Raspberry Pi + Slack + LINE] 外出先から鍵閉めたか確認できるようにしてみた

# 0. はじめに
僕も含め家族がよく鍵を締めたか外に出てから心配になって戻りがちなので、外出先で鍵締めたか確認できるようしてみました。[^syogatu]


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596310/cc67c42c-7c25-8f0e-eaa5-37063811d5ab.png" alt="tojimari_door1.png" width=50%>

できるだけコストをかけず、手元にある機器で作りたかったので余っていた**Raspberry Pi**を使って、玄関において外出先から扉を撮影する設計にしました。

また、家族なども使うため、操作がわかりすいことが必要だったので、**LINE Bot**を使ったインターフェースを設計します。

# 1. 設計
### 要件
- Line Botに「鍵見て」と呼びかけると起動する
- 玄関に置いたRaspberryPiが写真を撮る
- 鍵のところが撮影されてLineで送られてくる

### 構成
LINE BotとRaspberry Piを双方向に直接やり取りする方法はありません。
また、Raspberry PiからLINEにはLINE Notifyで簡単かつ安全に接続できますが、LINEからRaspberry Piは少々工夫をしてやる必要があります。

そもそも、LINEから飛んでくるリクエストを確認するためにはAPIを公開する必要があります。

一番手っ取り早いのは、Raspberry Piを直接Webサーバにしてしまう方法です。しかし、この方法は無防備のサーバーを剥き出しにするのでセキュリティリスクが高すぎます。
AWSやAzureといったクラウドサービスを挟む方法も考えられますが、ランニングコストがかかるのでこの方法は採用したくないです。

そこで、**Google Apps Script(GAS)**と**Slack bot**を挟んでRaspberry Piにリクエストを投げるようにします。[^slack]
ここまでの構成をまとめると以下のようになります。
![sekkei.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596310/685088d3-bb50-ee88-05eb-8343a0bc409e.png "設計図")

# 2. LINE botを作る
### 準備
[LINE Developers](https://developers.line.biz/ja/)にアクセスし、ログインします。LINEアカウント持ってない場合はアカウントを作ってください。
ログインができたら開発者情報をポチポチ登録して、「**新規プロバイダー作成**」からプロバイダーを作成します。その後、「**Messaging API**」を選択してからチャネルを作成します。
![スクリーンショット 2021-01-04 153732.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596310/05afff51-c77b-eda3-3159-21018c10877e.png)

チャネルが作成されたら、「**Messaging API設定**」の中にある「**チャネルアクセストークン**」を発行します。(後でこのトークンが必要になります。)

:::note alert
悪用されるのを防ぐためにこのトークンは公開してはいけません。
:::

![スクリーンショット 2021-01-04 155051.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596310/3599e1b9-6da2-a61c-4aec-b6ce26f481dc.png)

### GASを作成
[Googleドライブ](https://drive.google.com/drive/)にアクセスして、「新規＞その他」から**Google Apps Script**を選択します。表示が無ければ、「アプリを追加」から検索して追加してください。

GASはJavaScriptで記述します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596310/59a129dd-acb0-0a15-a4e4-0286bd510606.png)

プロジェクト名を適当に入れて、以下のスクリプトを入れます。このコード中の`hello()`関数を呼び出してるあたりに関数を追加して開発していきます。

とりあえず、アクセストークンのところを張り付けて、保存します。

```bot.js
function doPost(e) {
  var replyToken= JSON.parse(e.postData.contents).events[0].replyToken;
  if (typeof replyToken === 'undefined') {
    return;
  }

  var url = 'https://api.line.me/v2/bot/message/reply';
  var channelToken = 'ここにアクセストークン（ロングターム）を貼り付け';

  var event = JSON.parse(e.postData.contents).events[0];
  var source = event.source;
  var input = event.message;

  var messages = null;
  
  //■■■■　この中に機能を追加していく　■■■■
  if(input.type == 'text') {
    messages = hello();
  }

  UrlFetchApp.fetch(url, {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + channelToken,
    },
    'method': 'post',
    'payload': JSON.stringify({
      'replyToken': replyToken,
      'messages': messages,
    }),
  });
  return ContentService.createTextOutput(JSON.stringify({'content': 'post ok'})).setMimeType(ContentService.MimeType.JSON);
}

function hello(){
  var messages = [{
    'type': 'text',
    'text': 'こんにちは',
  }];

  return messages;
}
```

### ひとまず公開
右上にある「**デプロイ>新しいデプロイ**」を押して公開します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596310/b986f331-9a0a-cdbc-f613-6ff669bd483b.png)
種類の選択から、ウェブアプリをえらんでアクセスできるユーザーを「**全員**」にしてデプロイします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596310/d2385142-ece0-b1f0-a1a5-4a522cebbe3c.png)
初回はアクセスの承認が必要になると思うので、承認しましょう。おそらく、英語や日本語で「このアプリは確認されていません」[^allow]と言われると思うので「詳細を表示」/「Advance」から自分のアプリケーションに移動して許可します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596310/0db6a7a1-723b-85fc-2c85-c36357c0a17b.png)

「デプロイを更新しました。」と表示されていれば成功です。
**ウェブアプリのURL**をコピーしておきます。

### LINEと連携
作成したGASをLINEと連携させます。「Messaging API設定」のページに戻って「**Webhook設定**」を更新していきましょう。
<dl>
  <dt>Webhook URL</dt>
  <dd>GASのウェブアプリケーションのURLを貼り付け</dd>
  <dt>Webhookの利用</dt>
  <dd>利用する</dd>
</dl>

![LINE2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596310/8f38e4b0-67a2-bed1-c8f4-965a2136aecc.png)

次にその下にある「**LINE公式アカウント機能>応答メッセージ**」を編集します。
<dl>
  <dt>応答メッセージ</dt>
  <dd>オフ</dd>
  <dt>Webhook</dt>
  <dd>オン</dd>
</dl>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596310/94818240-8f3e-2f10-92d8-ce42e2635072.png)

**設定は以上です！** 一度動くか試してみましょう！
QRコードを読み取ってテストしてみてください。なにか呼びかけると「こんにちは」と帰ってきたら成功です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596310/5a51fd24-b87f-2f6d-ba49-168b57005513.png)

### 応答範囲を限定する
LINEbotが他人に友達登録されると、鍵の様子を他人が確認できるようになってしまいます。
セキュリティ的にいい方法ではありませんが、**送信元のユーザーIDもしくはグループID**を調べて、それら以外のIDからのリクエストには応答しないようにします。

先ほどのコードの中に、`source`という変数を用意しているので、こいつを使ってIDを判別しようと思います。

まず、`//■■■■　この中に機能を追加していく　■■■■`書いてあったあたりのコードを改変して、IDをそのまま返すようにしてデプロイしましょう。
下記コードではグループIDを判別するようにしています。sourceの中身は[ドキュメント](https://developers.line.biz/ja/reference/messaging-api/#common-properties)を参照してください。

:::note
このあとに、「LINE Notify」と同じグループに入れる必要があるので、グループを作ってBotを招待しておくのをおすすめします。
:::

更新は右上のデプロイから「**新しいデプロイ**」を押して、「**デプロイ**」を押します。

:::note warn
このとき、保存してからデプロイするのを忘れないようにしましょう。
:::


``` .js
//■■■■　この中に機能を追加していく　■■■■
if (source.type == 'group' && source.groupId == 'ここにID'){
  if(input.type == 'text') {
    messages = hello();
  }
} else {
  messages = [{
    'type': 'text',
    'text': 'id:' + source.groupId,
  }];
}
```
デプロイしてから呼びかけるとIDが返ってくると思うので、そのIDをif文のなかに記述してもう一度デプロイします。
これで、いま入力したID以外からのリクエストを受け付けなくなりました。(入力したID以外からにはIDを返す処理になってます)

ひとまず、LINEbotの開発はひと段落です。

# 3. Slack botを作る
### 導入
次に、Slack botを作っていきましょう。
適当なワークスペースを作成し(もちろん既存のワークスペースも可)、そのワークスペースに[ボットインテグレーションの作成](https://my.slack.com/services/new/bot)からbotを作成します。

Raspberry PiはIP固定しておいてください。(固定方法はここを[参照](https://mugeek.hatenablog.com/entry/2019/05/27/230256))[^ras]

Raspberry Piにslackbotをpipで導入します。

```
$ sudo pip3 install slackbot
```

### ひとまずSlack Botを動かす
Slack botを動かすために必要なファイルを作っていきましょう。

```
$ mkdir bot
# 好きな名前のファイルを作ってください。ここでは'bot'にします。
$ cd bot
$ touch run.py
$ touch slackbot_setting.py
$ mkdir plugins
$ touch ./plugins/__init__.py
```
ファイル構造は下のようになっているはずです

```
bot/             # 任意の名前
├─ run.py        # 起動時に使う
├─ slackbot_settings.py   # botに関する設定ファイル
└─ plugins/               # botの機能はこの配下に追加していく
   ├─ __init__.py         # モジュールを示すためのファイル。空ファイル。
   └─ test.py             # 任意の名前（まだない）
```

各ファイルを編集していきましょう。
`slackbot_settings.py`には先ほど作ったSlackのトークンを入れましょう。

``` slackbot_settings.py
API_TOKEN = '<BOTのトークン>'

# デフォルトの返答
default_reply = 'わからん'

# プラグインを記述するディレクトリ名
PLUGINS = ['plugins']
```

`test.py`を`plugins/`の配下に作って行きましょう。

``` plugins/test.py
from slackbot.bot import respond_to, listen_to
import re

# おはようテスト
@listen_to('おはよう')
@respond_to('おはよう')
def ohayou(message, *something):
    message.send('おはよう') # 投稿
    #message.reply('おはよう') # メンション付き投稿
```

`@listen_to`デコレータを付けた関数は、botが参加したチャンネルに、**botにメンションされなかった投稿**に、引数の文字列が含まれるときに反応します。
逆に`@respond_to`デコレータを付けた関数は、**botに向けた投稿**に、引数の文字列が含まれるときに反応します。
どちらもつけるとチャンネル内の投稿すべてに対応できます。

最後に、起動用の`run.py`を作りましょう。

``` run.py
from slackbot.bot import Bot


def main():
    bot = Bot()
    bot.run()


if __name__ == "__main__":
    print('start slackbot')
    main()
```

これで動かす準備は整いました。実行してみましょう。

```
$ python3 run.py
```

エラーなく'start slackbot'と表示されていれば行けていそうです。
ダイレクトメッセージかBotを任意のチャンネルに招待して、Slack上で問題なく「おはよう」が帰ってくれば成功です。
![Screenshot from 2021-01-05 19-04-41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596310/0cc9b708-80f2-b06d-13ca-02473ec77d6c.png)

### GAS-Slackの連携
ここまでで下図の緑色になっているところまでが開発が終わりました。次に④のところの連携をします。
![a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596310/07d4f57a-9680-57a5-e9b0-2b02e1fd8b43.png)

まずはSlackの**Incoming WebHooks**を登録し、前章で作成したSlack BOTがいるチャンネルに投稿するように設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596310/d695e764-ae6c-9d83-f192-bfb32a8af629.png)

これで下準備は完了です！

### GASの改変

すでに作っているGASのスクリプトにSlackに投稿する関数を追加します。
ここでは、「やあ」と呼びかけたら、「おはよう」とSlackに投げるようにします。

:::note warn
SlackのWebhoolURLを入れるのを忘れないようにしましょう。
:::

``` bot.js

// ～～～～～～～～～～～～～～～～～～～～～～
//■■■■　この中に機能を追加していく　■■■■
  if (source.type == 'user' && source.userId == 'groupのID') {
    if(input.type == 'text') {
      if (input.text.match('おはよう')) {
        messages = sendSlack();
      } else {
        messages = hello();
      }
    }
// ～～～～～～～～～～～～～～～～～～～～～～
// 中略
// ～～～～～～～～～～～～～～～～～～～～～～

function sendSlack(m) {
  try{
    var postUrl = 'SlackのWebhoolURL';
    var username = 'LINE';  // 通知時に表示されるユーザー名
    var icon = ':globe_with_meridians:';  // 通知時に表示されるアイコン
    var jsonData =
    {
       "username" : username,
       "icon_emoji": icon,
       "text" : m
    };
    var payload = JSON.stringify(jsonData);
    
    var options =
    {
      "method" : "post",
      "contentType" : "application/json",
      "payload" : payload
    };
  
    UrlFetchApp.fetch(postUrl, options);
    
    var messages = [{
            'type': 'text',
            'text': 'Slackに送信したよ',
          }];
          
    return messages;
    
  } catch (e) {
    var em = [{
            'type': 'text',
            'text': e.message,
          }];
          
    return em;
  }
}

```

改変できたら、デプロイします。
LINEで「やあ」と呼びかけるとSlackにも「おはよう」と送信されて、ラズパイからSlack上で「おはよう」と帰ってきました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596310/1c4a8f77-d9c6-7a8e-b7fd-83060099a9e2.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596310/7dc22330-7405-8358-2541-1d11bdb610e9.png)
これで、ほぼ完成に近づきました！あとは本題の撮影機能を実装しましょう！

# 4. 撮影機能の実装

要件をおさらいします。

> - Line Botに「鍵見て」と呼びかけると起動する
> - 玄関に置いたRaspberryPiが写真を撮る
> - 鍵のところが撮影されてLineで送られてくる

### 「鍵見て」に反応するようにする
GASを改変して「カメラ」や「鍵」などを含むときにSlackに送信するようにします。

``` bot.js
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//■■■■　この中に機能を追加していく　■■■■
  if (source.type == 'user' && source.userId == 'U09f976d7b3c475d54161d589f74fe9e8'){
    if(input.type == 'text') {
      if (input.text.match('やあ')) {
        messages = sendSlack('おはよう');
      } else if (input.text.match('カメラ') || input.text.match('かぎ') || input.text.match('鍵') || input.text.match('玄関') || input.text.match('げんかん') || input.text.match('ドア')){
        messages = sendSlack('カメラ');
      } else {
        messages = hello();
      }
    }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
```

### Raspberry Piで撮影する
Raspberry Piのカメラで撮影するためには、`raspistill`コマンドを実行すればいいので、Slack botに組み込んでしまいましょう。`plugins/ `配下に新しいファイルを作成します。

``` plugins/camera.py
from slackbot.bot import respond_to, listen_to
import re
import os
import datetime
import requests

@listen_to('カメラ')
@respond_to('カメラ')
def cam(message, *something):
message.send('ハイチーズ!!')
os.system('raspistill -w 1280 -h 1024 -o image.jpg')
message.send('パシャ!!!!!!')
```

ここまでの実装で、LINEで「鍵みて」って呼びかけると、Slackにも投げられて、Raspberry Piのカメラがエラーなく起動すれば成功です。[^camera]

### LINE Notifyで画像を返す
[LINE Notify](https://notify-bot.line.me/ja/)からログインして、「**トークンを発行する**」を選びます。先に作ったLINE Botがいるグループを選んで、トークンを発行しましょう。

:::note alert
発行したトークンはページを閉じると再発行されないので、必ずコピーしてから移動しましょう。
:::

![notify.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596310/423104d9-d9b1-8fd1-f538-ebbb5c5b9619.png)

次にRaspberry Piのプログラムを改変します。**LINE Notifyのトークンを張り付けるのを忘れないでください。**

``` plugins/camera.py
from slackbot.bot import respond_to, listen_to
import re
import os
import datetime
import requests

@listen_to('カメラ')
@respond_to('カメラ')
def cam(message, *something):
    message.send('ハイチーズ!!')
    os.system('raspistill -w 1280 -h 1024 -o image.jpg')
    dt_now = datetime.datetime.now()
    message.send('パシャ!!!!!!')

    fname='image.jpg'

    url = "https://notify-api.line.me/api/notify"
    token = "LINE Notifyのトークンを張り付ける"
    headers = {"Authorization" : "Bearer "+ token}

    message = dt_now.strftime('%Y年%m月%d日 %H:%M:%S')
    payload = {"message" :  message}
    files = {"imageFile": open(fname, "rb")}
    r = requests.post(url, headers = headers, params=payload, files=files)
```

お疲れ様でした。**これで完成です!!**
最後にLINEで「鍵見て」と呼びかけて写真が返ってくるか見てみましょう。

うまく画像が返ってきたら成功です！

# 5.オチ
ワイ「玄関の様子みて」
Bot「見てきたやで」っ[画像]
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596310/87172ae6-1a58-92d0-f56e-e3abeb479e10.jpeg" width=50%>
**う～～～～～～～～ん、逆光！！**

##参考にした記事&関連記事
- [Raspberry Pi とSlackとLINE Messaging APIでLINEから家電を操作する | Zumi blog](https://www.zumid.net/entry/raspberry-pi-slack-line-messaging-api/#toc13)
- [LINE Botの作り方と作ってみた時にマジかと思ったことと作るときに使ってほしいサンプルと・・・っていうたくさんの話 - Qiita](https://qiita.com/ShassBeleth/items/f221acb7f6eb6ef964bf)
- [Google Apps ScriptでLINE BOTつくったら30分で動かせた件 - Qiita](https://qiita.com/hakshu/items/55c2584cf82718f47464)
- [LINE BOTの作り方～アカウントの作成とQ&Aの登録方法～](https://first-contact.jp/blog/article/linebot/)


[^syogatu]: 正月暇だったから三が日にちゃちゃっと何か作りたかった。
[^slack]: slackbotのセキュリティリスクは今回は無視してます。
[^allow]: 当たり前ですが、Googleにとって僕たちは確認されてないサードパーティーで、僕たちが開発したアプリは未確認のアプリです。
[^ras]: 使用したラズパイがRaspberry Pi2だったために、Wi-Fiデバイスが搭載されてなくて子機を買う羽目になったのはまた別の話。
[^camera]: カメラの設定が有効になってなくて3時間ぐらい溶けたのもまた別の話。「設定」＞「Raspberry Piの設定」から設定できます。