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

SwitchBot プラグミニで E Ink 時計の充電を自動化した話

SwitchBot プラグミニと Home Assistant で、卓上の E Ink 時計(BOOX Leaf2)の電池を 20〜80% に保つように自動で充電しています。使っている機器と仕組み、残量の取り方を切り替えるまでのつまずきをまとめます。

木の机の上に置いた BOOX Leaf2 を正面から少し離れて。画面に 08:22、今日と明日の天気、25.7°C・55%・485ppm。右にモニター
机の上で時計にしている BOOX Leaf2。右上に電池の残量(76%)が出ている
目次
  1. 自動にしたきっかけ
  2. 使っている機器
  3. 今の仕組み
  4. 最初はコンパニオンアプリの残量を使った
  5. 消費の見積もりを間違えた
  6. アプリを信じて 2 回止まった
  7. ブラウザ経由に替えた
  8. 「スリープで報告が止まる」は誤診だった
  9. 数字で比べて決めた
  10. 自動化でつまずいたところ
  11. 同じ値を書いても last_reported は進まない
  12. ON にした 75 ミリ秒後に OFF になった
  13. 再起動でクールダウンがずれた
  14. 5 分おきでは 80% で止められない
  15. 時間から逆算するのはやめた
  16. 今の様子
  17. 使ってみて

机の上で時計にしている BOOX Leaf2(電子ペーパーの端末)の電池を、20〜80% の間に保つ充電の自動化を作りました。SwitchBot のプラグミニで USB 充電器への給電を入り切りして、Home Assistant(以下 HA)が残量を見て、20% を割ったら給電を始め、80% を超えたら止めます。つなぎっぱなしで満充電のままにしたり、空になるまで使ったりせず、電池を長持ちさせるためです。

今は、時計ページ(ブラウザ)から毎分 HA に届く残量を使っていて、充電は約 2 日おきに規則正しく続いています。最初は HA のコンパニオンアプリ(Android 用のアプリ)が報告する残量を使っていましたが、うまくいかず、今の形に切り替えました。

この記事では、使っている機器と仕組み、切り替えるまでのつまずきを書きます。時計そのものの話は別の記事に書いています。

直近 11 日間のグラフ。上は電池の残量で、20% から 80% までを、約 2 日かけて下がり 1 時間ほどで上がるのこぎりの歯の形を 5 回くり返す。下はプラグの消費電力で、残量が上がる 5 回の時だけ 7〜8W 台の山があり、それ以外は 0W
直近 11 日間の残量(ブラウザ経由)とプラグの電力。Home Assistant の記録から作成

自動にしたきっかけ

時計にしてから少しの間は、手で充電していました。100% になったらしばらくケーブルを抜き、減ってきたらまた挿す。夜寝る前のように長く放っておくときは、ケーブルを抜いておく。これを続けたのは短い間で、すぐに自動にする必要を感じて作り始めました。

使っている機器

  • BOOX Leaf2: Android 11 の電子ペーパーの端末。机の上で時計にしている
  • SwitchBot プラグミニ(JP): USB 充電器への給電を入り切りする。消費電力も測れる
  • Home Assistant: 手元の Linux の PC で、Docker で動かしている
黒いデスクマットの上に並べた BOOX Leaf2 と SwitchBot プラグミニ。Leaf2 は縦向きで、画面に 09:32、今日と明日の天気、26.0°C・55%・472ppm、右上に電池の残量 75%。右の白いプラグミニは差し込み口の刃が見える向き
BOOX Leaf2 と SwitchBot プラグミニ

今の仕組み

残量の 2 つの経路と充電の制御。BOOX Leaf2 の時計ページ(ブラウザ)が毎分、nginx の /ha/battery-report に残量を POST し、nginx が HA の webhook に中継する。HA は残量と受けた時刻を記録し、それをオートメーションが制御に使う。コンパニオンアプリの残量は点線の経路で、突き合わせと満充電の検出だけに使う。オートメーションは 20% 未満で給電を始め、80% 超・満充電・報告の途絶・4 時間で止め、電力 1W 未満が 10 分続いたら打ち切る。SwitchBot プラグミニを BLE で入り切りし、プラグは消費電力を HA に返し、USB で Leaf2 に給電する
残量の 2 つの経路と充電の制御。実線が今の制御に使う経路、点線がコンパニオンアプリの経路

充電を制御するオートメーションは 3 つです。

  • 給電を始める: 残量が 20% 未満、プラグが off、最後の報告が 10 分以内、打ち切りから 2 時間たっている
  • 給電を止める: 80% を超えた、アプリが満充電を知らせた、給電を始めて 15 分以降に報告が 10 分途絶えた、給電から 4 時間たった、のどれか
  • 打ち切る: プラグの消費電力が 1W 未満のまま 10 分続いた

給電を始める条件は、残量の報告が来るたびに評価し直します。しきい値をまたいだ瞬間だけを見る書き方(numeric_state のトリガー)だと、報告が飛び飛びのときに取りこぼすからです。ほかに 15 分ごとと HA の起動時にも評価します。最後の報告が古いときに始めないのは、古い値で始めると、実際は 90% なのに充電する、といった事故になるからです。

打ち切りは、HA が持つ残量に頼らない、プラグの電力という物理的な証拠だけで止める経路です。充電中の Leaf2 は 5〜10W、何もつながっていない USB 充電器は 1W 未満なので、ケーブルが抜けていたり、満充電で電流が落ちたりしたら、ここで止まります。

黒いデスクマットの上に置いた白い SwitchBot プラグミニを斜め上から。側面にコンセントの差し込み口、正面に丸いボタン
SwitchBot プラグミニ。ここに USB 充電器を挿している

プラグミニは BLE(Bluetooth Low Energy)で HA から操作しています。

最初はコンパニオンアプリの残量を使った

HA の設定と調査は、Claude Code と一緒に進めました。Leaf2 のコンパニオンアプリとプラグミニを HA に登録し、まずはアプリが報告する残量で制御を組みました。実測して分かったアプリの癖は次のとおりです。

  • 値が変わったセンサーしか送らない。 しかも電池の値が変わっても自分からは報告しないので、HA から「センサーを読み直して」と通知(command_update_sensors)で小突く必要があります。アプリが生きていれば、応答は 3〜16 秒でした
  • Boox は、ほかのアプリを開くと HA のアプリを止める。 同じ給電断のイベントで、ある時は 3 秒で報告が来て、6 分後は報告がゼロでした
  • 止まったアプリは、電源をつないでも起きない。 アプリが止まった状態で 8 分間給電しても、報告はゼロでした。HA からのプッシュでも起きません。「充電中だけアプリを生かす」という作りはできません

止められないようにする手段として見つけたのは、アプリの設定の「永続的な接続」を「常に」にすることでした。アプリが常駐のサービス(フォアグラウンドサービス)として動くようになります(そのかわり常駐の通知は消せません)。表記と場所が分かりにくいので書いておきます。

  • 表記は「常時接続」ではなく「永続的な接続」
  • 「通知」の下ではなく、設定 → コンパニオンアプリ → サーバー名の行をタップ した中にある
  • 選択肢は「なし / ホームネットワークのみ / 画面表示中 / 常に」で、「充電中」は無い

ほかの対策として、端末の設定でスリープ後も Wi-Fi をつないだままにすることと、アプリ一覧で HA のアイコンを長押しして出る「最適化」でバックグラウンドでの実行を許可することも、メモに残しています。

消費の見積もりを間違えた

「常に」にすると電池の減りが 0.35%/時 から 1.26%/時(3.6 倍)に増える、として、一度はこの設定を却下しています。

これは比べる相手を間違えていました。0.35%/時は、時計を表示していない夜の値です。時計を出したままなら、永続的な接続なしでも 1.11%/時かかります。「常に」で増えるのは +0.15%/時だけで、電池を使っていたのは時計の表示の方でした。

アプリを信じて 2 回止まった

使い始めてから 8 週間のグラフ。上はアプリが報告した電池の残量で、1 回目は 66% のまま約 37 時間平らになり、2 回目は 51% で平らになったまま最後まで続く。下はプラグの消費電力で、制御をブラウザ経由に切り替えたあとも約 2 日おきに 8W 前後の山が規則正しく並ぶ
アプリ経由の残量と、プラグの電力(1 時間ごと)。アプリが止まると、値が止まった時点のまま変わらなくなる。Home Assistant の長期統計から作成

アプリが止まると、HA が持つ残量は止まった瞬間の値のまま動かなくなります。困るのは、値が古いことよりも、20% を割ったことに気づけない ことです。給電を始める条件が成り立たないので、充電そのものが始まりません。

  • 1 回目: 37 時間止まり、HA は 66% のまま、実際は 45% まで減っていた
  • 2 回目: 30 時間止まり、HA は 51% のまま、実際は 15% まで減っていた。20% を割っても給電は始まらなかった

止まったときに「永続的な接続」が「常に」になっていたかどうかは、記録が無く分かりません。

上のグラフは、HA の長期統計に残っていたアプリ経由の残量です。1 回目は 66% で、2 回目からは 51% のまま平らになっています。下のプラグの電力を見ると、2 回目のあとも充電は約 2 日おきに続いています。2 回目に気づいた日に、制御に使う値をブラウザ経由に切り替えたからです。

ブラウザ経由に替えた

「スリープで報告が止まる」は誤診だった

実は、時計ページ(ブラウザ)からも毎分、残量を HA に送らせていました。ただ当時は「端末がスリープすると、ページのプログラム(JavaScript)がタイマーごと止まって報告が届かなくなる。E Ink は最後の画面を表示したままなので、見ても止まったことに気づけない」と判断して、参考の値にとどめていました。

これは誤診でした。止まっていた本当の原因は、ページから HA のポート(8123)へ直接 POST していたことです。時計ページとは別のオリジンになるので、ページを開いた直後の 1 回しか届いていませんでした(プリフライトを避けるために no-cors とフォーム形式にしても同じでした)。

8123 への直接の POST が最初の 1 回しか届かないことは、半日かけて確かめました。原因が分かるまでに、WakeLock、自動スリープの設定、ブラウザの最適化、Chrome への変更と、省電力の線を疑って無駄に試しています。答えは、自分が言っていた「時計は正常に更新されている」の方にありました。時計の表示に使う値は nginx の /ha/* を通していて、同じオリジンだったので影響を受けていなかったのです。

報告も nginx の /ha/battery-report に相乗りさせて同じオリジンにしたら、24 分間、一度も止まりませんでした。ところが、この観測が 24 分しかなかったので、「ブラウザ経由は信頼できない」という評価だけが古いまま残ってしまいました。

数字で比べて決めた

評価を見直したのは、2 回目に止まったときです。その直前の 5.7 日間で、2 つの経路を比べました(ブラウザ経由は nginx のアクセスログで数えています)。

経路 結果
ブラウザ経由 8262 件。5 分を超える停止は 0 件
コンパニオンアプリ経由 29.5 時間(21.4%)止まっていた

これで、制御に使う値をブラウザ経由に切り替えました。アプリの値は 30 分ごとに取って突き合わせに使い、アプリの値が 1 時間以内に届いているときだけ、2 つの差が 10% を超えたら通知します。

ブラウザ経由にも弱点はあります。時計ページを閉じたら止まることです。ただ、この端末は時計を出すためのものですし、ほかのアプリを開くという同じ操作でアプリ経由も止まります。片方を選ぶなら、ブラウザ経由の方が有利だと判断しました。

一度「信頼できない」と決めたものでも、原因を直したら見直す。今回の教訓です。

自動化でつまずいたところ

同じ値を書いても last_reported は進まない

ブラウザ経由の残量は、webhook で受けて input_number(数値の入れ物)に書いています。この input_number の時刻で報告の鮮度を見られそうですが、同じ値では進みません。input_number.set_value に同じ値を渡しても、last_reported は進みませんでした。残量は 1 時間に 1% ほどしか動かないので、この時刻は「最後に値が変わった時刻」でしかなく、経路が生きているかの判定には使えません。

そこで、webhook を受けるたびに、別の input_datetime に今の時刻を書くようにしました。鮮度はこちらで見ます。この時刻は毎分書き換わるので、HA の記録(recorder)からは外しています。アプリ経由の last_reported も同じで、「アプリが生きている」ではなく「値が変わった」という意味でしかありません。

ON にした 75 ミリ秒後に OFF になった

「報告が途絶えていたら給電を止める」というフェイルセーフが、給電を始めた 75 ミリ秒後に給電を止めてしまいました(組み始めたその日です)。給電を始めた直後は、最後の報告が古いのが普通だからです。「給電を始めてから 15 分たっている」という条件を足して直しました。

再起動でクールダウンがずれた

打ち切った直後に給電を始めて ON / OFF を繰り返さないように、打ち切りから 2 時間は給電を始めないようにしています。最初はプラグのスイッチの last_changed を見ていましたが、これは HA の再起動でリセットされます。試験では、再起動のせいで給電の開始が 1 時間遅れました。

今は、打ち切りのオートメーションが最後に動いた時刻(last_triggered)を見ています。こちらは再起動をまたいで残ります。

5 分おきでは 80% で止められない

アプリの値で制御していたころは、HA からアプリを小突く間隔が、そのまま残量の鮮度になっていました。充電中は 1 分に約 1% 増えるので、5 分おきだと 5% きざみでしか分かりません。78% で止めるつもりが 82% で止まったことがあり、充電中だけ毎分小突くようにしていました。ブラウザ経由は毎分届くので、今は行き過ぎても 1〜2% です。

時間から逆算するのはやめた

途中では、「充電中はアプリが止まって報告が来ない」ことへの対策として、給電を始めたときの残量と充電の速さから、80% に届く時間を逆算して止める方式も入れました。これは後に廃止しています。今は、残量が見えなくなったら推測で流し続けず、素直に止めます。

今の様子

直近 5 回の充電は、どれも 19% で始まって 81〜82% で止まりました。かかった時間は 60〜62 分、プラグの電力は最大 7.2〜8.5W、充電の間隔は約 50〜52 時間です(冒頭のグラフ)。作り始めてから 8 週間足らずで充電は 28 回、ブラウザ経由に替えてからは約 2.1 日おきに規則正しく続いています。

実測した値もまとめておきます。

  • 充電の速さ: 75% までは 1.05%/分、75% からは 0.82%/分。プラグの電力も同じところで 7.0W から 4.9W に下がる
  • 電池の減り: 時計を表示したままで約 1.1%/時。80% から 20% まで約 54 時間

nginx のアクセスログを見ると、直近の 2 週間で残量の報告は 20,232 回届いていて、すべて成功、間が 5 分を超えたことは一度もありませんでした。コンパニオンアプリの方は、2 回目に止まってから止まったままです(HA の値は 51% のまま)。復旧するには端末で HA のアプリを開くしかなく、遠くから起こす方法はありません。

使ってみて

自動にしてからは、充電のことを気にしなくなりました。時計の電池が切れたことも一度もありません。