電子ペーパー温度湿度CO2計を作った話
作ったもの
電子ペーパーディスプレイに現在の日付・時刻・室温・湿度・CO2 濃度が表示される。1 分に 1 回更新され、1 時間に 1 回 Wi-Fi 経由で ntp.nict.jp と同期して時刻を合わせる。ついでにセンサーデータのログをクラウドに送信する。

有孔ボードマウントケースは自作。上にポコっと出ているのがセンサー。

動機
断捨離してたら温度湿度計が出てきたので電池入れて使えるようにしてみたものの、なんか妙に気温が高かったり湿度が低かったりして壊れてる感じがした。たまたま良さそうなサイズの電子ペーパーディスプレイを見かけたので、じゃあ作ってみるかーとなった。
材料
- 電子ペーパーディスプレイ CrowPanel ESP32 5.79inch E-paper $31.90
- 温度湿度CO2センサー SCD41 ¥2,394
- リチウムイオン電池用モニター MAX17048 ¥1,226
- リチウムイオン電池 1S 1500mAh 3.7V ¥999
配線

SCD41 と MAX17048 はどっちも I2C なので同じとこに配線してもよかったんだけど、いろいろデバッグしてる過程で別々になってしまってそのまま。4054A の CHRG 端子は細かすぎて写真に写ってない。
開発環境
- macOS 26.1
- Cursor 2 (Composer 1, Gemini 3 Pro, Opus 4.5)
- arduino-cli
Arduino 開発における Agentic Coding
このプロジェクトにおけるソースコードは全部 Cursor によって生成されている。初期は Composer 1、途中で Gemini 3 Pro、最後のほうは Opus 4.5 を使った。人力で書いたとこはほぼない。ある程度レビューはしたけど見てないコードもたくさんある。ビットマップフォントを生成する Python スクリプトとかも全部生成。まあ Arduino は基本 C/C++ でオープンなので知識量もそこそこあり、この程度のプロジェクトでは全く問題なさそう。
ChatGPT 5.1 Pro
実装計画を立てる前に SCD41 や MAX17048 の仕様や挙動を ChatGPT に聞いて確認した。CrowPanel の回路図ファイル(XML)も読んでくれるので「どこにどうつなぐべきか」とか「回路のここがおかしい」みたいなことも提案してくれて非常に助かった。
arduino-cli
arduino-cli は Arduino IDE のコマンドラインツール。Arduino IDE を使わずにコマンドラインから Arduino プロジェクトを管理できる。
CLI 版があるおかげでコンパイルからファームウェアのアップロードまで Cursor に任せることが可能。AGENTS.md に適切なコマンドを書いておけば、コードを更新するたびに自動でコンパイルからアップロードまでやってくれる。
シリアルモニターも Cursor 自身に開かせて読ませることは可能なんだけど、ログ全部を流し込むとコンテキストを圧迫するってのもあって、今回は手動で別ターミナルで開いて必要なとこだけコピペするようにした。
次なんか作るときは、コンパイル→アップロード→シリアルモニター起動をいい感じに管理できる MCP サーバーを作りたいかも。(シリアルモニター開いたの忘れてアップロードしようとするとエラーになるのがちょっと面倒)
電子ペーパー制御
チュートリアルがちゃんと用意されているので、単純に文字や画像を表示するのは簡単。制御ライブラリはサンプルからコピーして持ってくる必要がある。
部分更新
よくある電子ペーパーの書き換えシーンでは全面が白黒チカチカしてるのを見かけるけど、毎分の更新でそれやってると気になって仕方がない。なんか方法ないかなーと探したらちゃんとあった。ただし制御にクセがあって、手順を守らないとどんどん画面が壊れていく(どこにも記載がないので挙動見ながら勘で実装してるけど、ちゃんと動いてるのでたぶん正解)。
EPD_Display で真っ白データを用意した後 EPD_PartUpdate で事前準備する。
その後、必要な描画をしてから再度 EPD_PartUpdate を呼ぶと、ピクセル変化があった部分だけが更新される。白黒チカチカリフレッシュはされないので割と普通のディスプレイっぽくなるが、ほんの少しだけ残像が残る(一番上の画像がその状態)。
フォント描画
文字だろうが画像だろうが最終的に 1 ピクセル 1bit のバイナリデータに変換しないといけない。当然ながら一般的な TrueType/OpenType フォントをそのまま使えないので、事前にビットマップフォントにしておく必要がある。

必要な文字だけをビットマップ画像としてレンダリングし、それを C のヘッダーファイルとして出力する Python スクリプトを生成して最終的なプログラムに組み込んだ。
プロポーショナルフォント
最初のデザインではレンダリングがめんどいので等幅フォントとして描画してたんだけど、やっぱ文字間が変なので元のフォントファイルからメトリクスを抽出してプロポーショナルフォントとして描画するようにした。手動コーディングではめんどすぎてやる気にならないけど、Cursor に任せて数分待てばできるのはマジありがたい。「めんどいけどやったほうがいい」系のタスクはどんどん AI に任せるのが吉。
センサー読み取り
温度湿度 CO2 センサーの SCD41 は I2C でデータを読み取る。メーカーの Sensirion がライブラリ提供してくれてるのでそれ使えば簡単(Cursor がやってくれたので詳細不明)。
省電力制御
電子ペーパーにした理由のひとつは壁掛けにしたいというのがあった。電子ペーパーは内容を更新するときだけ電力を消費するのでバッテリー駆動でもそれなりに長時間動くが、制御用の ESP32 もちゃんと Deep Sleep で省電力にしないと無駄に電力を使ってしまう。
Deep Sleep する時間は wake-up してから処理が終わるまでの時間を計測して、それを次に起きる時間から引いたものを Deep Sleep する時間とすることで、毎分処理が実行されるようにした(だいたい処理が 8 秒ぐらいで終わるので 52 秒寝る、みたいな感じ)。
そして Deep Sleep すると全てのメモリがリセットされてしまうので、画面用のフレームバッファーは SD カードに保存して起きたらそこから再読み込みすることで以前の状態を復帰させている。
SCD41 のセンサーデータ読み取りには 5 秒の待機が必要だが、その間も Light Sleep で待機することで省電力にした。
SCD41 センサー自体にも各種省電力機能があって当初はそのモード (Power-down モード) にしてたんだけど、1 分おきの読み取りだと逆効果であることがわかって Single-shot モードを使い、センサーを idle 状態に保つようにした。
(けど、まだ電池をフル充電から何日持つかテストしてないので、実際にどこまで効果があるかはわからない。)
バッテリー残量監視
一番最初に実装したのは、いわゆる分圧したやつを GPIO に入れて ADC で読み取る方式。まあある程度の傾向はわかるんだけど精度がイマイチすぎるのを ChatGPT に相談したら「fuel gauge IC ってのがあるよ」とのこと。Adafruit の MAX17048 ボードを使うようにしたら、かなり正確かつ簡単に電圧と残量%が読み取れるようになった。
ハマりポイントがひとつあって、バッテリーは必ずコネクタ経由もしくは BAT 端子(not VIN)に接続しないといけない。そうしないと MAX17048 の電源が入らなくて I2C でデバイスが見つからない(Adafruit のサイトに書いてあるんだけどね)。
バッテリー充電機能
CrowPanel のページには一切記述がないんだけど、このデバイスには 4054A という LiPo 充電用の IC が内蔵されている。バッテリーつないで USB-C を接続すると充電される。
さらにこの IC には CHRG 端子があって、これを読み取れば充電中かどうかがわかる。ただし実際にはどこにも繋がってないので、根性で極小ピンから GPIO に引っ張ってくる必要がある(やった。配線写真参照)。
画面デザイン
ずっと Photoshop 使ってたんだけど Affinity が話題になってたので試しに使ってみた。しかし Photoshop に慣れすぎてるのもあってやりづらい…。これぐらいのデザイン作業なら機能的には問題なさそうだけどやりづらい…。

ということでとりあえずアタリのデザインだけ作業してあとは実装側で調整することにした。
クラウドダッシュボード

センサーデータ取るならログ取りたいしグラフで見たいよねーと思ってその系のサービス探してみたんだけど、どれもパッとしないし金かかるしで。これぐらいなら自分で作ってしまおうということで作った。といっても Cursor(Opus 4.5)に「こんな感じで〜」って言ったら一発目でほぼこれができて、あとは微調整って感じだったけど。
Cloudflare の D1 にデータを蓄積してる。フロントは Astro で作られてて Pages にデプロイされている。Cloudflare の Zero Trust 使って GitHub アカウントでアクセス制限している。Zero Trust は柔軟なアクセス制限がわりと簡単にできるので、Cloudflare のアプリ保護にはとてもよい。アクセストークンを使った認証も同時にできるので、ESP32 側からはそのトークンでセンサーデータを送信している。
全ソースコード
学んだこと
- Arduino プロジェクトでも Vibe Coding は全然できる
- 電子ペーパーの制御はわりとクセがある
- fuel gauge IC 便利
- ChatGPT は回路図が読める
- Zero Trust 便利
Discussion