SwitchBot プラグミニで E Ink 時計の充電を自動化した話
SwitchBot プラグミニと Home Assistant で、卓上の E Ink 時計(BOOX Leaf2)の電池を 20〜80% に保つように自動で充電しています。使っている機器と仕組み、残量の取り方を切り替えるまでのつまずきをまとめます。
目次
机の上で時計にしている BOOX Leaf2(電子ペーパーの端末)の電池を、20〜80% の間に保つ充電の自動化を作りました。SwitchBot のプラグミニで USB 充電器への給電を入り切りして、Home Assistant(以下 HA)が残量を見て、20% を割ったら給電を始め、80% を超えたら止めます。つなぎっぱなしで満充電のままにしたり、空になるまで使ったりせず、電池を長持ちさせるためです。
今は、時計ページ(ブラウザ)から毎分 HA に届く残量を使っていて、充電は約 2 日おきに規則正しく続いています。最初は HA のコンパニオンアプリ(Android 用のアプリ)が報告する残量を使っていましたが、うまくいかず、今の形に切り替えました。
この記事では、使っている機器と仕組み、切り替えるまでのつまずきを書きます。時計そのものの話は別の記事に書いています。

自動にしたきっかけ
時計にしてから少しの間は、手で充電していました。100% になったらしばらくケーブルを抜き、減ってきたらまた挿す。夜寝る前のように長く放っておくときは、ケーブルを抜いておく。これを続けたのは短い間で、すぐに自動にする必要を感じて作り始めました。
使っている機器
- BOOX Leaf2: Android 11 の電子ペーパーの端末。机の上で時計にしている
- SwitchBot プラグミニ(JP): USB 充電器への給電を入り切りする。消費電力も測れる
- Home Assistant: 手元の Linux の PC で、Docker で動かしている

今の仕組み
充電を制御するオートメーションは 3 つです。
- 給電を始める: 残量が 20% 未満、プラグが off、最後の報告が 10 分以内、打ち切りから 2 時間たっている
- 給電を止める: 80% を超えた、アプリが満充電を知らせた、給電を始めて 15 分以降に報告が 10 分途絶えた、給電から 4 時間たった、のどれか
- 打ち切る: プラグの消費電力が 1W 未満のまま 10 分続いた
給電を始める条件は、残量の報告が来るたびに評価し直します。しきい値をまたいだ瞬間だけを見る書き方(numeric_state のトリガー)だと、報告が飛び飛びのときに取りこぼすからです。ほかに 15 分ごとと HA の起動時にも評価します。最後の報告が古いときに始めないのは、古い値で始めると、実際は 90% なのに充電する、といった事故になるからです。
打ち切りは、HA が持つ残量に頼らない、プラグの電力という物理的な証拠だけで止める経路です。充電中の Leaf2 は 5〜10W、何もつながっていない USB 充電器は 1W 未満なので、ケーブルが抜けていたり、満充電で電流が落ちたりしたら、ここで止まります。

プラグミニは 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 回止まった

アプリが止まると、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 のアプリを開くしかなく、遠くから起こす方法はありません。
使ってみて
自動にしてからは、充電のことを気にしなくなりました。時計の電池が切れたことも一度もありません。



