お願いするとWebカメラで写真を撮って送信してくれるSlackBotを作った

はじめに

Pythonで Raspberry Pi3 B+ に Webカメラ ( logicool C270) ) を接続して、Slackのchannelで特定の発言があった時に、写真とって送ってくれるSlackBotを作りました。

最初はLINEで実装しましたが、「Slackでもやって!」といわれたので実装しました。

LINEBotと違うのは、常時SlackBotを動かしておき、特定の発言をトリガとして「写真を撮って保存 → 保存した写真をSlack channel に送信」というプログラムを実行しているところです。

開発期間は半日ぐらい(?)LINEBotよりちょっと難しかったです。

目次

Step1 . webカメラの写真を撮る

fswebcam を使って撮影しました。

次の手順で写真が撮れます。

  1. バイスが接続されているか確認します。

    lsusb

  2. fswebcamのインスト―ル

    sudo apt-get install fswebcam

  3. とりあえず一回とってみる

    fswebcam -p MJPEG image.jpg

optionもあるので参考に(man-fswebcam)

Pythonでは、subprosessモジュールを使うとコマンドの実行ができます。参考

import subprocess
def get_picture():
    cheese=['fswebcam','-p','MJPEG','-r','1280x1024','--no-banner','-D','1',image.jpg]
    try:
        subprocess.check_call(cheese)
        print ("Command finished.")
    except:
        return "Command envailed."

cheese変数にコマンドを入れて(スペース区切りでリストに代入)subprocess.check_call(cheese) で実行しています。

fswebcam -p MJPEG -r 1280x1024 --no-banner -D 1 imgage.jpgを実行しているのと同義です。

いくつかオプションがあります。

  • -p MJPEG : フォーマットをmjpegで
  • -r 1280×1024 : 画像サイズの指定
  • -D 1 : 撮影時、1秒遅延させる。(写真撮影の安定性が上がります)

参考

Step2 . Slackのchannelに撮った画像を送信する

Pythonslackbotライブラリを使いました。

slackbotの作成については、こちらの記事を参考にしてください。

トークンについては、こちらの記事を参考にしてください。

上記の記事を見ると、次からの話分かると思います。

from slackbot.bot import respond_to     # @botname: で反応するデコーダ
from slackbot.bot import listen_to      # チャネル内発言で反応するデコーダ
from slackbot.bot import default_reply  # 該当する応答がない場合に反応するデコーダ

# channel内の誰かが「こんにちは」というとこの関数を実行する
@listen_to('こんにちは')
def listen_func(message):
    message.send('こんにちは')      # ただの投稿
    message.reply('わーい')        # 「こんにちは」といった人に対して、メンションで「わーい」と返す

# channel内で、「@hogehogebot おやすみ」というとこの関数が実行される
@respond_to('おやすみ')
def mention_func(message):
    do_something()

こんな感じでメンションに対してリプライや発言をします。

画像の送信方法

画像送信には、Slack API の files.upload method を使用します。

CHANNEL_TOKENで指定する特定のchannelに対して、画像 (image.jpg) を送信します。

CHANNEL_TOKENは、Slackをwebで開いた時に出るURLの一部にあります。

https://{workspacename}.slack.com/messages/{これ}

一括でCHANNEL_TOKENを取得したい場合は、Slack API の channels.list methodでTestすると良いです。

(slackbotライブラリに画像送信メソッドはありませんでした(たぶん))

import requests
def post():
    files = {'file':open(image.jpg,'rb')}
    params = {
            'token':TOKEN, 
            'channels':CHANNEL_TOKEN,
            'filename':"filename",
            'initial_comment': "画像についてのコメント",
            'title': "ファイルの名前"
    }
    requests.post(url="https://slack.com/api/files.upload",params=params, files=files)

Step1で撮った画像を、request.postで送信して終了です。

python3 run.pyで実行してみましょう。

参考

Step3 . ログアウトしてもSlackBotの処理を続けるようにする

Raspberry pissh接続をしてStep2で作ったrun.pyを実行しますが、slackbotの起動中は、常に実行状態になければいけません。

また、ssh接続を解除すると実行中のプログラムも終了します。

コマンドを実行している際に、仮想端末(Terminal)の画面を閉じたりログアウトしたりすると、実行中のコマンドも終了してしまいます(コマンドをバックグラウンド実行していても終了する)。

nohup

そこで、nohupコマンドを使って、nohup python3 run.py & と指定すると、コマンドの実行を続けることができます。

ps

nohupコマンドで実行した処理の様子はpsコマンドで見ることができます。

ps -o pid,ppid,stat,tt,user,cmd

-oは出力内容を指定するオプションです。左から

プロセスID(pid)と親プロセスのID(ppid)、状態(stat)、端末(tt)、ユーザー(user)、コマンド列(cmd)を示しています。

ssh接続解除後、再び接続してpsを実行すると、ユーザーが変わって(?)見れなくなることがあります。

ps axuを実行すると、他のプロセスも全て表示できます。

kill

プロセス終了には、killコマンドを使用します。

kill <プロセスID>で終了できます。プロセスIDは上記のpsコマンドを実行して確認しましょう。

参考

Step4 . 細かなバグ修正など

Step3まででとりあえずプログラムは動きます。

しかし、「撮影 => 送信」のタイミングや、同じ画像名であったことから、結構不安定でした。

解決策

  1. 「撮影 => 送信」の間に5秒の遅延時間を入れました。
 from time import sleep
 sleep(5)
    
 # try-expect文で例外処理を入れましょう。
  1. 画像名を写真の撮影時刻に変えました。

    次のように時刻を取得します。

import datetime
def get_now_time():
  d = datetime.datetime.today()
  return d.strftime("%Y-%m-%d %H:%M:%S")
  1. 画像名時刻にしたので、ちゃんと送信ができたら撮影した画像を削除します。
   import os
   ...
   os.remove('./'+img_name)

追加

nohupコマンドを使用中、nohup.outに出力されます。fswebcamでは、デバッグメッセージがかなり表示されるので、定期的に消してあげるようにしました。

    import os
    os.remove('./nohup.out')

動作

こんな感じです。botの入っているチャンネルで「なう」というとwebカメラが写真を撮影してchannelに流してくれます。Webカメラの位置は動かせるので、中だけではなく、研究室からみえる外の景色等も撮れますね。

f:id:sho0126hiro:20190616180555j:plain:w300

しっかり動きました。