AI・開発、電子工作、クルマ。実際にやってみたことを残すブログ

BOOX Leaf2 を E Ink の卓上時計にした

電子ペーパー端末の BOOX Leaf2 に、自作の時計ページを表示し続けています。時刻・天気・室温・湿度・CO2・電池を白黒 2 値で出し、Home Assistant の値は nginx の中継で読むのでトークンを端末に置きません。E Ink に合わせた画面の決まりと仕組みをまとめます。

机の上に立てた BOOX Leaf2 を斜め前から。画面に 08:20、今日と明日の天気、25.7°C・55%・485ppm。下の 2 か所を 3D プリンターで作った白いスタンドで支えていて、奥にモニターがある
目次
  1. 作ったきっかけ
  2. 立てるためのスタンド
  3. 画面に出しているもの
  4. E Ink に合わせた 3 つの決まり
  5. 白と黒だけで描く
  6. 強調は白黒の反転で
  7. 値が変わったときだけ書き換える
  8. トークンを端末に置かない:nginx の中継
  9. 天気は 1 つのセンサーにまとめる
  10. 最初の版から今の形まで
  11. 使ってみて便利だったところ

BOOX Leaf2(Android 11 の電子ペーパー端末)を、机の上に置いて時計にしています。ブラウザで自作の時計ページを開いたままにして、時刻と日付のほか、今日と明日の天気、室温・湿度・CO2、端末自身の電池の残量を出しています。

値は、手元のサーバーで動かしている Home Assistant(以下 HA)から読みます。ページを配信している nginx が HA への問い合わせを中継してトークンを付けるので、端末にはトークンを置いていません。

この記事では、E Ink の画面に合わせて決めたこと(白黒 2 値、強調は反転、値が変わったときだけ書き換える)と、HA とのつなぎ方、時刻だけの最初の版から今の形になるまでを書きます。

木の机の上に置いた BOOX Leaf2 を正面から少し離れて。画面に 08:22、今日と明日の天気、25.7°C・55%・485ppm。右にモニター
机に置いたところ

作ったきっかけ

今の事務所に移って数か月、大きな時計がなくて地味に不便でした。時計を買おうかとぼんやり考えてはいたものの、絶対に必要というほどでもありません。ちゃんとしたものを買おうとするとそれなりの値段がしますし、「これだ」と思える時計も見つからず、気がつくと後回しになっていました。

そんなとき、E Ink のディスプレイで置き時計を作っている記事を見かけて、これなら自分でも作ってみようと思いました。最初は E Ink のディスプレイを買って自作するつもりでしたが、そこで思い出したのが BOOX Leaf2 です。数年前に買ったものの、iPad mini を買ってからは押し入れにしまったきりで、まったく使っていませんでした。

これを使えば、ディスプレイを別に買わなくても時計が作れる。そう考えて、Leaf2 を時計にすることにしました。

立てるためのスタンド

Leaf2 を机に立てておくスタンドも、3D プリンターで自作しました。Leaf2 の寸法を AI に調べてもらい、それをもとにやりたいことを伝えて、3D モデルも AI に作ってもらいました。印刷してみると、一発で使えるスタンドができました。

黒いデスクマットの上に置いた BOOX Leaf2。横向きの画面に日付、08:19、今日と明日の天気、25.6°C・55%・501ppm が出ている。右に 3D プリンターで作った白いスタンドの部品が 2 つ
BOOX Leaf2 と、3D プリンターで作ったスタンド(右の 2 つ)

画面に出しているもの

画面は横向きで使っています。上から順に、次のものを並べています。

  1. 日付と曜日、右上に電池の残量
  2. 時刻(文字の大きさは画面の幅の 30%)
  3. 今日と明日の天気(アイコンと、最高・最低気温)
  4. 室温・湿度・CO2(数値と横棒)

時刻と日付は、端末の時計から描きます。HA から読むのは天気と室温・湿度・CO2 です。室温・湿度・CO2 は SwitchBot の Meter Pro CO2 の値です。

電池の残量は、ブラウザの Battery Status API(navigator.getBattery())で端末から取ります。この API は HTTPS のページでしか使えないブラウザが多いので、取れない環境では電池の表示を出さないだけにして、時計には影響させないようにしました。Leaf2 のブラウザでは http のページでも取れています。

横棒の範囲は、温度が 15〜35°C、湿度が 20〜80%、CO2 が 400〜1600ppm です。ふだんの値が真ん中あたりに来るように取りました。CO2 の棒の下には、1000ppm の位置に短い目印を付けています。

E Ink に合わせた 3 つの決まり

白と黒だけで描く

背景は純白、文字は純黒にして、中間調(グレー)とアニメーションは使いません。E Ink ではグレーが見分けにくいからです。天気のアイコンも線画だけにして、塗りは白か黒のどちらかにしています。

強調は白黒の反転で

グレーに頼れないので、目立たせたいところは白黒を反転させます。今は次の 2 か所です。

  • CO2 が 1000ppm 以上のときの、CO2 の数値
  • 電池が 20% 以下で、充電していないときの電池の表示
横向きの時計画面で、右下の CO2 1120ppm が黒地に白い文字に反転し、右上の電池は黒地に白い 18% の数字だけになっている(電池のアイコンは黒地に溶けて見えない)。一番下の横棒の下は画面からはみ出している
電池が 20% 以下、CO2 が 1000ppm 以上のときは白黒を反転させる。ダミーの値で再現した画面

電池の表示を反転させると、今のページでは電池のアイコンが黒地に溶けて見えなくなり、白い数字だけが残ります(アイコンの線を黒で描いているため)。

反転しても文字の位置が動かないように、数値のまわりの余白は反転していないときも確保しています。前の版のコードのコメントには、E Ink は位置がずれると残像が出るため、と書いていました。

値が変わったときだけ書き換える

ページの中身(DOM)を書き換えると、E Ink の画面が描き直されます。そこで、表示する文字が前と同じなら何もしないようにしました。

時刻は 1 秒ごとに確かめますが、描き直すのは分が変わったときだけです。分が変わったときに、天気・室温・電池もまとめて取り直します。

トークンを端末に置かない:nginx の中継

時計ページと Home Assistant のつなぎ方。机の上の BOOX Leaf2 のブラウザが、手元の Linux サーバーの nginx に /ha/temperature などを問い合わせ、電池の残量を /ha/battery-report に送る。nginx は問い合わせを HA の API にトークンを付けて中継し、電池の報告は HA の webhook にそのまま中継する。トークンは root だけが読める nginx の設定に置き、端末には渡さない。HA には SwitchBot Meter Pro CO2 の室温・湿度・CO2 と、30 分ごとにまとめた天気予報が入っている
時計ページと HA のつなぎ方。トークンを持っているのは nginx だけ

HA の API を読むには、アクセストークンが要ります。ページにトークンを書き込むと、ページを開ける人なら誰でも読めてしまいます。

そこで、時計ページを配信している nginx に HA への中継を持たせました。

  • ページは /ha/temperature のような、同じサーバーのパスを読むだけ
  • nginx がそれを HA の API(/api/states/<センサー>)に中継し、そのときにトークンを付ける
  • トークンは root だけが読める nginx の設定ファイルに置き、端末には渡さない
  • 中継するのは決めたセンサーだけで、HA の API 全体は外に出さない

毎分の電池の報告も /ha/battery-report で同じ nginx を通し、HA の webhook に渡しています。webhook はトークンが要らないので、ここはトークンを付けずにそのまま中継します。webhook の側は、POST だけ、LAN からだけ受けるように設定しています。

ページと問い合わせ先が同じオリジン(同じサーバーとポート)になるので、ブラウザのクロスオリジンの制約も受けません。電池の報告を HA のポート(8123)に直接送っていたときは、最初の 1 回しか届きませんでした。

こうして HA に届けた電池の残量は、Leaf2 の充電の自動化に使っています。その話は「SwitchBot プラグミニで E Ink 時計の充電を自動化した話」に書いています。

天気は 1 つのセンサーにまとめる

天気は、HA の met の予報を使っています。HA の天気のエンティティは予報を属性に持っていないので、予報を取るには weather.get_forecasts を呼ぶ必要があります。

そこで、HA のテンプレートセンサーで 30 分ごと(と HA の起動時)に予報を取り、今日と明日の天気・最高気温・最低気温を 1 つのセンサーの属性にまとめました。こうすると、nginx の中継も天気の分は 1 本で済みます。

最初の版から今の形まで

時計ページの 3 つの版を、横向きの端末の枠に入れて横に並べた画像。1 は最初の版で、大きな 10:24 と 10月13日 (火) だけ。2 は 2 つ目の形で、時刻と日付の下に気温・湿度・CO2 が 1 行で並び、右上に小さく 62%。3 は今の形で、日付と電池、時刻、今日と明日の天気、室温・湿度・CO2 の横棒
時計ページの移り変わり。ダミーの値で再現した画面

最初から今の形だったわけではありません。

  • 最初の版: nginx を入れて、時刻と日付だけの時計を作りました(画像の 1)
  • その日のうちに: HA を構築し、Meter Pro CO2 を登録して、nginx の中継の設定を作りました
  • 2 つ目の形: 室温・湿度・CO2 を 1 行に並べ、右上に電池の残量を出しました(画像の 2)。この頃には、HA の /local/ にページを置く形も試しています(/local/ は認証なしで配信されるのでトークンを埋め込めず、値は HA がファイルに書き出す形)
  • 今の形: 天気の欄を足し、室温・湿度・CO2 を横棒つきで下にまとめました(画像の 3)。最初の版から 1 週間あまりで、ここまで来ました

2 つ目の形では、端末のフォントによってはセンサーの行が画面の幅に収まらないことがありました。折り返したら少しずつ文字を小さくする処理を入れていましたが、今の版では 3 つを横に並べた枠に分けています。

この記事の画面の画像(写真以外)は、実際のページ(と以前の版)を Playwright で開き、HA への問い合わせの応答をダミーの値に差し替えて撮ったものです。表示している日時・天気・数値は実際のものではありません。

使ってみて便利だったところ

最初は、時刻が分かればいいくらいのつもりで作り始めました。ただ、この仕組みはブラウザで開けるものなら何でも表示できます。CO2 などを測る仕組みはもともとあって、それを画面に出す部分も、今は AI に頼めば簡単に作れます。

そのおかげで、出したいものを気軽に足せました。どうせなら CO2 も出したい。CO2 を出すなら、ついでに温度と湿度も。それでも画面の余白が余っているから、天気も出そう。そんなラフな思いつきを、すぐに形にできたのがすごく便利でした。

最近は手を入れていませんが、今の画面は、たぶん 1 日に何回も、無意識のうちに見ています。作って終わりではなく、生活の一部になっている。本当に実用的なものが作れたのがよかったと思っています。