M5Stack でスケジュール管理に役立つ機能が実装できないかなと思い、 Google Calendar に登録しているスケジュールを表示させてみました。
Google Calendar API の利用設定
まずは Google Calendar API を利用できるように設定する必要があります。GCP のコンソールから API とサービスを追加
をクリックします。
API のリストの中から Google Calendar API
をクリックします。
API の詳細ページで 有効にする
をクリックして API を使える状態にします。
次に API の認証情報を作成します。左メニューから 認証情報
をクリックします。
認証情報の種別を選択するプルダウンで サービスアカウントキー
を選択します。
サービスアカウント名には任意の名前を設定します。役割については今回は参照だけできれば良いので、 閲覧者
を設定しました。サービスアカウント ID はサービスアカウント名から自動的に設定されます。キーのタイプはデフォルトが JSON になっているのでそのままにしておきます。最後に 作成
をすると認証情報が作成され、ダウンロードできるようになりますので、ローカルに取得しておきます。
Google Calendar の共有設定
次に先ほど作成したサービスアカウントからカレンダーを参照できるように、共有設定を行います。共有するカレンダーの共有設定画面から ユーザーの追加
をクリックします。
作成したサービスアカウントのサービスアカウント ID を設定します。権限は閲覧権限のみにしておきます。設定したら 送信
をクリックします。
AWS Lambda の関数作成
Google Calendar 側の設定はここまでで完了なので、次に AWS Lambda の関数を作成します。関数の作成画面で任意の関数名を指定します。ランタイムは今回は Python 3.6 を使用しています。
認証情報はコードの中に極力書きたくないので、 Lambda の実装画面で環境変数にサービスアカウントのキー ID を設定しておきます。
Google Calendar API を使うために Google が提供しているクライアントモジュールを使用します。 Lambda で外部モジュールを使用するにはローカルで zip に固めたものをアップロードする形になります。まずはローカルのプロジェクト用ディレクトリで pip を使用して google-api-python-client と oauth2client をインストールします。
$ pip3 install --upgrade google-api-python-client oauth2client -t ./
下記のようなファイルがインストールされます。 Google Calendar API の認証情報作成時にダウンロードした認証情報ファイルも google_key.json として同じディレクトリに置いておきます。
$ ls -l total 112 drwxr-xr-x 4 akanuma staff 128 Nov 17 12:39 __pycache__ drwxr-xr-x 4 akanuma staff 128 Nov 17 12:39 apiclient drwxr-xr-x 12 akanuma staff 384 Nov 17 12:39 cachetools drwxr-xr-x 9 akanuma staff 288 Nov 17 12:39 cachetools-3.0.0.dist-info drwxr-xr-x 4 akanuma staff 128 Nov 17 12:39 google drwxr-xr-x 7 akanuma staff 224 Nov 17 12:39 google_api_python_client-1.7.4.dist-info -rw-r--r-- 1 akanuma staff 539 Nov 17 12:39 google_auth-1.6.1-py3.7-nspkg.pth drwxr-xr-x 8 akanuma staff 256 Nov 17 12:39 google_auth-1.6.1.dist-info drwxr-xr-x 9 akanuma staff 288 Nov 17 12:39 google_auth_httplib2-0.0.3.dist-info -rw-r--r-- 1 akanuma staff 8434 Nov 17 12:39 google_auth_httplib2.py -rw-r--r--@ 1 akanuma staff 2345 Nov 17 11:24 google_key.json drwxr-xr-x 15 akanuma staff 480 Nov 17 12:39 googleapiclient drwxr-xr-x 8 akanuma staff 256 Nov 17 12:39 httplib2 drwxr-xr-x 7 akanuma staff 224 Nov 17 12:39 httplib2-0.12.0-py3.6.egg-info -rw-r--r-- 1 akanuma staff 625 Nov 17 12:37 lambda_function.py drwxr-xr-x 17 akanuma staff 544 Nov 17 12:39 oauth2client drwxr-xr-x 7 akanuma staff 224 Nov 17 12:39 oauth2client-4.1.3.dist-info drwxr-xr-x 9 akanuma staff 288 Nov 17 12:39 pyasn1 drwxr-xr-x 11 akanuma staff 352 Nov 17 12:39 pyasn1-0.4.4.dist-info drwxr-xr-x 31 akanuma staff 992 Nov 17 12:39 pyasn1_modules drwxr-xr-x 11 akanuma staff 352 Nov 17 12:39 pyasn1_modules-0.2.2.dist-info drwxr-xr-x 19 akanuma staff 608 Nov 17 12:39 rsa drwxr-xr-x 11 akanuma staff 352 Nov 17 12:39 rsa-4.0.dist-info drwxr-xr-x 9 akanuma staff 288 Nov 17 12:39 six-1.11.0.dist-info -rw-r--r-- 1 akanuma staff 30888 Nov 17 12:39 six.py drwxr-xr-x 7 akanuma staff 224 Nov 17 12:39 uritemplate drwxr-xr-x 9 akanuma staff 288 Nov 17 12:39 uritemplate-3.0.0.dist-info
この中で *.dist-info は不要なので削除しておきます。
$ rm -rf *.dist-info
削除後のリストは下記のようになります。
$ ls -l total 112 drwxr-xr-x 4 akanuma staff 128 Nov 17 12:39 __pycache__ drwxr-xr-x 4 akanuma staff 128 Nov 17 12:39 apiclient drwxr-xr-x 12 akanuma staff 384 Nov 17 12:39 cachetools drwxr-xr-x 4 akanuma staff 128 Nov 17 12:39 google -rw-r--r-- 1 akanuma staff 539 Nov 17 12:39 google_auth-1.6.1-py3.7-nspkg.pth -rw-r--r-- 1 akanuma staff 8434 Nov 17 12:39 google_auth_httplib2.py -rw-r--r--@ 1 akanuma staff 2345 Nov 17 11:24 google_key.json drwxr-xr-x 15 akanuma staff 480 Nov 17 12:39 googleapiclient drwxr-xr-x 8 akanuma staff 256 Nov 17 12:39 httplib2 drwxr-xr-x 7 akanuma staff 224 Nov 17 12:39 httplib2-0.12.0-py3.6.egg-info -rw-r--r-- 1 akanuma staff 625 Nov 17 12:37 lambda_function.py drwxr-xr-x 17 akanuma staff 544 Nov 17 12:39 oauth2client drwxr-xr-x 9 akanuma staff 288 Nov 17 12:39 pyasn1 drwxr-xr-x 31 akanuma staff 992 Nov 17 12:39 pyasn1_modules drwxr-xr-x 19 akanuma staff 608 Nov 17 12:39 rsa -rw-r--r-- 1 akanuma staff 30888 Nov 17 12:39 six.py drwxr-xr-x 7 akanuma staff 224 Nov 17 12:39 uritemplate
これを zip に圧縮しておきます。
$ zip -r google_calendar_m5stack.zip ./*
圧縮した zip ファイルを Lambda のコンソールからアップロードします。
メインの関数(lambda_function.py)の内容は下記のようにしました。クラスの初期化時に認証情報を取得し、 get_schedules() メソッドで Google Calendar API をコールしてスケジュールの情報を取得しています。今回はとりあえず直近5件のスケジュールの開始日時、終了日時とサマリだけ使用しています。
from dateutil.parser import parse from apiclient import discovery from oauth2client.service_account import ServiceAccountCredentials import datetime import httplib2 import json import logging import os logger = logging.getLogger() logger.setLevel(logging.INFO) class GoogleCalendar: def __init__(self): self.service_account_id = os.environ['GOOGLE_SERVICE_ACCOUNT_ID'] scopes = 'https://www.googleapis.com/auth/calendar.readonly' self.credentials = ServiceAccountCredentials.from_json_keyfile_name( 'google_key.json', scopes = scopes ) self.calendar_id = 'XXXXXXXXXXXXXXX@gmail.com' self.max_results = 5 def get_schedules(self): http = self.credentials.authorize(httplib2.Http()) service = discovery.build('calendar', 'v3', http = http) now = datetime.datetime.utcnow().isoformat() + 'Z' events_result = service.events().list( calendarId = self.calendar_id, timeMin = now, maxResults = self.max_results, singleEvents = True, orderBy = 'startTime' ).execute() events = events_result.get('items', []) if not events: logger.info('No upcoming events found.') schedules = [] for event in events: start = event['start'].get('dateTime', event['start'].get('date')) end = event['end'].get('dateTime', event['end'].get('date')) summary = event['summary'] schedules.append({ 'start': parse(start).strftime('%Y/%m/%d %H:%M:%S'), 'end': parse(end).strftime('%Y/%m/%d %H:%M:%S'), 'summary': summary }) return schedules def lambda_handler(event, context): calendar = GoogleCalendar() schedules = calendar.get_schedules() return { 'statusCode': 200, 'body': json.dumps({'schedules': schedules}) }
API Gateway の設定
Lambda の関数が作成できたので、次に関数を API として実行できるように、 API Gateway の設定を行います。 AWS Lambda の実装画面でトリガーの追加メニューから API Gateway
を選択します。
トリガーの設定フォームで API Gateway の設定が行えます。新規の API を作成するか既存の API から選択するかを選べますので、今回は 新規 API の作成
を選択します。セキュリティでは API キー使用でのオープン
を選択して、 API キーで認証するようにしておきます。
下記のように未保存の状態で API が作成されますので、 Lambda コンソールの 保存
ボタンをクリックして設定を保存します。
すると実際に API が作成され、下記のように API の情報が表示されます。下記画像は色々とマスクしてあります。
試しに API キーなしで API にアクセスしてみると、 Forbidden となりアクセスが拒否されます。
$ curl https://XXXXXXXXXX.XXXXXXXXXXX.ap-northeast-1.amazonaws.com/default/googleCalendarM5Stack {"message":"Forbidden"}
API キーを Header に設定してリクエストを投げると下記のように情報を取得することができます。
$ curl https://XXXXXXXXXX.XXXXXXXXXXX.ap-northeast-1.amazonaws.com/default/googleCalendarM5Stack --header 'x-api-key:EnXb6oRB957iVzKXXXXXXXXXXXXXXXXXXXXXXXXX' {"schedules": [{"start": "2018-11-18", "end": "2018-11-19", "summary": "\u6771\u4eac\u30aa\u30d5\u30a3\u30b9\u79fb\u8ee2"}, {"start": "2018-11-18", "end": "2018-11-19", "summary": "\u30bf\u30c3\u30d7\u516c\u6f14\u30ea\u30cf"}, {"start": "2018-11-22", "end": "2018-11-23", "summary": "SORACOM Technology Camp 2018"}, {"start": "2018-11-22T13:30:00+09:00", "end": "2018-11-22T19:30:00+09:00", "summary": "SORACOM Technology Camp 2018"}, {"start": "2018-11-23T12:00:00+09:00", "end": "2018-11-23T22:00:00+09:00", "summary": "TAP\u516c\u6f14\u30ea\u30cf"}]}
M5Stack のファームウェア実装(MicroPython)
では最後に M5Stack 側の実装です。コードの全体は下記の通りです。主な処理は Lambda 側でやっているので、 M5Stack 側では単純に API を呼んで結果をループで表示しているだけのものになります。
from m5stack import lcd import time import ujson import urequests class GoogleCalendar: def __init__(self): self.base_url = 'https://XXXXXXXXXX.XXXXXXXXXXX.ap-northeast-1.amazonaws.com/default/googleCalendarM5Stack' self.api_key = 'EnXb6oRB957iVzKXXXXXXXXXXXXXXXXXXXXXXXXX' lcd.setCursor(0, 0) lcd.setColor(lcd.WHITE) lcd.font(lcd.FONT_DejaVu18) self.fw, self.fh = lcd.fontSize() def get_schedules(self): headers = {'x-api-key': self.api_key} response = urequests.get(self.base_url, headers = headers) json = response.json() return json['schedules'] def display(self, schedules): lcd.clear() lcd.setCursor(0, 0) for schedule in schedules: print(schedule) lcd.println("{}".format(schedule['start'])) lcd.println(" {}".format(schedule['summary'])) calendar = GoogleCalendar() while True: schedules = calendar.get_schedules() calendar.display(schedules) time.sleep(60)
動作確認
実行した結果は下記のようになります。表示は適当ですが、とりあえず Google Calendar の情報を M5Stack に表示することができました。ただ、日本語はそのままでは表示できませんので、テスト用のスケジュールを英語で登録して表示してみました。
まとめ
今回ひとまず Google Calendar から情報が取得できるようになりました。実際に使用するには表示を見やすく工夫したり、エラーハンドリングなどももっとちゃんと作りこむ必要がありますが、スケジュールの情報が使えると色々やれそうな気がします。また、今回 Lambda を経由して Google Calendar にアクセスすることで、 Google Calendar の認証情報は Lambda 側に保持し、デバイス上には持たせない構成になっているのはセキュリティ面では良い点かと思います。
今回 Google Calendar API の使い方については下記チュートリアルを参考にしました。
Python Quickstart | Calendar API | Google Developers
また、 Lambda で外部モジュールを使う方法については下記サイトを参考にさせていただきました。
Lambda から Google Calendar API を利用する方法については下記サイトを参考にさせていただいています。