AI

暇なのでAIと電子工作してみた——音楽に連動する小型ディスプレイができるまで

音楽の再生に連動してジャケットや曲名をリアルタイム表示する小型ディスプレイを、ESP32ボードとiPhoneアプリの自作で実現するまでの記録。市販ガジェットの挫折を経て、BLE通信の最適化や3Dプリンターによるケース設計、AIとのペアプログラミングによる実装の試行錯誤を解説する。

はじめに

きっかけは、デジタル缶バッジが流行っているらしいという話を耳にしたこと。「暇だし、今は AI も手伝ってくれるし、デジタル工作で何か面白いものが作れるかもしれない」——という軽い思いつきからスタートした。

目指したのは、「いま聴いているサカナクションのジャケットを、缶バッジのような超小型ガジェットにリアルタイム表示させる」 こと。ハードウェアの選定からケースの 3D プリント、そして完成に至るまでの試行錯誤をここに記録しておく。

ガジェット.jpg

最終的に完成したもの:

  • ディスプレイ: Seeed Studio XIAO 1.47" IPS Touch Display(ESP32-S3搭載、172×320ドット)
  • 完全自動連携: 曲が変わると、画面ロック中の iPhone が自動検知して BLE でボードへ転送。ボタン操作は一切不要
  • SDキャッシュ: アートワークは microSD に保存し、同一アルバムの2曲目以降は転送をスキップ
  • UI・機能: 加速度センサーで縦横を自動回転。画面の横スワイプで現在地の天気を表示
  • 電源・筐体: USB-C モバイルバッテリー給電。ケースは CadQuery で設計し PLA で 3D プリント

コードはボード側が Arduino、iPhone アプリ側が SwiftUI。どちらも AI(Claude Code)と対話し、ペアプログラミングを重ねながら組み上げた。

構成の全体像

preview.jpeg

曲の変更を感知して描画データを生成するのは iPhone アプリ側の役目だ。ボード側は 5 秒ごとに BLE 経由でトリガー(合図)を送ってバックグラウンドの iPhone を起こし、受信した画像を画面に描画して microSD に保存する。

最初の計画:E87 缶バッジで挫折

当初は、着想の元となった市販の BLE 搭載デジタル缶バッジ「E87」をそのままハックして表示させるつもりだった。有志がリバースエンジニアリングした OSS を頼りに Swift へ通信手順を移植したが、実機検証で致命的な仕様の壁にぶつかった。

  • 自動更新ができない: 本体の物理ボタンを押して「BTモード」に入れないと接続できず、画像を1枚送ると自動でモードを抜けてしまう。曲が変わるたびに手動でボタンを押す必要があり、「自動で切り替わる」という前提が崩れた。
  • ファイル管理の制約: 同一ファイル名での上書きが拒否され、個別削除もできない(満杯になったら全初期化するしかない)。
  • 心もとないバッテリー: 内蔵電池の実稼働時間は 2〜3 時間が限界。

「ボタンを押して 5 秒待つ缶バッジ」は、やりたかったことと別物だ。ここで E87 は諦めることにした。

XIAO 1.47" IPS Touch Display への方針転換

IMG_0948.jpeg

缶バッジの代わりになるものを AliExpress でいろいろ探して見つけたのが、Seeed Studio XIAO 1.47" IPS Touch Display だった。実物は想像以上に小さかったが、172×320 の高精細 IPS、BLE 5.0、microSD スロット、6軸加速度センサー、静電容量式タッチパネルがワンボードに集約されており、実に遊び甲斐のある器だった。ファームウェアを自前で組めるため、当初考えた「曲変更をトリガーにした完全自動表示」も実装できる。

AI に相談した当初は外部サーバーや自宅 LAN 経由の案ばかり返ってきたが、持ち歩きを最優先して「USB-C 給電、端末内 microSD キャッシュ、iPhone からの BLE ダイレクト通信」というローカル完結の構成へと組み直した。

最大の関門:画面ロック中の iPhone をどう起こすか

iOS のアプリは、画面をロックするとほどなく止められる。止まっている間は曲の変化に気づけない。
そこで採用したのが、「ボード側から能動的に iPhone を起こしてもらう」 アプローチだ

  1. iPhone アプリ側でバックグラウンドでの Bluetooth 通信継続を宣言(Info.plist に bluetooth-central を指定)
  2. ボード側から 5 秒間隔で BLE の Notify(起床トリガー)を送信
  3. Notify で iOS アプリが数秒間だけウェイクした隙にメタデータを取得し、変更があれば画像転送を走らせる。

本当に動くのか、まず試験用の小さなアプリとファームウェアだけで確かめた。画面ロックのまま約 29 分、合図は 5.0 秒間隔で 345 回、取りこぼしなし。曲の変化 7 回も全部拾えた。
ダメだったときは iOS の AMS(Apple Media Service)で曲名だけ受け取る案に逃げるつもりだったが、AMS ではジャケットが来ない。通ってくれて助かった。

ジャケットと曲名転送の最適化

日本語フォントは iPhone 側でラスタライズして送る

ボード側に日本語フォントを載せると、収録する文字の範囲、折り返し、省略記号まで全部ボード側で面倒を見ることになる。そこで曲名・アーティスト・アルバム名は iPhone が 172×148 ドットの画像(4bit グレースケール・約 12KB)としてラスタライズしてBLE転送する設計にした。ボード側は受け取ったビットマップを貼るだけで済む。

「無いときだけくれ」とボード側から要求するキャッシュ戦略

曲が変わると、まず iPhone が「曲名画像」と「アルバム識別子」を送る。ボード側は microSD 内を探し、該当アルバムの JPEG(172×172)が無ければ「画像をくれ」と iPhone へリクエストして転送してもらう。

iPhone 側で送信履歴を記憶させると microSD を差し替えた瞬間に不整合が起きるため、「ファイルがあるかは保存先自身に判断させる」のが一番シンプルで堅牢だ。転送完了時には CRC32 で検証し、一時ファイルからリネーム保存することで破損ファイルが残る事故も防いでいる。

ハマりどころ:ストリーミング楽曲の曲変更を検知できない問題

当初は楽曲の特定に MPMediaItem.persistentID を使っていたが、Apple Music 等でライブラリ未追加のストリーミング楽曲を再生すると ID が 0 のまま返ってくる罠に直面した(albumPersistentID も同様)。結果、曲が変わっても同一曲と誤判定されてしまう。

対策として、楽曲識別には複数の ID と曲名・アーティスト名・アルバム名の組み合わせで、アルバム識別には「アルバムアーティスト名+アルバム名」のハッシュ値を採用した。これでローカルとストリーミングが混在していても、画面ロック中に完全追従できる。

ケース作り(3D プリント)

IMG_0997.jpg

ボードむき出しで下げるわけにもいかないので、ケースを作った。CAD で図面を描く代わりに、CadQuery という Python のライブラリで形をコードとして書き、FDM方式の3D プリンター(PLA)で出力した。

……と書くと簡単そうだが、ここが一番手こずった。

窓の縁が割れる、閉まらない、閉めてもすぐ開く……で AIと対話しながら、数回出力し直し、すぐに外れる不具合は接着することで決着させた(笑)。
仕上げに、積層の筋をならすためサーフェイサーと塗料を 3 度塗りして積層痕を見えなくした。

Play

追加実装:縦横の自動切り替えと天気表示

ケースが形になったところで、ハードウェアのポテンシャルを使い切るべく機能を追加した。

加速度センサーによる縦横 4 方向の自動回転

ボードには加速度センサー(LSM6DS3TR-C)が載っている。これで重力の向きを読み、縦・横とそれぞれの 180° 回転の 4 通りを切り替える。約 1 秒、同じ向きに 40 度くらい以上傾いていたら切り替え、平らに置いているときは今の向きのまま。

  • 即時描画: 曲名画像は「縦用」「横用」の 2 枚を事前転送し、ジャケットとともにボードの PSRAM に常駐させることで、回転時は再通信なしで即座に再描画する。
  • 誤爆防止と軸補正: 歩行時の揺れ対策としてアプリ側に「縦向き固定」スイッチを用意。また、AIが探したデータシートだけではチップの実装向きが確定せず初期ファームでは上下が反転したため、実機のなま値をダンプして軸の符号を補正した。

タッチスワイプによる現在地天気表示

画面を右から左になぞると現在地の天気、左から右で曲に戻る。軽く叩いても切り替わり、10 秒で自動的に曲へ戻る。なぞる向きはそのときの画面の向きに合わせて読み替える。

天気データは iPhone アプリが WeatherKit API から取得する。バッテリー節約のため位置情報は Significant-Change Location Service(大幅変更位置情報サービス)を使い、30 分ごとの起床タイミングか 5km 以上の移動検知時にバックグラウンドで更新する。天候画面も iPhone 側で SF Symbols を使って JPEG レンダリングして送る方式に割り切ったため、ボード側の実装は極限までシンプルに保てた。なお、この画面サイズでは 13pt 未満のフォントは潰れて読めないという知見もここで得た。

この辺の実装はほぼAIが自動でやりきった。私は吐き出すコードをiPhoneに導入して動作の確認をするだけだ。これでほぼ完成。

Play

実測パフォーマンスと振り返り

  • 消費電力: ボード単体で約 0.42W。8,000mAh のモバイルバッテリーを繋ぎ、常時点灯で 3 時間の外出テストを余裕でクリア。
  • 通信レイテンシ(画面ロック時): 曲変更から曲名表示まで 0.4〜1.5 秒、アートワークの新規転送・デコードも 0.6〜0.9 秒 程度で完了し、体感の追従性は極めて良好。

AIとの対話で 最初から実装を行う事はせずに、試験コードを作っていろいろ試したこと、「画面ロック中の iPhone を BLE で起こし続けられるか」という最大の急所を真っ先に検証できたこと が比較的スムーズに実装できた勝因だろう。E87 での失敗から得た教訓がここで活きた。また、重い処理はすべて iPhone に集約し、ESP32 側は「描画・キャッシュ専用端末」に徹する非対称な分離にしたことで、UI の微調整や機能追加をアプリ側の修正だけで素早く実現することができた。

コードは Claude Code がすべて生成した。人間の役割は「実機で試し、物理的な振る舞いや数値をフィードバックすること」そしてAIと対話しながら、次の改善の道筋を立てていくことだった。なによりClaudeCodeがなければ短期間にここまでできることはなかったろう。いい時代だ。

参考資料・使用環境