3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2021-01-06

0. はじめに

僕も含め家族がよく鍵を締めたか外に出てから心配になって戻りがちなので、外出先で鍵締めたか確認できるようしてみました。1

tojimari_door1.png

できるだけコストをかけず、手元にある機器で作りたかったので余っていた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にリクエストを投げるようにします。2
ここまでの構成をまとめると以下のようになります。
sekkei.png

2. LINE botを作る

準備

LINE Developersにアクセスし、ログインします。LINEアカウント持ってない場合はアカウントを作ってください。
ログインができたら開発者情報をポチポチ登録して、「新規プロバイダー作成」からプロバイダーを作成します。その後、「Messaging API」を選択してからチャネルを作成します。
スクリーンショット 2021-01-04 153732.png

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

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

スクリーンショット 2021-01-04 155051.png

GASを作成

Googleドライブにアクセスして、「新規>その他」からGoogle Apps Scriptを選択します。表示が無ければ、「アプリを追加」から検索して追加してください。

GASはJavaScriptで記述します。
image.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
種類の選択から、ウェブアプリをえらんでアクセスできるユーザーを「全員」にしてデプロイします。
image.png
初回はアクセスの承認が必要になると思うので、承認しましょう。おそらく、英語や日本語で「このアプリは確認されていません」3と言われると思うので「詳細を表示」/「Advance」から自分のアプリケーションに移動して許可します。
image.png

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

LINEと連携

作成したGASをLINEと連携させます。「Messaging API設定」のページに戻って「Webhook設定」を更新していきましょう。

Webhook URL
GASのウェブアプリケーションのURLを貼り付け
Webhookの利用
利用する

LINE2.png

次にその下にある「LINE公式アカウント機能>応答メッセージ」を編集します。

応答メッセージ
オフ
Webhook
オン

image.png

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

image.png

応答範囲を限定する

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

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

まず、//■■■■ この中に機能を追加していく ■■■■書いてあったあたりのコードを改変して、IDをそのまま返すようにしてデプロイしましょう。
下記コードではグループIDを判別するようにしています。sourceの中身はドキュメントを参照してください。

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

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

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

.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を作っていきましょう。
適当なワークスペースを作成し(もちろん既存のワークスペースも可)、そのワークスペースにボットインテグレーションの作成からbotを作成します。

Raspberry PiはIP固定しておいてください。(固定方法はここを参照)4

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.pyplugins/の配下に作って行きましょう。

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

GAS-Slackの連携

ここまでで下図の緑色になっているところまでが開発が終わりました。次に④のところの連携をします。
a.png

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

image.png

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

GASの改変

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

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
image.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のカメラがエラーなく起動すれば成功です。5

LINE Notifyで画像を返す

LINE Notifyからログインして、「トークンを発行する」を選びます。先に作ったLINE Botがいるグループを選んで、トークンを発行しましょう。

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

notify.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「見てきたやで」っ[画像]

う~~~~~~~~ん、逆光!!

##参考にした記事&関連記事

  1. 正月暇だったから三が日にちゃちゃっと何か作りたかった。

  2. slackbotのセキュリティリスクは今回は無視してます。

  3. 当たり前ですが、Googleにとって僕たちは確認されてないサードパーティーで、僕たちが開発したアプリは未確認のアプリです。

  4. 使用したラズパイがRaspberry Pi2だったために、Wi-Fiデバイスが搭載されてなくて子機を買う羽目になったのはまた別の話。

  5. カメラの設定が有効になってなくて3時間ぐらい溶けたのもまた別の話。「設定」>「Raspberry Piの設定」から設定できます。

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?