Lisp と M5Stack でプライバシー第一のデジタルフォトフレームを作る
ほとんどの家電製品がクラウドサブスクリプションと個人データの提供を要求する時代において、家族の思い出をシンプルかつプライベートに共有したいという欲求は驚くほど稀です。商用のデジタルフォトフレームは高価で、必須のアカウントがプライベートな写真がインターネットに漏れたり、AIモデルの学習に使用されたりするリスクがあります。
この課題を解決するために、開発者 chrisjj は M5Stack Tab5 と uLisp を用いてカスタムデジタルフォトフレームを構築しました。ロジックを Lisp ベースのファームウェアに移行し、画像をプライベートサーバーにホストすることで、高解像度ディスプレイと完全なデータ主権のバランスを実現しています。
ハードウェアとディスプレイの仕様
このプロジェクトは M5Stack Tab5 を中心に構成されており、1280 x 720 の高解像度ディスプレイを備えています。これにより、ワイドスクリーンの 16:9 アスペクト比と優れた色再現が可能となり、高価な商用フレームの実用的な代替となります。
画像処理のために、作者は uLisp 用のカスタム GIF デコード/エンコード拡張を利用しました。ハードウェアはメモリと処理能力に制約があるため、写真は 256 色 GIF として 75% ディフュージョン・ディザリングでフォーマットされます。このプロセスは通常 Photoshop やオンライン変換ツールで行われ、典型的な写真サイズを約 500KB に抑えつつ、オリジナルとほぼ区別できない視覚品質を維持します。
システムアーキテクチャ
フォトフレームはネットワーククライアントとして動作し、リモートウェブサイトから画像リストを取得して順に表示します。このアーキテクチャにより、所有者は写真ギャラリーを遠隔で更新でき、友人や家族にアップロード権限を付与しても、複雑なアプリエコシステムを必要としません。
バックエンド
画像は www.wackyimage.com のシンプルな HTTP コンテンツ管理システム (CMS) で提供されます。サイトは画像タグのリストを含む保護されたページで構成されています。
<body>
<p>Insert photos here! GIF format, image size 1280 x 720.</p>
<p><img src="/pictures/k58/sleepycat.gif" alt="SleepyCat.gif" /></p>
<p><img src="/pictures/k58/wedding.gif" alt="Wedding.gif" /></p>
</body>
Lisp 実装
ファームウェアは uLisp で記述され、接続、収集、表示という直線的な実行フローに従います。
1. ネットワーク接続
デバイスは wifi-connect 関数を使用してローカル Wi‑Fi ネットワークへの接続を初期化し、ハンドシェイクが完了することを確認するために 5 秒の遅延を行います。
(wifi-connect "Mynetwork" "mypassword")
(delay 5000)
2. 画像リストの取得
collect-pics-list 関数は HTTP リクエストを処理します。セキュリティのためにログインクッキーを使用し、ヘルパー関数 skip-headers で HTTP 応答ヘッダーをスキップして HTML 本文に到達します。
(defun collect-pics-list nil
(with-client (s "www.wackyimage.com" 80)
(format s "GET /show?K5A HTTP/1.0~a~%" #\Return)
(format s "Host: www.wackyimage.com~a~%" #\Return)
(format s "Cookie: LOGIN=WI1NDlkNmE4YzBmZmE4MTQyYzNjODk2YzRkZDEwMiIp~a~%" #\Return)
(format s "Connection: close~a~%" #\Return)
(format s "~a~%" #\Return)
(skip-headers s)
(collect-pics s))))
3. パースと表示
collect-pics 関数は HTML を走査し、特定のディレクトリパターン(/k58/)を探してファイル名を抽出します。リストが作成されると、display-pic が個々の GIF ファイルを取得し、そのストリームを decode-gif 関数に渡して M5Stack の画面に描画します。
(defun collect-pics (s)
(let ((pics nil))
(loop for line = (read-line s nil)
while line
do (when (search "/k58/" line)
(push (extract-filename line) pics)))
(nreverse pics)))
メイン実行ループ
全体のプロセスは photo-frame 関数でラップされ、ローテーションロジックを管理します。利用可能な画像数を追跡し、存在しないインデックスにアクセスしようとしないようにし、設定可能な *minutes* 変数で遷移速度を制御します。
(defun photo-frame ()
(wifi-connect "Mynetwork" "mypassword")
(delay 5000)
(let ((nextpic 0) (lastpics 0))
(loop
(let* ((pics (collect-pics-list))
(npics (length pics)))
(when (> npics lastpics)
(setq nextpic lastpics)
(setq lastpics npics))
(display-pic (nth (mod nextpic npics) pics)))
(incf nextpic)
(delay (* 1000 60 *minutes*)))))
デバイスが単体の家電として機能するように、作者は uLisp の resetautorun 機能を (save-image 'photo-frame) で使用し、電源投入時にループを自動的に起動させます。
結論
Lisp の柔軟性と手頃な M5Stack ハードウェアを組み合わせることで、このプロジェクトは侵入的なクラウドサービスに依存せずに機能的で高解像度の家庭用機器を構築できることを示しています。シンプルなマイコンを動的で遠隔更新可能なギャラリーに変換し、個人データの厳格な管理を維持します。