電子ペーパーでGoogleと連動したカレンダーを作る

ふとした瞬間にスケジュールをパッと確認できると便利ですよね.スマホやパソコンで Google カレンダーを開けば良い話ですが,毎度操作するのは煩わしく,もっと少ないステップで見られると嬉しいなあと思ってきました.

Google Nest Hub や Amazon Echo Show は欲しいものとはちょっと違いますし,いい感じのデバイスも見つかりません.

ということで,Raspberry Pi と電子ペーパーを使って Google カレンダーと連動したカレンダーを作ってみることにしました.

製作方針

  1. 電子ペーパーをゲットする
  2. Google カレンダーの内容を API で取得する
  3. 自分のスケジュールを反映したカレンダーを画像にする
  4. 電子ペーパーに出力する

ソフトウェア

順番は前後しますが,考えているものを実装できるのか試しておく必要があるので方針2と3を先に行います.

前例をちらほら見かけるので,今回は Python を使います.

実装だけ見たいという方は記事最下部のリポジトリを参照してください.

プロジェクトディレクトリ作成

terminal

// create the project directory
$ poetry new my-project
// initialize configuration if the directory exist
$ poetry init
// add packages
$ poetry add google-api-python-client google-auth-httplib2 google-auth-oauthlib pillow

Google の API による認証とデータの取得,取得したデータを画像にするためのパッケージを入れます.

Google カレンダーのイベントを取得する

Google Calendar APIを用いてスケジュールのイベントを取得します.API の基本的な使用方法は以下の記事の通りです.

https://blog.mktia.com/get-my-schedule-from-google-calendar

私が実装したいのは

  • 一週が日曜日に始まり,土曜日に終わる
  • 日祝日は日付の色が赤色で表示される
  • 用事がある日にはマークだけつける
  • 今日以降の予定の具体的な内容と詳しい日時はリストで表示する

というカレンダーなので

  • 日本の祝日をイベントとして持つカレンダーを使用して祝日の日付を取得
  • その月にあるイベントの日程のみを取得
  • 更新日以降にあるイベントの日程とその内容を取得

できるように編集します.編集内容は GitHub のコードに反映されています.

Google カレンダーとの連携には認証用ファイルが必要です.credentials.json が存在していれば初回の実行時に認証を求められ,一度認証すると token.pickle ファイルが生成されるため,以降はこのファイルを使って自動で認証できるようになります.

Raspberry Pi Zero WH では処理能力が低くブラウザによる認証が難しいので,予め他の PC で token.pickle を生成し,SCP で送信するのが良さげです.

画像生成

カレンダーを画像を電子ペーパーのディスプレイに出力するため,画像を自動で生成する必要があります.

Python にはいくつかメジャーな画像系ライブラリがありますが,OpenCV とかはちょっと面倒ですし高度な画像処理を必要としないので Pillow (PIL) を使います.

参考:Kindle のディスプレイ応用例

記事の長さが大変なことになるのでコードは貼りませんが,実装例は GitHub で公開しています.

connect_calendar.py は Google カレンダーから情報を取得する簡単なモジュール,main.py はカレンダーの生成から電子ペーパーの制御までを行うメインのスクリプトです.

カレンダーは年月によって日数や月初の曜日が異なるため,calendar パッケージで解決します.デフォルトではヨーロッパに合わせて月曜日始まりのため,日曜日始まりに修正しています.また,パッケージでは祝日が取得できないので,Google カレンダーの「日本の祝日」カレンダーを用いて祝日かどうか判断します.

後は好きなフォントを選んで適当なところに線を引いたり位置を調整したりして形を作れば完成です!

文字の位置合わせが若干面倒ですが,multiline_textbbox を使うとテキストの外枠が取得できます.

電子ペーパーの調達

画面サイズ大きめの電子ペーパーの購入にあたって Waveshare Electronics 社(微雪电子有限公司)のものが比較的調達しやすそうでした.モノによっては国際配送になるかもしれませんが,日本の代理店もあるようです.

参考:Distributors | Waveshare

7.5 インチで 7,000 円弱,制御基盤に用いる Raspberry Pi に Zero WH を使うと想定すれば,予算は全て含めて 10,000 円弱です.

実は Kindle をゴニョゴニョするといけるかも!とメルカリで綺麗な中古を調達したのですが,ファームウェアバージョンの関係で Jailbreak できませんでした.

(参考)

購入した第8世代は KT3W (Kindle Touch 3 White) で,JB 可能なのは FW 5.8.7 未満.入手したものは FW 5.12.4 ということで,普通に電子書籍リーダーを購入した人になりました(Kindle Paperwhite あるのに…).

ちなみに,ファームウェアダウングレードからの JailBreak は不可です.ファームウェアが古いときに JB していればアップデート後も使えるようですが,まっさらな状態で入れることはできません.どうしても JB したいなら Kindle を分解し,シリアルポートに直接はんだ付け等で接続してターミナル操作するしかないようです.

ハードに弱い私にそれは無理なので,結局冒頭に述べた構成で作ることにしました.

  • Waveshare 7.5inch HD (880x528) e-paper black/red/white (B)
  • Raspberry Pi Zero WH
  • microSD 16GB (余ってたもの)
  • miniHDMI 変換ケーブル
  • microUSB ハブ

電子ペーパーは深圳から国際配送で 10 日ほど.配送方法に epacket(国際 e パケットライト)を選択したため China Post でしたが,配送料3倍の DHL とかならもう少し早いかもしれません.解像度にこだわらなければ国内でも多少入手しやすいと思います(千石電商とか).

Waveshare の電子ペーパーは色ごとに別々のイメージを生成する必要があります.購入したものは3色の電子ペーパーなので,黒で表示する部分と赤で表示する部分を別々のイメージとして入力します.

Raspberry Pi の準備

Raspberry Pi には様々な種類がありますが,今回はスペックを必要としていないので安くて扱いやすい Zero WH を選択しました.W はワイヤレス対応(Wi-Fi),H はヘッダピン付き(無印では要はんだ付け)という意味です.

Raspberry Pi を操作するために,microSD カードに Raspberry Pi OS(旧 Raspbian)をインストールしておきます.Raspberry Pi Imager という便利なソフトを使えば,SD カードを挿入してボタンを押すだけ.他の PC で事前に準備できるため,OS インストールは以前より時間がかからなくなったようです.

書き込みが終了したら Raspberry Pi に挿入して "PWR IN" と書かれた microUSB ポートにケーブルを接続すれば電源が入ります.電源ボタンはないので,シャットダウンするときはコマンドで入力します.

terminal

$ shutdown -h now

セキュリティ対策

miniHDMI はついていますが,SSH で同一 LAN 内から操作できた方が楽なので設定しておきます.デフォルトの設定では外部からの攻撃に弱いため,設定の変更が必要です.

参考:買ったらまず実施!Raspberry Pi のセキュリティ対策 | Qiita

まず,新規ユーザ名の作成とデフォルトユーザの削除を行います.

terminal

$ sudo passwd root
$ sudo adduser USERNAME
$ groups pi
$ sudo usermod -G pi,adm,dialout,cdrom,sudo,audio,video,plugdev,games,users,input,netdev,spi,i2c,gpio,lpadmin USERNAME
$ groups USERNAME
$ sudo cp -r /home/pi/* /home/USERNAME

次に,新規ユーザでログインしてデフォルトユーザを削除します.初期設定ではオートログインが有効になっていて,ユーザを選択できません.

terminal

$ sudo raspi-config

上記のコマンドで表示される設定画面で System Options > Boot / Auto Login > Console でオートログインを無効化し,コンソールでログインできるようにします.設定したら再起動です.

terminal

$ sudo reboot

再起動されると CUI が表示されるので新しいユーザ名とパスワードを入力してログインし,デフォルトアカウントの削除を行います.

terminal

$ sudo userdel -r pi
$ id -a pi
id: 'pi': no such user

CUI ではなく GUI で再び操作できるように,再び設定を変更します.このとき,オートログインは無効のままにしておきます.

terminal

$ sudo raspi-config

System Options > Boot / Auto Login > Desktop を選択して GUI を有効化します.

terminal

$ sudo reboot

これでログインウィンドウが出るようになります.

SSH 有効化

terminal

$ sudo raspi-config

Interface Options > SSH で SSH(ポート番号 22)が有効になります.本来は公開鍵認証の方が良いのですが,とりあえずパスワード認証で初期設定は済ませます.

terminal

$ ssh USERNAME@raspberrypi
$ ping raspberrypi.local

上記コマンドで SSH が有効であることと,ローカル IP アドレスが確認できます.これで,同一 LAN 内の PC からも接続できるようになりました.

SPI 有効化

電子ペーパーを操作するため,デフォルトで無効になっている SPI も有効化します.CUI で Interface Options > SPI > enable にします.

terminal

$ sudo raspi-config
$ sudo reboot

最低限の Python 環境諸々も準備します.大方インストール済みでした.

terminal

$ sudo apt-get update
$ sudo apt-get install python3-pip
$ sudo apt-get install python3-pil
$ sudo apt-get install python3-numpy
$ sudo pip3 install RPi.GPIO
$ sudo pip3 install spidev

BCM2835,WiringPi ライブラリは Python で実装するなら必要なさそうです.

電子ペーパーのテスト

公式が用意しているデモコードを動かしてみます.

terminal

$ sudo apt-get install git
$ git clone https://github.com/waveshare/e-paper.git
$ cd ./e-paper/RaspberryPi_JetsonNano/python/examples
$ python epd_7in5b_HD_test.py

画面映らない~って試行錯誤していたら,電子ペーパーの端子を裏表逆に挿していました.

本番の準備

なんとか無事出力できたので,実装したコードを実行してみます.Python バージョンが古いので,元の環境が壊れないように仮想環境を用意します.

ちなみに,Raspberry Pi Zero WH(シングルコア,メモリ 512MB)のため,インストールはなかなか時間がかかります.CPU 使用率が 100% に張り付き,Python 入れるだけで3時間,パッケージ追加して3時間…という単位でかかるので,ゆっくり進めてください.

terminal

// install pyenv
$ git clone https://github.com/pyenv/pyenv.git ~/.pyenv
$ echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc
$ echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc
$ echo -e 'if command -v pyenv 1>/dev/null 2>&1; then\n  eval "$(pyenv init -)"\nfi' >> ~/.bashrc

// install poetry
$ curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -

pyenv が入ったら適当なバージョンの Python をインストールします.

terminal

$ sudo apt-get install -y build-essential libssl-dev zlib1g-dev libbz2-dev \
libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev \
xz-utils tk-dev libffi-dev liblzma-dev python-openssl git
$ pyenv install 3.9.0

Pillow のインストールで詰まるので,必要なパッケージをインストールしておきます.

参考:# Building on Linux | Installation - Pillow

terminal

$ sudo apt-get install libtiff5-dev libjpeg8-dev libopenjp2-7-dev zlib1g-dev \
    libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python3-tk \
    libharfbuzz-dev libfribidi-dev libxcb1-dev

仮想環境はプロジェクトディレクトリ下に生成するようにしておきます.

terminal

$ poetry config virtualenvs.in-project true --local

.env

PYTHONPATH=.venv

必要なパッケージは一通り Poetry で入るようにしてありますが,一部 Raspberry Pi のみで使うパッケージは含めておらず,別でインストールが必要です.

terminal

$ git clone https://github.com/mktia/e-calendar.git && cd ./e-calendar
$ pyenv local 3.9.0
$ poetry install
$ pip3 install RPi.GPIO
$ pip3 install spidev

カレンダーの自動更新

定期的に Google カレンダーから最新情報を取得して自動で反映してほしいので,crontab で毎時間処理を実行するようにします.

cron の準備

まず,crontab のログを有効化します.

terminal

$ sudo vi /etc/rsyslog.conf

rsyslog.conf

###############
#### RULES ####
###############

#
# First some standard log files.  Log by facility.
#
auth,authpriv.*                 /var/log/auth.log
*.*;auth,authpriv.none          -/var/log/syslog
// uncomment
cron.*                          /var/log/cron.log

terminal

$ sudo /etc/init.d/rsyslog restart

ログ記録用のファイルも作っておきます.

terminal

$ sudo touch /var/log/e-calendar.log
$ sudo chmod 777 /var/log/e-calendar.log

次に,cron のサービスの状態を確認します.

terminal

$ sudo /etc/init.d/cron status
● cron.service - Regular background program processing daemon
   Loaded: loaded (/lib/systemd/system/cron.service; enabled; vendor preset: enabled)
   Active: active (running) since Fri 2021-03-19 14:17:29 JST; 3h 11min ago
     Docs: man:cron(8)
 Main PID: 262 (cron)
    Tasks: 1 (limit: 877)
   CGroup: /system.slice/cron.service
           └─262 /usr/sbin/cron -f

既に起動していたのでそのまま使用します.もし起動していなければ,start してください.

cron の設定

cron は以下のコマンドで編集できます.

terminal

$ crontab -e

ユーザとしてシェルを実行するわけではないので,環境変数が引き継がれないことに注意する必要があります.

まず,シェルスクリプトを用意します.

terminal

$ touch ~/e-calendar.sh
$ sudo chmod 755 ~/e-calendar.sh
$ nano ~/e-calendar.sh

e-calendar.sh

#!/bin/bash

SHELL=/bin/bash
PYENV_ROOT=$HOME/.pyenv
POETRY_ROOT=$HOME/.poetry
PATH=$PYENV_ROOT/bin:$POETRY_ROOT/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

if command -v pyenv 1>/dev/null 2>&1; then
  eval "$(pyenv init -)"
fi

cd ~/e-calendar
$POETRY_ROOT/bin/poetry run python main.py

次に,用意したシェルスクリプトを cron に登録します.1時間1回の実行です.

crontab

0 * * * * /home/USERNAME/e-calendar.sh > /var/log/e-calendar.log 2>&1

上記の設定ではログが上書きされていきます.追記していく場合は設定を変更してください.

完成品

電子ペーパーを卓上カレンダーとして加工するならフォトフレームとか額縁を使うと良さそうです.アクリル製のフォトフレームも良さげでしたが,最終的に下のフォトフレームを選択しました.

7.5 インチの電子ペーパーは 2L 判の写真より少し小さいくらいの大きさです.いい感じに隙間を埋めればそれっぽく見えます.

マット紙の向きを変更すれば縦横どちらも対応可能です.横向きにして隙間を適当な画用紙で埋めたら以下のような出来になりました.

e-calendar-1.jpg

ベゼルレスの方が好みですが,これも意外と悪くありません.ただ,このフレームの大きさは 24cm 四方なのでそこそこ大きく,机の上に置いていますが若干邪魔になっています….電源が取れるのであれば壁掛けも可能です.

ちなみに,裏面に配線を逃していますが雑な感じです.埃が積もって発火しないように対策が必要かもしれません.

e-calendar-2.jpg

参考

最後に

電子ペーパーってカレンダーにめっちゃ向いているんじゃないかと個人的に思っています.消費電力が少ない上に,更新するのはカレンダーの内容が変化した時だけ.処理も重くないのでプロセッサやバッテリーは最小限でよく,軽量化しやすいので紙のカレンダーのように壁に掛けられるデバイスになりますよね.

問題は,面積の大きい電子ペーパーの調達コストが高いことです.調べた感じでは 13inch の電子ペーパーだと5万円は下らないようで,カレンダーと考えるとかなり高めです.しかし,電子ペーパーの開発元は実質 E Ink 社の一強で競争は起きそうにありません.作っても赤字になりそう.

以前に Magic Calendar なるものが Android Experiments OBJECT グランプリとなり,そう!それだよ!と思ったのですが….多色で高精細電子ペーパーを使って3ヶ月持続するバッテリーを搭載した壁掛けカレンダー,夢はありますがそのレベルに達した電子ペーパーはありません.

大画面電子カレンダーの開発,待ってます.

GitHub: mktia/e-calendar

(追記 2023.12.11)

時の経過とともに技術も進み,とうとうカラーで大画面の商品が出始めました!

E ink 社が 2023 年 4 月に発表した E Ink Spectra™ 6 を搭載した商品(ePoster)が SHARP 社から発表されていました.カラーモデルで最大の 25.3 型は 2023 年 10 月に発売されたばかりです. コンシューマ向けではないですが,いずれ一般消費者向けにも販売されることを願っています. いくつかの EC サイトで取り扱われているのを確認しましたが,25.3 型の価格は税込 40 万円以上でした…笑