背景

最近 Claude Code をよく使うようになって、「これがあれば何でもできるな」という感触が出てきた。 ずっと放置していた自宅のProxmoxサーバーを久しぶりに動かしてみたら普通にいろいろできて、Narou.rbのLinux移行もついでにやったし、今はClaude CodeもProxmoxのコンテナで動かして外出先からちょくちょく遊んだりしてる。

どうせProxmox動かしてるならできるのではと思ってはじめたら数時間でできてしまったのでメモだけ残しておく。

そもそもとして、我が家のPCは9950X3D/9070XT/3060と電力バカ喰いマシンで、かなり発熱する。そこで自宅の吸気ダクトにファンを取り付け、強制的にPCの排気をそのダクトから排出する仕組みを構築している。

これで夏場に熱い排熱を部屋に垂れ流さないのでかなり違うのだが、毎回PCに合わせてファンをオン・オフするのが面倒だった。

PC連動タップみたいなものもあるが、如何せんお値段がそこそこするので躊躇っていた。

SwitchBotのスマートプラグでも似たようなオートメーションができた気がするのでそちらの選択肢もあったが、SwitchBotのプラグは気づいたら8台壊れていたので今回は別の手段を取ることにした。

それが、今回使っているTapo P110M。こいつはTP-Linkのスマートプラグで、電力記録機能はあるが、電力をトリガーとした他デバイスの操作は基本的にできないものの、Matter対応品のため、自宅にサーバーを立てる事ができれば電力トリガーで自由に動かすことができる。

しかもセールなら2つセットで2000円くらいで買える上に汎用性もずっと高い。

構成

Tapo P110M はローカルAPIを持っているので、クラウド・外部サービス不要で同一ネットワーク内から直接制御できる。

Proxmox LXC コンテナ
  └── Python スクリプト (systemd サービス)
        ├── P110M (PC側)    ← 消費電力を10秒ごとに取得
        └── P110M (ファン側) ← 25W超でON、20W未満でOFF

Proxmoxを使ったのは自宅に常時稼働のサーバーがあったから、というだけで、RaspberryPiや他のLinuxマシンでも同じことができる。

事前準備

Tapoアプリでサードパーティ連携を有効にする

Tapoアプリの 私 → 音声アシスタント を開き、サードパーティ連携 をONにする。

これをしないとローカルAPIへのアクセスが弾かれるので最初にやっておく。

P110MのIPアドレスを固定する(推奨)

ルーターのDHCP設定でMACアドレスバインドしておくと安心。再起動などでIPが変わるとスクリプトが止まる。 今回はルーター入替予定があるためサボったが、やっておいた方がいい。

Proxmox に LXC コンテナを作る

メモリ128MB、ストレージ4GBで十分。LXCコンテナはVMより軽量で、既存サーバーに同居させても影響はほぼない。

pct create 204 local:vztmpl/debian-12-standard_12.12-1_amd64.tar.zst \
  --hostname tapo-monitor \
  --memory 128 \
  --cores 1 \
  --rootfs local-lvm:4 \
  --net0 name=eth0,bridge=vmbr0,ip=dhcp,gw=192.168.x.x \
  --nameserver 8.8.8.8 \
  --start 1 \
  --onboot 1

Python環境とtapoライブラリを入れる。

pct exec 204 -- bash
apt-get update && apt-get install -y python3 python3-venv
python3 -m venv /opt/tapo-monitor/venv
/opt/tapo-monitor/venv/bin/pip install tapo

スクリプト

設定ファイルとメインスクリプトの2ファイル構成。

/opt/tapo-monitor/config.py

TAPO_EMAIL    = "your@email.com"
TAPO_PASSWORD = "your_password"

WATCH_IP   = "192.168.x.x"  # 監視するP110M (PC)
CONTROL_IP = "192.168.x.x"  # 制御するP110M (ファン)

THRESHOLD_W   = 25.0  # この値を超えたらON
HYSTERESIS    = 5.0   # この値だけ下回ったらOFF
POLL_INTERVAL = 10    # ポーリング間隔 (秒)

THRESHOLD_W はPC起動時の待機電力より少し大きい値に設定する。HYSTERESIS はON/OFFが頻繁に切り替わるのを防ぐバッファで、今回は25W超でON、20W未満でOFFという設定にした。

/opt/tapo-monitor/monitor.py

import asyncio
import logging
import sys
sys.path.insert(0, "/opt/tapo-monitor")
import config
from tapo import ApiClient

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
)
log = logging.getLogger(__name__)

async def main():
    client = ApiClient(config.TAPO_EMAIL, config.TAPO_PASSWORD)

    watch = await client.p110(config.WATCH_IP)
    ctrl  = await client.p110(config.CONTROL_IP)

    watch_info = await watch.get_device_info()
    ctrl_info  = await ctrl.get_device_info()
    log.info(f"Watch  : {watch_info.nickname} ({config.WATCH_IP})")
    log.info(f"Control: {ctrl_info.nickname} ({config.CONTROL_IP})")

    ctrl_on = ctrl_info.device_on

    while True:
        try:
            usage = await watch.get_energy_usage()
            power_w = (usage.current_power or 0) / 1000.0
            log.info(f"{watch_info.nickname}: {power_w:.1f}W  (control={'ON' if ctrl_on else 'OFF'})")

            threshold_off = config.THRESHOLD_W - config.HYSTERESIS
            if not ctrl_on and power_w > config.THRESHOLD_W:
                await ctrl.on()
                ctrl_on = True
                log.info(f"-> {ctrl_info.nickname} ON  ({power_w:.1f}W > {config.THRESHOLD_W}W)")
            elif ctrl_on and power_w < threshold_off:
                await ctrl.off()
                ctrl_on = False
                log.info(f"-> {ctrl_info.nickname} OFF ({power_w:.1f}W < {threshold_off}W)")

        except Exception as e:
            log.error(f"Error: {e}")

        await asyncio.sleep(config.POLL_INTERVAL)

asyncio.run(main())

systemd サービスとして常駐させる

/etc/systemd/system/tapo-monitor.service

[Unit]
Description=Tapo P110M Power Monitor
After=network-online.target
Wants=network-online.target

[Service]
ExecStart=/opt/tapo-monitor/venv/bin/python3 /opt/tapo-monitor/monitor.py
WorkingDirectory=/opt/tapo-monitor
Restart=always
RestartSec=30

[Install]
WantedBy=multi-user.target
systemctl enable --now tapo-monitor
journalctl -u tapo-monitor -f

動作確認のログはこんな感じ。PCが起動中は200W超を検知してファンがON、シャットダウン後に20W未満になるとOFFになる。

2026-06-16 11:55:17 INFO Watch  : PC
2026-06-16 11:55:17 INFO Control: ファン
2026-06-16 11:55:17 INFO PC: 217.2W  (control=ON)
2026-06-16 11:55:27 INFO PC: 211.6W  (control=ON)

まとめ

  • Tapo P110M はローカルAPIがあるのでクラウド不要で制御できる

  • Tapoアプリで Third-Party Compatibility をONにしないとAPIが使えない

  • LXCコンテナはメモリ128MBで十分。Proxmoxで気軽に立てられる

  • PC連動タップの代替として P110M 2台で同等のことができた