DIYで作る電子ペーパー額縁に表示する内容を外から変える(cgi編)

2023年3月28日

DIYで作る電子ペーパー額縁の作り方(ハード編)DIYで作る電子ペーパー額縁の作り方(ソフト編) のつづき。

表示している画像を外から変更できるようにします。
Webサーバを動かしてcgiで画像を変えられるようにすることで外部からhttpアクセスで画像を変更できるようにします。

簡単なのはCGI自体で電子ペーパーを書き換えてしまう形ですが、ちょっとこの後の展開上の必要もあって間にjsonファイルを挟む形にします。

図で書くとこんな感じ。

jsonファイルのフォーマット

中間のjsonファイルのフォーマットには白黒用、赤用の画像ファイル名を格納しておきます。
画像ファイル自体はラズパイのディレクトリにあらかじめ入れておきます。

{
	"BnWImage":"白黒画像ファイル名",
	"RedImage":"赤画像ファイル名"
}

Webサーバとcgi

Webサーバには軽量Webサーバのlighttpdを使います。

sudo apt install lighttpd

cgiを有効にします。
/etc/lighttpd/conf-available/cat 10-cgi.conf

# /usr/share/doc/lighttpd/cgi.txt

server.modules += ( "mod_cgi" )

$HTTP["url"] =~ "^/cgi-bin/" {
        cgi.assign = ( "" => "" )
        alias.url += ( "/cgi-bin/" => "/cgiファイルのあるディレクトリ/cgi-bin/" )
}

そしてcgiです。
以下は、入力値チェックなどを省いた最低限の処理ですので必要に応じて入力チェックの追加やアクセス制限を必ず追加してください。

#!/usr/bin/python
import cgi
import json

image_pattern = [
	{
		"BnWImage":"白黒画像ファイル名①",
		"RedImage":"赤画像ファイル名①"
	},
	{
		"BnWImage":"白黒画像ファイル名②",
		"RedImage":"赤画像ファイル名②"
	},
	{
		"BnWImage":"白黒画像ファイル名③",
		"RedImage":"赤画像ファイル名③"
	},
]
try:
	form = cgi.FieldStorage()
	imageno = int(form.getvalue('no',''))

	with open("jsonファイル名", "wt") as f:
		json.dump(image_pattern[imageno], f)

	print("OK")

except Exception as e:
	print("NG")

常駐アプリ

次に常駐アプリ側です。ファイル名はsignplate.py とします。
ここだけの話、すでに初期バージョンが残ってないので記憶を頼りに書いてます。動かなかったらごめんない。

from PIL import Image, ImageDraw, ImageFont

from waveshare_epd import epd7in5b_V2

epd = epd7in5b_V2.EPD()

last_images = []
def display_image(loop):
	global last_images

	with open('jsonファイル名') as f:
		images = json.load(f)

	if images["BnWImage"] != last_image["BnWImage"] or images["RedImage"] != last_image["RedImage"]:
		# 前回と表示画像ファイル名が異なる時のみ電子ペーパーに描画する。
		Himage_bnw = Image.new('1', (epd.width, epd.height), 255)
		Himage_red = Image.new('1', (epd.width, epd.height), 255)
		Himage_bnw.paste(Image.open(images['BnWImage']), (0,0))
		Himage_red.paste(Image.open(images['RedImage']), (0,0))
		epd.init()
		epd.display(epd.getbuffer(Himage_bnw), epd.getbuffer(Himage_red))
		epd.sleep()
		last_images = images

	# 5秒に1回チェック
	loop.call_later(5, check_calendar, loop)


# メインループ
loop = asyncio.get_event_loop()
loop.call_soon(display_image, loop)

常駐させたいので、ユーザ権限でsystemdのサービスとして動かします。
ユーザー権限で systemd のサービスを稼働させる方法 – pyopyopyo – Linuxとかプログラミングの覚え書き –

起動したいユーザのホームディレクトリ下にユニットファイルを作成します。
~/.config/systemd/user/signplate.service
内容は以下のイメージ。

[Unit]
Description=signplate

[Service]
# 起動コマンド(ExecStart)はイメージで書いています。実際にはシェルスクリプトを指定しています。
ExecStart=python signplate.py  
# kill で落とす場合は、必要であれば python のプログラム側でもSIGTERMを受けて終了処理をするなど書いておきます。
ExecStop=/usr/bin/pkill -f 'python signplate.py'
Restart=always

[Install]
WantedBy=default.target

サービスに登録。

systemctl --user enable signplate
systemctl --user start signplate

OS起動時にユーザサービスを自動起動するためにはrootで以下の操作が必要。

sudo loginctl enable-linger ユーザー名

systemctl から起動されたプロセスの出力は、journalctl で見ることができます。

journalctl --user-unit signplate

使い方

あとは、cgi叩くときに画像ファイルの番号を指定して呼び出せば、あらかじめ登録されていた画像ファイルが表示されます。

http://192.168.x.x/cgi-bin/signplate-cgi.py?no=1

最後に

とりあえずこれでラズパイの外から変更できるようにはなりましたが、ラズパイと同一ネットワーク内からでないと変更できません。
できればSwitchBotとかと連動できるようにインターネット側から変更したい。

今のままでも ngrok などのサービスを使ってインターネットからのアクセスをラズパイに呼び込めばできないこともないですがインターネットから呼び込むなんて怖い怖い。

せめてIPアドレス制限とかできればと思いましたが、ngrok ソースIP制限とか有償プランじゃないとできないみたいなのですよねー。

てことで少し別のアプローチを取ることにしました。それは別の機会にー。

Raspberry Pi

Posted by toshyon