22
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

KDDIアジャイル開発センター(KAG)Advent Calendar 2024

Day 16

カラー電子ペーパーを使った個人ダッシューボード作成 2024

Last updated at Posted at 2024-12-15

概要

カラー電子ペーパーを使ったダッシュボードを作成しました。
同様の記事は昔から多くありますが、色数が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でダッシュボード画面を作成します

ブラウザSS

今回は以下のサービスのAPIを利用しています

pm2でサーバーの起動&永続化

画面が作成できたらダッシュボードのサーバーを永続化します。

必要に応じてpm2のconfigを作成します。今回はサーバーを3000番ポートで公開します。

pm2.config.cjs
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でサーバーを起動します

shell
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 # 設定保存

設定が終わったら一度再起動し、サーバーが永続化されていることを確認します。

shell
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以外を使う場合は適宜読み替えてください

shell
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
/boot/firmware/config.txt
- #dtparam=spi=on
+ dtparam=spi=on

保存したらraspberrypiを再起動します

システムライブラリ・パッケージのインストール

ダッシュボードへの描画用に以下のパッケージをインストールします

  • swig
  • lgpio-dev
  • mise
  • uv
  • (python)
    • uvで作った仮想環境を使うなら不要
shell
# 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のセットアップを済ませておきます

shell
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に出力されます

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上でのデザイン作成時に便利です。

shell
cd epdash/packages/imagen
uv sync
echo "SS_URL=\"http://<server ip address>:<port number>/dashboard\"" > .env
uv run python -m imagen  # /outout に画像が出力される

縮小・ディザリング処理を行うと次のような画像が得られます。

dithered_image_masked.png

画像の表示サイズによってはモアレがかかって見えますが、元画像ファイルを見ればうまくディザリングできていることが確認できるかと思います。

電子ペーパーへの書き込み

電子ペーパーへの書き込みには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
shell
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 # 電子ペーパーの画像クリア

書き込んだ画像がこちら。反射で少し見にくいですが思ったよりきれいに表示できています
PXL_20241209_151822030.png

cronを設定

動作確認ができたらcronで毎日0時に電子ペーパーを更新します

shell
crontab -e
cron設定
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にします

shell
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等のいろいろなツールを(浅くではありますが)組み合わせることができとても楽しかったです。
みなさんも自分だけの電子ペーパーダッシュボードを作ってみてはどうでしょうか。

  1. GoogleカレンダーのAPIを使ってWebサイトに表示させる方法

  2. Raspberry Pi Zero(W, WH, 2 W)のセットアップ

22
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?