raspberry pi でカメラ自動撮影&自動送信アプリを作る

子供の写真を自動で返してもらう

みなさんご存知のraspberry piで遊んでみました。 今回はこのバージョンでの動作確認を行ってます。

最近子供が産まれたのですが、 仕事中に子供の写真で癒やされたいため、 リアルタイムな写真が欲しいところです。

そんなアプリを作ってみました。

github.com

処理の流れ

lineのbotとか使ってみたのですが、管理が意外とめんどかったので、 レガシーなgmailを使ってみました。

ざっくりとこんな流れ

  1. 私がメールで写真を撮れと、司令する(gmailに)
  2. raspberry pigmailのメールを受信する
  3. 送信元アドレスをチェックして、送っていいか確認する ( 知らん人からメールが来ても送らないようにする)
    • 送っちゃ行けない場合は処理終了
    • 送っていい場合は4へ
  4. 写真を撮影する
  5. 撮った写真を添付して送信アドレスに返す

1だけが私の処理で、2-5がraspberry pi 側の処理ですね。

準備(ほぼ割愛)

さて、今回はこのカメラを使用しました。

差込口等で迷うこともないし、 OS等も入っていててpython3も入っている前提で。

撮影モジュール作成

さて、なんにしてもカメラで撮影できるようにならなければ始まりません。 python3で撮影するための関数を用意しました。

# カメラ撮影関数
import RPi.GPIO as GPIO
import subprocess
import os
import sys
import datetime

def shotPicture():
    d = datetime.datetime.today()
    filename = "{0}{1:02d}{2:02d}{3:02d}{4:02d}{5:02d}.jpg".format(d.year, d.month, d.day, d.hour, d.minute, d.second)
    args = ['raspistill', '-o', filename, '-t', '1']
    subprocess.Popen(args)
    return filename

ちょこっと解説すると、 raspberry piのOS側のコマンド(つまりshellコマンド)で、

raspistill -o ${任意のfilename} -t 1

と打つと撮影できるので、 このコマンドをpython側から実行します。 そのためのpythonの関数が

subprocess.Popen(args)

です。 argsに実行したいコマンドの中身をリスト形式で渡せばOKです。 撮影した写真のファイル名はYYYYMMDDHHMMSS.jpgにしました。

これで

file = shotPicture()

すれば、写真を撮影した上、撮影したファイル名がfile変数に格納されます。

メールの送受信

今回はメールの受信と送信モジュールが必要なので、こちらも関数で用意します。 受信についてはメールの本文は不要で、送ってきたメールアドレスだけが必要なので、

  1. gmailにログイン
  2. 新着メール有無チェック
  3. 新着メールがあったらメールアドレスを返す(ただし重複は弾く)

で行きます。

# メール受信
import imaplib, re, email, six

def recieveGmail(s,u,p):
    # s=server,u=username,p=password
    client = imaplib.IMAP4_SSL(s)
    client.login(u,p)
    # 受信箱指定
    client.select('INBOX')
    # 未読メールをメモリに格納(この時点で既読になる)
    typ, [data] = client.search(None, "(UNSEEN)")

    # ない場合は空のcuesリストを返す
    # 未読メールがあったか確認
    cues = []
    if typ == "OK":
        if data != b'':
            print("new mail(s)")
            # メールを一件ずつ処理
            for num in data.split():
                result, d = client.fetch(num, "(RFC822)")
                raw_email = d[0][1]
                #文字コード取得用
                msg = email.message_from_string(raw_email.decode('utf-8'))
                fromObj = email.header.decode_header(msg.get('From'))
                for f in fromObj:
                    cue = ""
                    if isinstance(f[0],bytes):
                        cue = f[0].decode('utf-8')
                    else:
                        cue = str(f[0])
                    cue = re.search(r'<(.+)>',cue)
                    cue = cue.group(0).replace("<","").replace(">","")
                    cues.append(cue)
    client.close()
    client.logout()
    return cues

特に解説はいらないですよね?(私もよくわかっていないだけ) ネットのコードを切った張った修正した、だけですが、これで

reply_address = recieveGmail(s,u,p)

すればreplay_addressにリスト形式でメールアドレスを返してくれます。

続いて送信側

# メール送信
import gmail
def sendGmail(u,p,t,s,b,a):
    #u=user,p=pass,t=to_addr,s=subject,b=body,a=attachment
    client = gmail.GMail(u, p)
    if a == '':
        message = gmail.Message(s,to=t,text=b)
    else:
        message = gmail.Message(s,to=t,text=b,attachments=[a])
    client.send(message)
    client.close()

こちらは添付ファイル有りなしで分岐がありますが、ただメールを送るだけです。

全部繋げる

あとはこれで完成

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

import os
import sys
import gmail
from time import sleep

import RPi.GPIO as GPIO
from time import sleep
import subprocess
import datetime

import imaplib, re, email, six

# カメラ撮影関数
def shotPicture():
    d = datetime.datetime.today()
    filename = "{0}{1:02d}{2:02d}{3:02d}{4:02d}{5:02d}.jpg".format(d.year, d.month, d.day, d.hour, d.minute, d.second)
    args = ['raspistill', '-o', filename, '-t', '1']
    subprocess.Popen(args)
    return filename

# メール送信
def sendGmail(u,p,t,s,b,a):
    #u=user,p=pass,t=to_addr,s=subject,b=body,a=attachment
    client = gmail.GMail(u, p)
    if a == '':
        message = gmail.Message(s,to=t,text=b)
    else:
        message = gmail.Message(s,to=t,text=b,attachments=[a])
    client.send(message)
    client.close()

# メール受信
def recieveGmail(s,u,p):
    # s=server,u=username,p=password
    client = imaplib.IMAP4_SSL(s)
    client.login(u,p)
    # 受信箱指定
    client.select('INBOX')
    # 未読メールをメモリに格納(この時点で既読になる)
    typ, [data] = client.search(None, "(UNSEEN)")

    # ない場合は空のcuesリストを返す
    # 未読メールがあったか確認
    cues = []
    if typ == "OK":
        if data != b'':
            print("new mail(s)")
            # メールを一件ずつ処理
            for num in data.split():
                result, d = client.fetch(num, "(RFC822)")
                raw_email = d[0][1]
                #文字コード取得用
                msg = email.message_from_string(raw_email.decode('utf-8'))
                fromObj = email.header.decode_header(msg.get('From'))
                for f in fromObj:
                    cue = ""
                    if isinstance(f[0],bytes):
                        cue = f[0].decode('utf-8')
                    else:
                        cue = str(f[0])
                    cue = re.search(r'<(.+)>',cue)
                    cue = cue.group(0).replace("<","").replace(">","")
                    cues.append(cue)
    client.close()
    client.logout()
    return cues


# gmail定義
username = 'アカウント名@gmail.com'
password = 'パスワード'
server = 'imap.gmail.com'

# 返信するホワイトリスト定義
whitelist = ['返信する','パスワード','のリスト','hogehoge@gmail.com']

while True:
    cues = recieveGmail(server,username,password)
    cues = list(set(cues))
    if cues == []:
        sleep(2)
    else:
        print(cues)
        for addr in cues:
            if addr in whitelist:
                sendGmail(username,password,addr,'かしこまり','ちょっと待ってね','')
                pictpass = shotPicture()
                sleep(3)
                sendGmail(username,password,addr,'はいどうぞ','いかが?',pictpass)

これを適当に名前をつけて(app.pyとか)保存し、

python3 app.py

すればおk。