West Gate Laboratory

人生を少しでも,面白く便利にするモノづくり

Google Calendarの予定に応じてRaspberryPiに処理をさせる(その2)

はじめに

この記事はオートロックマンション用不在時荷物受け取りシステムの開発記事です。

westgate-lab.hatenablog.com

前回までに、RaspberryPiとGoogle Calendarを連携させて、定期的に予定を取得、テキストファイルに出力するところまで述べた。今回は出力したテキストファイルを使ってカレンダーに登録された時間に応じた処理をさせる

前回までの仕様では、インターホンが鳴った場合にいつでも解錠してしまう状態だったため、カレンダーに登録された配達予定時間帯のみ、インターホン音検知を有効化する

具体的には、

  • カレンダーに登録された配達予定開始時刻になったら音検知を有効化

  • カレンダーに登録された配達予定終了時刻になったら音検知を無効化

する。

RaspberryPi:Model 3B+

Python:3.7.3

python+scheduleによる関数定期実行

定期的にプログラムを実行させるものとして、cronがよく使われるが、今回はpythonスクリプト中で直接予定の登録等を行いたかったため、scheduleを用いた。

schedule.readthedocs.io

これを使うと関数の定期実行などが簡易にできる。使い方も簡単で、ユーザーフレンドリーなインターフェースをしている。

基本的な使い方はいろんな人が解説しているので、下記記事等を参照して欲しい。

qiita.com

今回の使い方としては、

  • 配達予定の開始終了時間が記述されたテキストファイル(schedule.txt)を読み込む

  • 開始時間になったらインターホン音検知を有効化する

  • 終了時間になったらインターホン音検知を無効化する

といった感じである。なお、schedule.txtには例として以下のようなデータが含まれる。この場合1月5日の9~11時、13~14時、18~19時の間に時間指定の配達予定がある。(実際そんなに荷物を頼むことはないが・・・)

2020-01-05T09:00:00 2020-01-05T11:00:00
2020-01-05T13:00:00 2020-01-05T14:00:00
2020-01-05T18:00:00 2020-01-05T19:00:00

実際にscheduleを使ってみる

まずは、定期的に呼び出してschedule.txtの中身をチェック、新しい配達予定時間が追加されていたら、scheduleで検知有効化、無効化の関数の呼び出しを登録するような関数を定義する。

import datetime
import schedule

schedule_list = []

def updateSchedule():
    global schedule_list
    f = open("schedule.txt", "r")
    lines = f.readlines()
    f.close()
    schedule_new = []
    for line in lines:
        line = line.rstrip()     # delete \n
        schedule_new.append(line)
        if not line in schedule_list:
            log.info('New schedule added:' + line)            
            # This is new schedule    
            times = line.split()
            st_date = datetime.datetime.strptime(times[0], '%Y-%m-%dT%H:%M:%S')
            en_date = datetime.datetime.strptime(times[1], '%Y-%m-%dT%H:%M:%S')
            # set schedule
            schedule.every().day.at(st_date.strftime("%H:%M")).do(activateDetection)
            schedule.every().day.at(en_date.strftime("%H:%M")).do(deactivateDetection)

    schedule_list = schedule_new

このupdateSchedule()はsignalのようなタイマー割り込みを使って定期的に呼び出すようにしておく。 if not line in schedule_list:の部分でその予定がこれまでになかった新しい予定か否かを判別している。

lineには上のテキストファイルの例で示したように[配達予定開始時刻] [配達予定終了時刻]が記述されているため、split()でスペース区切りで開始時刻(例:2020-01-05T09:00:00)、終了時刻(例:2020-01-05T11:00:00)に分割する。(times = line.split()

さらに、それらの時刻の文字列をstrptimeで日付に変換する。(st_date = datetime.datetime.strptime(times[0], '%Y-%m-%dT%H:%M:%S')

最後に、strftimeでHH:MMの形式に再度文字列化し、scheduleに登録する。登録する関数は検知有効化関数(activateDetection)、検知無効化関数(deactivateDetection)である(詳細は後述)。なお、この方法でscheduleに登録すると、スクリプトを見てわかるように、毎日同じ時刻にactivateDetection()(およびdeactivateDetection())が呼ばれることになる。実際の使い方として、ある配達予定時刻に対しては一度関数を呼び出せばいいはずである。scheduleはこのような日付指定の一度だけ呼び出しに対応していないようである。一度だけ関数を実行する方法は次で記述する。

scheduleで呼び出される関数

次に、scheduleで呼び出される検知有効化:activateDetection(), 検知無効化:deactivateDetection()のそれぞれの関数の中身である。

def activateDetection():
    global activated
    activated = True
    return schedule.CancelJob

def deactivateDetection():
    global activated
    activated = False
    return schedule.CancelJob

中身は非常にシンプルで、scheduleで登録した時間に関数が呼び出されたら、グローバル変数であるactivatedをTrueにしたりFalseにしたりというものである。あとはメインルーチンの方でインターホンの音が鳴ったときにactivatedを参照してTrueだったら配達予定時刻にインターホンが鳴ったものと判断して解錠してやれば良い。

各関数の最後のreturn schedule.CancelJobが今回のキモの部分である。scheduleでは特定日付の特定時刻の呼び出しには対応していないため、今回はその日一日のみの予定をGoogle Calendarから取得し、その日の予定時刻を登録し、一度だけ実行したらscheduleを停止したい。scheduleで一度のみ実行したい場合は、上のようにreturn schedule.CancelJobしてやれば良い。公式サポートのFAQにもその記述がある(下記参照)。

schedule.readthedocs.io

これらにより、Google Calendarに登録した予定に応じてインターホン音の検知・非検知(解錠・非解錠)を切り替えられるようになった。

まとめ

  • python+scheduleを使って関数を定期実行する方法を述べた。

  • scheduleで関数を一度のみ実行する方法を述べた。

  • これらを使ってGoogle Calendarに登録された予定に応じてインターホン音の検知・非検知(解錠・非解錠)を切り替えられるようになった。

ここまでできるようになると、次はインターホンのモニタ画像を見たくなってくる。というわけで次回はインターホンが鳴ったときのモニタ画像をウェブカメラで撮影して、スマホに通知していく。