更新日: 2020-11-24
Nature Remoというスマートリモンコンを購入しました!
Nature Remoは、スマートリモコンとして多くの家電を操作できるだけではなく、機器自体にセンサーが内蔵されており、温度、湿度、照度、人感センサーのデータを取得することができます。
しかし、取得されるセンサーデータは過去のデータがすぐに消えてしまうのため、長期で傾向を見ようと思うと定期的にセンサーデータを取得して、どこかしらに保存する必要があります。
今回、Nature RemoのセンサーデータをHeroku 上で動かしたPythonスクリプトで定期取得し、Googleスプレッドシートに書き込んでみました。
センサーデータ定期測定システムの構成図
まずはざっくりとこういうことがしたい!というのを図で表現したいと思います。

Nature Remoスマートリモコンを我が家に設置するとスマートリモコンからNature Remo Cloudへ送信されたセンサーデータが送信されます。
Nature Remo Cloud上に保存されたセンサーデータをHeroku上で動かしたPythonスクリプトでAPI経由で取得!
取得したセンサーデータをGoogleスプレッドシートに書き込むという構成にしました。
方針としては、
- 自前で常時稼働が必要なサーバ(ラズパイ等)は用意しない!
- Nature Remoを購入するだけであとは無料で実現する!
の2つです。
ラズパイなどのお手頃価格のサーバを自宅で可動させるという手もあるのですが、スクリプト一つを定期実行するためだけにラズパイ用意するか?というとちょっとやりすぎだと思います。
また、今回の構成ではどれも無料で利用できるので、Nature Remoを購入する以外にはお金はかからないようになっています!
Nature Remoを購入
Nature Remoには第一世代と呼ばれる古い型と、第二世代と呼ばれる新しい型があり、私が使ったのはREMO-1W2という第2世代の商品です。
Nature Remoには、Nature Remo miniというより価格がお手頃な製品もありますが、miniはセンサーの種類が少ないので購入を考えている方は気をつけてください!
追記)2020年8月5日にNature Remo 3が発売されました。Bluetooth機器との連携が可能になり、本体のサイズが2よりもコンパクトなっています!センサやAPIの仕様は変わらないので、この記事の内容はそのままNature Remo 3にも使えます。
Nature スマートリモコン Nature Remo 3 ネイチャーリモ Remo-1W3 Alexa/Google Home/Siri対応
Nature Remo 2/3 | Nature Remo mini | |
---|---|---|
GPS連携 | ○ | ○ |
温度センサー | ○ | ○ |
湿度センサー | ○ | × |
照度センサー | ○ | × |
人感センサー | ○ | × |
Nature Remoを選んだ理由!
Nature Remoを選んだ理由は3つあります。
1つ目は、センサーが付いていること!
Nature Remoは、温度、湿度、照度、人感の4つセンサーが機器本体に搭載されており、置いておくだけでこれらの情報がクラウド上にアップロードされます。これだけ多くのセンサーがついている製品はあまりありません!
2つ目は、APIを公開していること!
APIとはApplication Programing Interfaceの略です。APIってなに?という人に一言で説明するのは難しいのですが、要は自作のプログラム等でNature Remoの情報を取得したり、操作をしたりすることができるようにプログラミング用のインターフェースです。
APIが公開されていないと自作プログラムからは何もできません!
Nature RemoはAPIをインターネットに公開しているので、今回私が作ったような自作のプログラムからでも情報を取得できます。
約1万円でNature Remoを購入するとスマーリモコンとして使えるだけでなく、APIを利用して遊べるので、かなりお得な製品だと思います!
3つ目は、家電の制御ができること!
Nature Remoはセンサー機器というよりはスマートリモコンなので、当たり前ではありますが、ただセンサーデータを収集するだけではなく、今後はそのデータを使って何かしらのアクションもしていきたいです!
このスマートリモコン機能による家電制御もAPI経由で実行できます。本記事ではそこまではやりませんが、Nature Remoは私の要望にぴったりの製品でした!
Nature Remo Cloud APIでセンサ(イベント)データ取得
Nature Remo の Web API の仕様は以下のページで説明されています。あまりパラメータの意味など詳しくはないですが、パラメータ名から大体の意味がわかります。笑
今回取得したいセンサーデータは、GET /1/devices というAPIで取得できます。これをcurlで実行してみると以下のような感じになります。
$ curl -s -H 'accept: application/json' -H 'Authorization: Bearer <token>' https://api.nature.global/1/devices | python3 -m json.tool [ { "name": "remo-1", "id": "xxxx", "created_at": "2019-05-19T08:03:11Z", "updated_at": "2019-09-27T14:45:40Z", "mac_address": "xxxx", "serial_number": "xxxx", "firmware_version": "Remo/1.0.77-g808448c", "temperature_offset": 0, "humidity_offset": 0, "users": [ { "id": "xxxx", "nickname": "xxxx", "superuser": true } ], "newest_events": { "hu": { "val": 61, "created_at": "2019-09-27T22:17:46Z" }, "il": { "val": 152, "created_at": "2019-09-27T14:46:07Z" }, "mo": { "val": 1, "created_at": "2019-09-27T22:03:10Z" }, "te": { "val": 25, "created_at": "2019-09-27T10:21:43Z" } } } ]
HTTPヘッダーに、認証のためのtokenを指定します。-H 'Authorization: Bearer <token>'
の部分です。<token>は、Nature Remo API のページのアクセストークンの新規発行ページから取得します。
レスポンスがjson形式で得られますので、| python3 -m json.tool
で整形しています。
センサーデータはnewest_events
の中にある4つで、それぞれte: 温度、hu: 湿度、il: 照度、mo: 人感を意味しているようです。valの値を視ればそれぞれの値がわかります。人感については、常に値は1になり、人を感知したときにcreated_at
の時刻が更新されるようです。
PythonからNature Remo Cloud APIを利用する
上のGET /1/devices
のAPIを発行して、結果を取得する処理をPythonで書いてみました。
使用したPythonのバージョンは、3.7.4です。
Pythonのバージョンとパッケージの管理は、pyenv + pipenvがおすすめ!ディレクトリ毎にバージョンとパッケージの組み合わせを変えられるので、herokuなどのクラウド上で実行する際にも環境を揃えやすいです!
以下の外部記事がわかりやすいので参考にどうぞ!
さて、実際のコードですが、urlやtokenの指定をするクラスを作って、それをmainのプログラムから呼び出すようにしてみました。のちのちセンサーデータ取得以外の目的にも使えるように、汎用的に書いています。
なお、実行するにはrequestsパッケージが必要ですので以下のようにインストールしてください。
$ pipenv install requests
# remoclient.py import os import requests import json class NatureRemoClient(object): def __init__(self, base_url=None): if base_url: self.base_url = base_url else: self.base_url = 'https://api.nature.global' token = os.environ.get('REMO_TOKEN') if not token: raise Exception('Please set your API token to REMO_TOKEN') self.headers = { 'accept': 'application/json', 'Authorization': 'Bearer ' + token } def call_api(self, url, method='get', params=None): req_method = getattr(requests, method) try: res = req_method(self.base_url+url, headers=self.headers, params=params) return res.json() except Exception as e: raise Exception('Failed to call API: %s' % str(e)) def get_devices(self): url = '/1/devices' return self.call_api(url)
ポイントは、token = os.environ.get('REMO_TOKEN')
のようにtokenを環境変数から読み出すようにしている部分です。tokenのような秘密情報はPythonのコードに直接書いてはいけません。汎用性がなくなる上に、githubなどで公開できなくなってしまいます。
また、あとで説明しますがherokuでこのPythonコードを実行する場合には環境変数から読み込むようにしておいた方が都合がいいです。
さらに以下の2つのメソッドを足しました。1つ目のget_device()
は、deviceは一つのアカウントに複数登録されている場合があるので、どれか一つを選べるようにするのが目的です。
get_newest_events()
は、センサーデータをだけを取り出すためのものです。
def get_device(self, device_name=None, device_id=None): devices = self.get_devices() if device_id: device = [d for d in devices if d['id'] == device_id] return device[0] elif device_name: device = [d for d in devices if d['name'] == device_name] return device[0] else: return devices[0] def get_newest_events(self, device_name=None, device_id=None, event_name=None): if device_name: device = self.get_device(device_name=device_name) elif device_id: device = self.get_device(device_id=device_id) else: device = self.get_device() if event_name: return device['newest_events'][event_name] else: return device['newest_events']
あとは、以下のようにメインのスクリプト内で、上で書いたremoclient.py
をimportして、センサーデータを取得します!
import remoclient client = remoclient.NatureRemoClient() events = client.get_newest_events()
以降で、この取得したセンサーデータをGoogleスプレッドシートに書き込む処理を書いていきます!
センサ(イベント)データをGoogleスプレッドシートに記録する
取得したセンサーデータをどうやって無料で記録するかですが、Googleスプレッドシートに書き込んでいくのがお手軽かと思います。Excelのように、あとでグラフ化もしやすいです。
PythonからGoogleスプレッドシートに書き込む!
スプレッドシートには、Pythonのライブラリも用意されているので、Pythonからはとても簡単に扱うことができます!
書いたコードは以下のような感じです。以下のコードを実行するためには、gspreadとoauth2clientというライブラリが必要ですので、pipもしくは、pipenvでインストールしておきます。
$ pipenv install gspread oauth2client
インストールしたこれらを一番上の部分でimportします。
# spreadsheet.py import os import sys import gspread from oauth2client.service_account import ServiceAccountCredentials class SpreadSheet(object): def __init__(self): scope = ['https://spreadsheets.google.com/feeds', 'https://www.googleapis.com/auth/drive'] credential = { "type": os.environ['GS_CREDENTIAL_TYPE'], "project_id": os.environ['GS_PROJECT_ID'], "private_key_id": os.environ['GS_PRIVATE_KEY_ID'], "private_key": os.environ['GS_PRIVATE_KEY'], "client_email": os.environ['GS_CLIENT_EMAIL'], "client_id": os.environ['GS_CLIENT_ID'], "auth_uri": os.environ['GS_AUTH_URI'], "token_uri": os.environ['GS_TOKEN_URI'], "auth_provider_x509_cert_url": os.environ[ 'GS_AUTH_PROVIDER_CERT_URL'], "client_x509_cert_url": os.environ['GS_CLIENT_CERT_URL'] } cred = ServiceAccountCredentials.from_json_keyfile_dict( credential, scope) self.gs = gspread.authorize(cred) def open_sheet(self, filename): self.sheet = self.gs.open(filename).sheet1 def get_col_length(self, row=1): return len(self.sheet.col_values(row)) def update_column(self, line, values): cells = self.sheet.range(line, 1, line, len(values)) for i in range(len(cells)): cells[i].value = values[i] self.sheet.update_cells(cells)
Googleスプレッドシートを使うためにはサービスアカウントキーという認証情報をあらかじめ取得する必要があります。
サービスアカウントキーの発行方法は以下の外部記事を参考に。
Nature Remoのtokenと同じように、ここでもサービスアカウントキー認証情報はすべて環境変数から読み込むようにしています。
credential = { "type": os.environ['GS_CREDENTIAL_TYPE'], "project_id": os.environ['GS_PROJECT_ID'], "private_key_id": os.environ['GS_PRIVATE_KEY_ID'], "private_key": os.environ['GS_PRIVATE_KEY'], "client_email": os.environ['GS_CLIENT_EMAIL'], "client_id": os.environ['GS_CLIENT_ID'], "auth_uri": os.environ['GS_AUTH_URI'], "token_uri": os.environ['GS_TOKEN_URI'], "auth_provider_x509_cert_url": os.environ[ 'GS_AUTH_PROVIDER_CERT_URL'], "client_x509_cert_url": os.environ['GS_CLIENT_CERT_URL'] }
Nature Remoのtokenなども含め設定しないといけない環境変数が多いので、以下のように一つのファイルに必要な環境変数をまとめて、一気に読み込むようにしておくと便利です。
例えば、以下のような感じです。
$ cat remorc.sh export REMO_TOKEN=xxxx export SHEET_NAME=xxxx export GS_CREDENTIAL_TYPE=service_account export GS_PROJECT_ID=xxxx export GS_PRIVATE_KEY_ID=xxxx export GS_PRIVATE_KEY="-----BEGIN PRIVATE KEY----- MIIEvAIBADANBgkqhkiG9w0BAQEFAASCxKYwggSiAgEAAoIBAQCcM+LsMOomXAAB ... /d7eeJM6/p7NmFkbenzny5QtDKx3g8he/qP2cAEEfocOO3TqlwDLHXqZDab5r3mT CBqV/v2QvUIr47d0/TTwag== -----END PRIVATE KEY----- " export GS_CLIENT_EMAIL=xxxx export GS_CLIENT_ID=xxxx export GS_AUTH_URI=https://accounts.google.com/o/oauth2/auth export GS_TOKEN_URI=https://oauth2.googleapis.com/token export GS_AUTH_PROVIDER_CERT_URL=https://www.googleapis.com/oauth2/v1/certs export GS_CLIENT_CERT_URL=xxxx
サービスアカウントのGS_PRIVATE_KEY
は、上のように改行コード(\n)を消して実際に改行するようにしないとうまくいきません。
また、SHEET_NAME
には、データを書き込むためのスプレッドシートの名前を入れます。このスプレッドシートには、GS_CLIENT_EMAIL
に記載のアカウントで編集できるように権限を追加しておく必要があります。これについても、上で紹介した外部記事にかかれています。
そして、このファイルを用意しておけば
$ source remorc.sh
で一気に実行するとさくっと環境変数を設定できます。
上のセンサーデータの取得のコードと合わせて、最終的には以下のようなコードになりました。
import os import datetime import remoclient import spreadsheet def run(): gs = spreadsheet.SpreadSheet() gs.open_sheet(filename=os.environ['SHEET_NAME']) last_line = gs.get_col_length() if last_line == 0: header = [ 'timestamp', 'timestamp_unix', 'temperature', 'temperature_created_at', 'humidity', 'humidity_created_at', 'illumination', 'illumination_created_at', 'motion', 'motion_created_at' ] gs.update_column(1, header) last_line += 1 now = datetime.datetime.now(datetime.timezone.utc) client = remoclient.NatureRemoClient() events = client.get_newest_events() now_iso = now.isoformat() values = [ now_iso, int(now.timestamp()), events['te']['val'], events['te']['created_at'], events['hu']['val'], events['hu']['created_at'], events['il']['val'], events['il']['created_at'], events['mo']['val'], events['mo']['created_at'] ] gs.update_column(last_line+1, values) if __name__ == "__main__": run()
スプレッドシートの各列のheaderを書き込む処理を加えたのと、
last_line = gs.get_col_length()
で行数を取得して、一番下の行に新しいデータを書き込むようにしています。
このコードを実行して、こんな感じでスプレッドシートに行が追加されていけば問題なしです。

以上でPythonスクリプトは完成です!以降では、このスクリプトをHerokuの上で実行できるようにしていきます。
Herokuを使って定期実行する!

HerokuはPaaS(Platform as a Service)としてアプリの実行環境を提供するクラウドサービスを展開しています。実行環境を提供する、と聞いても何を言っているのかよく分からない方もいると思いますので、簡単に説明すると、
上で書いたPythonのコードはどこかしらの環境(場所)で実行しなければ意味がありません。そして実行する環境では、Python3とgspreadやrequestsなどのパッケージがインストールされている必要があります。
Herokuは、このようなPythonスクリプトなどのアプリを実行する環境を提供するサービスで、Herokuのサービスを使うことで24時間365日いつでもPythonコードを実行することができます。
しかも!Herokuには無料枠があるので今回のような小規模なコードを短時間で実行するのは無料でできます。
Herokuを使いこなすにはgitの知識が必要不可欠ですので、gitについてはあらかじめ学習しておいてください。
Heroku アカウント作成、CLIの導入
Herokuの公式ページからアカウントを作成します。
アカウントが作成できたら、Heroku CLI(Command Line Interface)を自分の環境にインストールします。インストール方法は、Heroku CLIのページを読みながら自分の環境に合わせてやります。
私の実行環境はChromebook(ARM)なので、非推奨ではありますがnode.jsでインストールする方法をとりました。
$ heroku login
でブラウザを開いてログインできるようになればOKです!
Chromebook LinuxにHeroku CLIをインストールする方法!
プロジェクトの作成
Herokuを使うとき、一つのディレクトリが一つのプロジェクトになります。
まずはディレクトリを作成してその中に、上で作成したPythonコードを配置してください。今回は、nature-remo-data-collectという名前のディレクトリにしています。
pipenv, pyenvを使っている場合は、一度作成したディレクトリ内で新しい環境をセットアップしてください。そして、必要なパッケージをインストールしておきます。
$ mkdir nature-remo-data-collect $ cd nature-remo-data-collect $ pipenv --python 3.7.4 $ pipenv install requests gspread oauth2client
ここから以下の3つのファイルを作ったディレクトリ内に用意します。
- requirements.txt (必要なPythonパッケージ一覧を列挙する)
- runtime.txt(使うPythonのバージョンを指定する)
- Procfile(どのPythonスクリプトを実行するのかを指定する)
順番に説明していきます!
まず、Herokuに自分のPythonコードが使うパッケージを教えてあげる必要があります。そのために、requirements.txtというファイルを用意して、そこに必要なパッケージを列挙します。が、一つ一つ列挙するのは大変なので、以下のようにして今の自分の実行環境に入っているパッケージ一覧を自動で出力して書き込むと楽です。
$ pipenv run pip freeze > requirements.txt
このときもpipenvなどでパッケージを管理しておけば、そのプロジェクトに必要なパッケージがはっきりするのでやはりpipenvはかなり便利です。
ただし、pkg-resources==0.0.0
がrequirements.txtに入ってきている場合にはその行を削除してください。以下のページでも質問されているように、pip freeze時のバグにより誤って入ってきてしまうもののようです。これがあると後々Herokuにコードをアップロードするときにエラーで止まってしまいます。
Herokuでは、requirements.txtという名前のファイルがあったときにはそのプロジェクトがPythonによるものだと判断して、requirements.txtに書かれたパッケージをインストールした実行環境を自動で作ってくれます。なので、requirements.txtのファイル名は変えないように注意してください。
次に用意するのがruntime.txtというランタイム(Pythonとそのバージョン)を指定するファイルです。使っているPython環境に合わせて書きます。
HerokuでサポートされているPythonバージョンは限られていますので、Heroku Python Supportのページで確認してください。今回は、python-3.7.4を使います。
$ echo python-3.7.4 > runtime.txt
最後に、Procfileですが、今回はメインの実行ファイルをworker.pyという名前にして、Herokuではこのworker.pyを実行してもらうようにします。
$ echo worker: python worker.py > Procfile
Procfileに今回書いたworkerというのは単なるラベル(Heroku用語でプロセスタイプと呼ぶ)です。
web, releaseというプロセスタイプだけは特別な意味を持っていて、例えば、webという名前のラベルを付けると自動的にHeroku側でHTTPのリクエストを受け付けるように処理してくれます。web, release以外のラベルだと単なる識別子的な意味しか持っていないようです。
しかも今回は後でHeroku Schedulerを使うので、このProcfileの内容は使われることはありません。が、一応用意はしておきます。
ここまでできたら、githubにプッシュしてコードを保存しておきましょう!Herokuはgitでコード管理されていることを前提にしているので必ずgithubにプッシュできる状態にしておいてください。
Herokuにコードをアップロード(push)する
まず、以下のコマンドでHerokuアプリ(app)を作成します。
アプリ名を指定しないとランダムで適当な名前になります。アプリ名を指定したほうが後々分かりやすいので、指定することをおすすめします!<app name>
の部分を分かりやすい名前に変えてください。
$ heroku create <app name>
このコマンドによりgit remoteにherokuが追加されます。<app name>は上で指定したアプリ名になります。指定しなかった場合は、ランダムに生成された名前になります。
$ git remote -v heroku https://git.heroku.com/<app name>.git (fetch) heroku https://git.heroku.com/<app name>.git (push) origin https://github.com/xxxx/nature-remo-data-collect.git (fetch) origin https://github.com/xxxx/nature-remo-data-collect.git (push)
これでherokuに自分のPythonスクリプト(アプリ)をアップロードできるようになりました。
以下のコマンドでアップロードします。
$ git push heroku master
エラーなしで完了すれば問題なしです。
アップロードしたアプリを実行する前に、heroku config:set
を使って環境変数を設定しましょう!ここで設定した変数はアプリ内から環境変数として参照することができます。
例えば、以下のようにコマンドを実行して設定します。
heroku config:set REMO_TOKEN=xxxx
ローカル環境での環境変数の設定と同じように、これらの設定も一つのファイルにまとめておいて、sourceでまとめて実行できるようにしておくと便利です!
Heroku Schedulerで実行する
次に、Herokuにアップロードしたアプリを定期実行するように設定します。今回は、無料で使えるadd-onの一つ、Heroku Schedulerを使って実行します。
Heroku Schedulerの問題点としては、無料だがクレジットカードの登録が必須であること、実行時間間隔を、10min、1h、1dayの3パターンからしか選べないことです。
今回のようなセンサーデータの取得では10min毎に実行できれば十分ですので、このheroku Schedulerを使うことにしました。
Heroku Schedulerは、HerokuのWeb ダッシュボード上からマウス操作で設定できます。もちろん、HerokuのAPIからも実行できるので自動化したい人はHeroku APIも見てみてください。
まず、Heroku Dashboardを開き、Dashboard > app選択 > Resource と画面遷移します。add-onsの検索窓で、「Heroku Scheduler」と入力して、表示される候補の中からHeroku Schedulerを選択します。

あとは、Provisionをクリックして流れに従って設定すれば問題ありません。画面UIもとてもわかりやすかった印象です。
クレジットカードなしでやる場合は、clockタイプで実行する
Heroku Schedulerはadd-onのため、クレジットカードの登録が必須でした。
学生などクレジットカードが持てない場合には、Heroku Schedulerは使えないので、clockタイプでアプリを実行する手段があります。
clockタイプは特別なプロセスタイプではありませんが、上の公式ドキュメントに書かれているように、実行したいジョブのスケジュールのみを制御するスクリプトを用意することが推奨されています。
公式ドキュメントの例を参考に以下のようなclock.pyというスクリプトを書いてみました。
import os from apscheduler.schedulers.blocking import BlockingScheduler import worker sched = BlockingScheduler(timezone="UTC") @sched.scheduled_job('interval', minutes=int(os.environ.get('REMO_CLOCK_INTERVAL', 1))) def collect_remo_data(): worker.run() sched.start()
ここでは新たにapschedulerというパッケージを導入していますので、requirements.txtの更新も必要です。
このapschedulerでは上のようにデコレータで、その関数を呼び出すタイミングを制御できます。関数の中では、worker.run()
を呼び出すだけのシンプルな構成にしておくのがポイントです!
Procfileも以下のように更新します。
worker: python worker.py clock: python clock.py
ここまでの変更をmasterブランチにcommitして、git push heroku master
でアップロードします。
Herokuでは、アプリをアップロードするだけでは何も実行されることはありません。先程のclock.pyを実行するために、以下のコマンドを実行します。
$ heroku ps:scale clock=1
こうすることで、Procfileのclockで指定したコマンド(python clock.py)が実行されて、clock.pyに書いたとおりにスケジュールされてジョブ(アプリ)が実行されます。
Heroku Schedulerでは、10min、1hour、1dayの3パターンしかインターバルを選べませんでしたが、自分でclock.pyを定義すれば、好きなインターバル、タイミングで実行することができます。
clock.pyで実行する場合にはアプリが常に起動している扱いになるためdyno時間の消費も大きいです。
$ heroku ps Free dyno hours quota remaining this month: 950h 51m (95%) Free dyno usage for this app: 49h 8m (4%) For more information on dyno sleeping and how to upgrade, see: https://devcenter.heroku.com/articles/dyno-sleeping === clock (Free): python clock.py (1) clock.1: up 2019/10/06 11:17:39 +0900 (~ 17h ago)
クレジットカード登録をしていない場合は500h/monthが無料枠の上限ですので、常時可動させることはできません(24hx30days=720h)。このあたりのherokuの無料枠設定は絶妙です。笑 なので、1週間や2週間など期間を絞って測定するのがよいです!
センサーデータの収集&グラフ化結果!
取得したセンサーデータをスプレッドシート内でグラフ化してみました!
横軸をうまく日時フォーマットで表示させたかったのですが、スプレッドシートの扱いがまだよくわからず、unixtimeで表示させました。
照度センサーの値(オレンジ、右軸)を見れると周期性があるので、だいたい5日間のデータであることが分かるかと思います。

うちはマンションなので、温度は26度付近で常に安定していて素晴らしい環境だと思いました!笑 一戸建てやアパートでも測ってみたいところですね!
一方で、湿度のこのなぞの動きはよくわかりませんね。その日の気候やエアコンのON-OFFに影響されやすいのかもしれません!
以上、Nature Remo + Python + Herokuによるお家データの定期測定でした!
今回書いたコードを以下で公開していますので、興味がある方はご覧ください。