Automated, weather-based control of roof heat cables (de-icing / ice-dam prevention) via TP-Link Kasa KP400 smart outdoor plugs.
It queries the US National Weather Service for your location, decides whether icing conditions warrant running the cables, and switches your Kasa plugs directly over the LAN (no cloud account, no reverse-engineered web API).
Ice dams form in a melt/refreeze window near freezing with snow present: snow melts, water runs to the cold eaves, and refreezes. So the cables run when:
- the temperature is inside a configurable band (default 15–38 °F), and
- frozen precipitation is present/forecast (can be disabled).
Extras for real-world robustness:
- Hysteresis widens the band once the cables are on, to avoid flapping.
- Minimum dwell time prevents rapid on/off cycling.
- Fail-safe: if weather can't be fetched during winter months, bias to a safe state (default: ON).
- Graceful degradation: an unreachable switch is logged and reported (and
makes
runexit non-zero) without aborting the rest of the cycle.
Requires Python 3.10+. Run this on an always-on machine on the same LAN as the switches (e.g. a Raspberry Pi).
python3 -m venv .venv
source .venv/bin/activate
pip install -e .cp config.example.yaml config.yamlThen edit config.yaml:
location— your roof's latitude/longitude (decimal degrees).weather.user_agent_contact— an email; NWS requires an identifying User-Agent.switches— the IP(s) of your KP400(s) and whichoutletsto control.icing— the temperature band and behavior tunables.
Give each KP400 a reserved/static IP in your router so its address never changes. Find the switches on your network with:
heatwires discoverconfig.yaml and state.json are git-ignored (they're host-specific).
heatwires -c config.yaml discover # list Kasa devices on the LAN
heatwires -c config.yaml status # show weather + switch state + decision (no change)
heatwires -c config.yaml run # run one control cycle (turns cables on/off)
heatwires -c config.yaml run --dry-run # decide + read switches, but don't change them
heatwires -c config.yaml loop -i 900 # run continuously, every 900sOption A — cron (runs one cycle every 15 minutes):
*/15 * * * * /path/to/.venv/bin/heatwires -c /path/to/config.yaml run >> /path/to/heatwires.log 2>&1Option B — systemd service (long-running loop, auto-restart):
# /etc/systemd/system/heatwires.service
[Unit]
Description=Roof heat-cable weather controller
After=network-online.target
Wants=network-online.target
[Service]
ExecStart=/path/to/.venv/bin/heatwires -c /path/to/config.yaml loop -i 900
Restart=always
RestartSec=30
User=pi
[Install]
WantedBy=multi-user.targetsudo systemctl enable --now heatwires
journalctl -u heatwires -fThe Kasa app keeps working independently as a manual override at any time.
pip install pytest
pytest -q # offline tests for the decision logic and NWS parsing| File | Purpose |
|---|---|
heatwires/weather.py |
NWS client → temperature + snow/ice signal |
heatwires/logic.py |
the icing decision (band + snow + hysteresis) |
heatwires/switches.py |
Kasa control over the LAN (python-kasa) |
heatwires/controller.py |
one cycle: weather → decision → switches → state |
heatwires/state.py |
run-to-run state (last state, dwell timing) |
heatwires/config.py |
YAML config loading/validation |
heatwires/cli.py |
discover / status / run / loop commands |