概要
カラー電子ペーパーを使ったダッシュボードを作成しました。
同様の記事は昔から多くありますが、色数が2~3色だったり、描画をすべてPillowで行っている都合でUIの作成が大変だったりします。
今回は勉強も兼ねて以下の技術等を利用し、2024年にふさわしい綺麗で管理しやすいダッシュボードを作成します。
私の作ったソースコードは以下から確認できます。
利用したツール等
ハードウェア
- Waveshare 7.3inch e-Paper HAT (F)
- 7色描画可能な電子ペーパー
- Raspberry pi
- なんでもいいですが、Raspberry pi Zero 2 WH もしくはRaspberry pi 4/5 を想定しています
- 画像の取得、電子ペーパーの描画に利用します
- 本記事ではRaspberry pi Zero 2 WHを利用します
- 適当なサーバー(Raspberry pi Zero 2を利用する場合)
- ダッシュボードをWebページとしてローカルで公開するのに使います
- Raspberry pi Zero 2を利用する場合、Webサーバーを立ち上げつつ画像を取得するのはメモリの制限上厳しいため別途用意します
- Raspberry pi 4/5を利用する場合はWebサーバーとスクリーンショットの取得を同一マシン上で行っても動くので不要です(確認済み)
ライブラリ・ソフトウェア
javscript / typescript
- bun
- ダッシュボードプロジェクトの開発・パッケージ管理に使います
- remix
- ダッシュボードのUIを作るフレームワークです
- pm2
- サーバーの永続化に使います
python
- uv
- pythonのパッケージ管理に使います
- playwright
- サーバーでホストしたダッシュボードのスクリーンショットを取得するのに使います
- pillow
- 取得した画像の減色/ディザリング処理に利用します
その他
- mise
- python, nodeのバージョン管理に利用しています
サーバーのセットアップ
今回、ダッシュボードの画面はRemixを利用したWebページとして作成します。
そのためのWebページをホストするために raspberry piとは別にサーバーを利用します。
Raspberry pi 4/5を利用する場合は別途サーバーを用意せずRaspberry pi上でホストすることも可能です。
Raspberry pi zero 2ではメモリが厳しく安定した動作が難しいため別途サーバーを用意します。
システムライブラリ・パッケージのインストール
ダッシュボードのサーバー用に以下のパッケージをインストールします
- mise
- bun
- node
- pm2
# miseのインストール
# https://mise.jdx.dev/getting-started.htmlを参照してください
curl https://mise.run | sh
# bunのインストール
mise use -g bun@latest
# node
mise use -g node@latest
# pm2のインストール
bun add -g pm2
ダッシュボード画面の作成
今回はremixを利用してダッシュボード画面を作成します。
電子ペーパーのサイズに合わせて800 x 480 pxでダッシュボード画面を作成します
今回は以下のサービスのAPIを利用しています
-
OpenWeatherMap
- 天気予報の取得
-
GoogleCalenderAPI
- カレンダーイベントの取得
- こちら1の記事を参考にAPIキーを使った認証を利用しています
-
今日は何の日API
- 右上の記念日の表示に利用
pm2でサーバーの起動&永続化
画面が作成できたらダッシュボードのサーバーを永続化します。
必要に応じてpm2のconfigを作成します。今回はサーバーを3000番ポートで公開します。
module.exports = {
apps: [
{
name: "dashboard",
script: "remix-serve",
args: "build/server/index.js",
autorestart: true,
restart: "on-failure",
error_file: "/dev/null",
out_file: "/dev/null",
env: { PORT: 3000, NODE_ENV: "production" },
node_args: "--env-file .env",
},
],
};
設定ファイルを作ったらpm2でサーバーを起動します
bun run build # ビルド
pm2 start pm2.config.cjs
pm2 startup
# 次のようなスタートアップの設定用スクリプトが表示されるのでそれをコピー&ペーストして実行
# [PM2] To setup the Startup Script, copy/paste the following command:
# sudo env PATH=$PATH:/home/<username>/.nvm/versions/node/v22.12.0/bin /home/<username>/.bun/install/global/node_modules/pm2/bin/pm2 startup systemd -u <username> --hp /home/<username>
pm2 save # 設定保存
設定が終わったら一度再起動し、サーバーが永続化されていることを確認します。
pm2 ls
# ┌────┬────────────────────┬──────────┬──────┬───────────┬──────────┬──────────┐
# │ id │ name │ mode │ ↺ │ status │ cpu │ memory │
# ├────┼────────────────────┼──────────┼──────┼───────────┼──────────┼──────────┤
# │ 0 │ dashboard │ fork │ 0 │ online │ 0% │ 165.2mb │
# └────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘
最後に、PCからもアクセスし接続できることを確認します。
今回作成したサンプルプロジェクトの場合はhttp://<server ip address>:3000/dashboard
で確認できます。
Raspberry pi のセットアップ
Raspberry pi Imagerを利用してマウス、キーボード、モニタなしでセットアップします。
sshでアクセスできるようにしておきます。
セットアップ手順はこちら2の記事を参考にしました。
初回のセットアップが完了したら、電子ペーパーを使うための設定、ライブラリをインストールします
vimのインストール(任意)
vim-tinyに慣れていないのでvimをインストールします。vim以外を使う場合は適宜読み替えてください
sudo apt-get --purge remove vim-common vim-tiny
sudo apt-get install vim
spi通信の有効化
/boot/firmware/config.txt
を編集しspi通信を有効化します
vim /boot/firmware/config.txt
- #dtparam=spi=on
+ dtparam=spi=on
保存したらraspberrypiを再起動します
システムライブラリ・パッケージのインストール
ダッシュボードへの描画用に以下のパッケージをインストールします
- swig
- lgpio-dev
- mise
- uv
- (python)
- uvで作った仮想環境を使うなら不要
# swig, lgpio-devのインストール
sudo apt-get install swig lgpio-dev
# miseのインストール(まだの場合)
# https://mise.jdx.dev/getting-started.htmlを参照してください
curl https://mise.run | sh
# uvのインストール
mise use -g uv@latest
# pythonのインストール
mise use -g python@latest
playwrightのセットアップを済ませておきます
cd <path to project dir>
uv add playwright
uv sync
uv run python -m playwright install
ダッシュボード画面のスクリーンショットを取得する
playwrightを利用してダッシュボード画面のスクリーンショットを取得します。
ポイント
- スクリーンショットを取るに当たりRaspberry pi Zero 2ではメモリに大きな制約があるため、以下の通り
CHROMIUM_FLAGS
を設定して軽量化します -
device_scale_factor=2
を設定し、縦横2倍の解像度でスクリーンショットを取得します。大きめの画像を利用することで後に縮小&ディザリング処理をしたとき、より綺麗な画像が得られます
from pathlib import Path
from os import PathLike
from playwright.sync_api import sync_playwright
CHROMIUM_FLAGS = [
"--disable-gpu",
"--disable-dev-shm-usage",
"--disable-setuid-sandbox",
"--no-first-run",
"--no-sandbox",
"--no-zygote",
"--single-process",
"--disable-audio-output",
"--disable-background-timer-throttling"
"--disable-backgrounding-occluded-windows"
"--disable-breakpad",
"--disable-extensions",
"--disable-sync",
"--disable-translate",
]
def get_url_image(url: str, dir: PathLike, name: str):
with sync_playwright() as p:
browser = p.chromium.launch(headless=True, args=CHROMIUM_FLAGS)
context = browser.new_context(
viewport={"width": 800, "height": 480},
device_scale_factor=2,
)
page = context.new_page()
page.goto(url)
page.screenshot(path=str(Path(dir) / name), full_page=True)
browser.close()
get_url_image("http://<server ip address>:3000/dashboard","out/dashboard.png")
スクリーンショットがoutput/dashboard.png
に出力されます
画像の縮小&ディザリング処理
Pillowを利用して画像を縮小&ディザリング処理を行います
ポイント
- 大きめの画像を縮小することで文字や細い線を綺麗に描画できます
- 今回利用する電子ペーパーの使用可能色に合わせてディザリング処理を行います
今回利用するWaveshare 7.3inch e-Paper HAT (F)で利用可能な以下の7色でパレットを作成し、Pillowのquantize
関数を使って減色を行います
- [255, 0, 0] # Red
- [0, 255, 0] # Green
- [0, 0, 255] # Blue
- [255, 255, 0] # Yellow
- [255, 128, 0] # Orange
- [0, 0, 0] # Black
- [255, 255, 255] # White
from PIL import Image
import numpy as np
def reduce_color_for_epd_7in3f(image: Image.Image):
image = image.convert("RGB")
image = image.resize((800, 480))
EPAPER_PALETTE = np.array(
[
[255, 0, 0], # Red
[0, 255, 0], # Green
[0, 0, 255], # Blue
[255, 255, 0], # Yellow
[255, 128, 0], # Orange
[0, 0, 0], # Black
[255, 255, 255], # White
]
)
palimage = Image.new("P", (16, 16))
palimage.putpalette(list(EPAPER_PALETTE.flatten()))
return reduce_color(image, palimage)
def reduce_color(image: Image.Image, palette: Image.Image):
return image.quantize(palette=palette, dither=Image.Dither.FLOYDSTEINBERG)
image = Image.open("out/dashboard.png")
dithered_image = reduce_color_for_epd_7in3f(image)
dithered_image.save("output/dithered_image.png")
サンプルプロジェクトの場合はpackages/imagen
を使って画像を取得しディザリング処理した結果を保存できます。
このプロジェクトはgpio等を使っていないため、Raspberry piで実行する前にディスプレイ上で結果を確認できます。PC上でのデザイン作成時に便利です。
cd epdash/packages/imagen
uv sync
echo "SS_URL=\"http://<server ip address>:<port number>/dashboard\"" > .env
uv run python -m imagen # /outout に画像が出力される
縮小・ディザリング処理を行うと次のような画像が得られます。
画像の表示サイズによってはモアレがかかって見えますが、元画像ファイルを見ればうまくディザリングできていることが確認できるかと思います。
電子ペーパーへの書き込み
電子ペーパーへの書き込みにはwaveshare-epaperパッケージが利用できます。
from PIL import Image
import epaper
from os import PathLike
import time
def draw(model: str, img_path: PathLike):
try:
epd = epaper.epaper(model).EPD()
epd.init()
draw_img = Image.open(img_path)
epd.display(epd.getbuffer(draw_img))
epd.sleep()
except IOError as e:
print(e)
except KeyboardInterrupt:
epaper.epaper(model).epdconfig.module_exit(cleanup=True)
exit()
def clear(model: str):
try:
epd = epaper.epaper(model).EPD()
epd.init()
epd.Clear()
epd.sleep()
except IOError as e:
print(e)
except KeyboardInterrupt:
epaper.epaper(model).epdconfig.module_exit(cleanup=True)
exit()
draw("epd7in3f", "output/dithered_image.png")
time.sleep(5)
clear("epd7in3f")
サンプルプロジェクトの場合はpackages/drawer
を使って画像を取得し電子ペーパーへの書き込みまで実行できます。こちらはgpioピンを使ったspi通信等を行うためRaspberry piでのみライブラリのインストール&実行が可能です。
実行できない場合は前述した以下のライブラリがインストールされていることを確認してください
- swig
- lgpio-dev
cd epdash/packages/drawer
uv sync
echo "SS_URL=\"http://<server ip address>:<port number>/dashboard\"" > .env
uv run draw-calendar.py # 電子ペーパーへの書き込み
uv run clear.py # 電子ペーパーの画像クリア
書き込んだ画像がこちら。反射で少し見にくいですが思ったよりきれいに表示できています
cronを設定
動作確認ができたらcronで毎日0時に電子ペーパーを更新します
crontab -e
PATH=<'echo $PATH' したものと同じPATHを設定>
0 0 * * * cd <path to project dir> && uv run draw-calendar.py
OverlayFSの有効化(ファイルシステムのReadOnly化)
この手順はRaspberry pi 4/5を利用する場合のみ行います。Raspberry pi Zeroではシステムリソースが厳しく、OverlayFSを有効化するとplaywrightがうまく動作しません
長期間運用することを想定し、電源断時のOS保護のためOverlayFSを有効にし、ファイルシステムをReadOnlyにします
sudo raspi-config
以下の通り選択します
- 4 Performance Options Configure performance settings
- P3 Overlay File System Enable/disable read-only file system
- Would you like the overlay file system to be enabled?
- Yes
- Would you like the boot partition to be write-protected?
- Yes
設定が終わったら再起動します
まとめ
Raspberry Pi Zero 2とカラー電子ペーパーを使用して個人ダッシュボードを作成しました。
Remixを使った画面構築とPillowによるディザリング処理により、個人的に満足の行く画面を7色電子ペーパーでありながら出力できました。
うまくいかなかったこと・今後の改善点
- 今回はRaspberry pi Zero W2 のメモリ制限が思った以上にきつく、ダッシュボードのサーバーの用意が必要でした。サーバーを用意する代わりにCloudflare PagesにてBasic認証つきで公開することも考えましたが、時間が取れなかったため一旦断念しました。時間があればやってみたいです
- Raspberry piの電源断対策としてファイルシステムをReadOnlyにしたかったのですが、Raspberry pi Zero 2では使用メモリが厳しく断念しました。長期運用したいダッシュボードとしては是非設定したいところなので改善したいところです。コンパクトさを捨ててRaspberry Pi 4や5を使ってもいいですが、せっかく低消費電力である電子ペーパーを使うのであればRaspberry pi Zeroにこだわりたいところなので悩ましいです
- 電子ペーパー自体が少し赤みがかったグレーの色合いをしているため、ディザリングした画像を実際に描画すると少し赤色の強い画像になってしまいます。減色時のパレットの白色を適切に変更することでもう少し色を近づけられるかもしれません
- ダッシュボードの画面としてWebページを作成しているため、別のURLパスとして画面を作ればOAuth認証をさせたり、設定画面を作ったりすることも可能です。例として、今回は天気の都市は環境変数として設定しましたが、設定画面で変更できるようにしても良さそうです
最後に
今回は mise
,bun
, remix
, pm2
, uv
, playwright
, pillow
, 電子ペーパー
, Raspberry pi
等のいろいろなツールを(浅くではありますが)組み合わせることができとても楽しかったです。
みなさんも自分だけの電子ペーパーダッシュボードを作ってみてはどうでしょうか。