Import live AC automations (v1 baseline)
Snapshot of the three AC peak-shaving automations exactly as they run on the HA server today, imported via the config API, plus deploy tooling. This is the baseline the v2 hardening diffs against. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
cfb3520ef7
6 changed files with 206 additions and 0 deletions
12
.gitignore
vendored
Normal file
12
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# secrets — never commit the HA token or anything derived from `pass`
|
||||
ha_token
|
||||
*.token
|
||||
.env
|
||||
*.secret
|
||||
|
||||
# python
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# editor / os
|
||||
.DS_Store
|
||||
6
README.md
Normal file
6
README.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
get my home assistant token by
|
||||
|
||||
ha_token=$(pass show home-assistant/192.168.128.3/jm/api) # it asks me to touch the yubikey.
|
||||
server: 192.168.128.3
|
||||
|
||||
I want you to add an automation to set the AC thermostat to 85 at 17:58, 90 at 17:59, 85 at 18:00, 90 at 18:01 and so on until 18:20
|
||||
15
automations/ac_peak_ends.yaml
Normal file
15
automations/ac_peak_ends.yaml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# ac_peak_ends — imported live baseline (v1) on 2026-07-09
|
||||
id: ac_peak_ends
|
||||
alias: AC - Peak ends, return to 74
|
||||
description: ''
|
||||
triggers:
|
||||
- trigger: time
|
||||
at: '19:00:00'
|
||||
conditions: []
|
||||
actions:
|
||||
- action: climate.set_temperature
|
||||
target:
|
||||
entity_id: climate.t6_pro_z_wave_programmable_thermostat_with_smartstart
|
||||
data:
|
||||
temperature: 74
|
||||
mode: single
|
||||
23
automations/ac_peak_setback.yaml
Normal file
23
automations/ac_peak_setback.yaml
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# ac_peak_setback — imported live baseline (v1) on 2026-07-09
|
||||
id: ac_peak_setback
|
||||
alias: AC - Peak setback to 82
|
||||
description: ''
|
||||
triggers:
|
||||
- trigger: time
|
||||
at: '14:00:00'
|
||||
conditions:
|
||||
- condition: state
|
||||
entity_id: binary_sensor.workday_sensor
|
||||
state: 'on'
|
||||
actions:
|
||||
- action: climate.set_hvac_mode
|
||||
target:
|
||||
entity_id: climate.t6_pro_z_wave_programmable_thermostat_with_smartstart
|
||||
data:
|
||||
hvac_mode: cool
|
||||
- action: climate.set_temperature
|
||||
target:
|
||||
entity_id: climate.t6_pro_z_wave_programmable_thermostat_with_smartstart
|
||||
data:
|
||||
temperature: 82
|
||||
mode: single
|
||||
68
automations/ac_precool_before_peak.yaml
Normal file
68
automations/ac_precool_before_peak.yaml
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
# ac_precool_before_peak — imported live baseline (v1) on 2026-07-09
|
||||
id: ac_precool_before_peak
|
||||
alias: AC - Pre-cool before peak
|
||||
description: ''
|
||||
triggers:
|
||||
- trigger: time
|
||||
at: '12:00:00'
|
||||
conditions:
|
||||
- condition: state
|
||||
entity_id: binary_sensor.workday_sensor
|
||||
state: 'on'
|
||||
actions:
|
||||
- action: weather.get_forecasts
|
||||
target:
|
||||
entity_id: weather.forecast_4315aspenhill
|
||||
data:
|
||||
type: daily
|
||||
response_variable: fc
|
||||
- variables:
|
||||
today_high: "{% set today = now().date() %} {% set entries = fc['weather.forecast_4315aspenhill'].forecast\n\
|
||||
\ | selectattr('datetime')\n | list %}\n{% set match = entries\n | selectattr('datetime',\
|
||||
\ 'search', today | string)\n | list %}\n{{ (match[0].temperature if match\
|
||||
\ else entries[0].temperature) | float(0) }}\n"
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: '{{ today_high >= 90 }}'
|
||||
sequence:
|
||||
- action: climate.set_hvac_mode
|
||||
target:
|
||||
entity_id: climate.t6_pro_z_wave_programmable_thermostat_with_smartstart
|
||||
data:
|
||||
hvac_mode: cool
|
||||
- action: climate.set_temperature
|
||||
target:
|
||||
entity_id: climate.t6_pro_z_wave_programmable_thermostat_with_smartstart
|
||||
data:
|
||||
temperature: 68
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: '{{ today_high >= 85 }}'
|
||||
sequence:
|
||||
- action: climate.set_hvac_mode
|
||||
target:
|
||||
entity_id: climate.t6_pro_z_wave_programmable_thermostat_with_smartstart
|
||||
data:
|
||||
hvac_mode: cool
|
||||
- action: climate.set_temperature
|
||||
target:
|
||||
entity_id: climate.t6_pro_z_wave_programmable_thermostat_with_smartstart
|
||||
data:
|
||||
temperature: 70
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: '{{ today_high >= 80 }}'
|
||||
sequence:
|
||||
- action: climate.set_hvac_mode
|
||||
target:
|
||||
entity_id: climate.t6_pro_z_wave_programmable_thermostat_with_smartstart
|
||||
data:
|
||||
hvac_mode: cool
|
||||
- action: climate.set_temperature
|
||||
target:
|
||||
entity_id: climate.t6_pro_z_wave_programmable_thermostat_with_smartstart
|
||||
data:
|
||||
temperature: 71
|
||||
default: []
|
||||
mode: single
|
||||
82
deploy.py
Normal file
82
deploy.py
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Deploy AC automations from automations/*.yaml to Home Assistant.
|
||||
|
||||
Source of truth is THIS repo. Home Assistant (remote, API-only) is the deploy
|
||||
target. Each YAML file's ``id`` is the key: the script POSTs to
|
||||
``/api/config/automation/config/<id>`` which replaces that automation *in place*
|
||||
(same id -> same entity_id, run history and traces stay continuous).
|
||||
|
||||
Usage:
|
||||
source ~/.bashrc # provides HA_API_KEY
|
||||
python3 deploy.py --check # parse/validate only, no writes
|
||||
python3 deploy.py # deploy every automations/*.yaml
|
||||
python3 deploy.py ac_peak_ends # deploy only the file(s) whose id matches
|
||||
|
||||
Env:
|
||||
HA_API_KEY long-lived HA token (required to deploy)
|
||||
HA_HOST host:port (default 192.168.128.3:8123)
|
||||
"""
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
import yaml
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
HA_HOST = os.environ.get("HA_HOST", "192.168.128.3:8123")
|
||||
TOKEN = os.environ.get("HA_API_KEY")
|
||||
|
||||
|
||||
def load_all():
|
||||
autos = []
|
||||
for path in sorted(glob.glob(os.path.join(HERE, "automations", "*.yaml"))):
|
||||
with open(path) as fh:
|
||||
doc = yaml.safe_load(fh)
|
||||
if not doc or "id" not in doc:
|
||||
sys.exit(f"{path}: missing required 'id'")
|
||||
autos.append((path, doc))
|
||||
return autos
|
||||
|
||||
|
||||
def deploy(doc):
|
||||
url = f"http://{HA_HOST}/api/config/automation/config/{doc['id']}"
|
||||
req = urllib.request.Request(
|
||||
url, data=json.dumps(doc).encode(), method="POST",
|
||||
headers={"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req) as r:
|
||||
return r.status, r.read().decode().strip()
|
||||
except urllib.error.HTTPError as e:
|
||||
return e.code, e.read().decode().strip()
|
||||
|
||||
|
||||
def main():
|
||||
args = [a for a in sys.argv[1:] if not a.startswith("-")]
|
||||
check = "--check" in sys.argv
|
||||
autos = load_all()
|
||||
if args:
|
||||
autos = [(p, d) for p, d in autos if d["id"] in args]
|
||||
if not autos:
|
||||
sys.exit(f"no automations matched {args}")
|
||||
if not check and not TOKEN:
|
||||
sys.exit("HA_API_KEY not set (try: source ~/.bashrc)")
|
||||
|
||||
rc = 0
|
||||
for path, doc in autos:
|
||||
name = os.path.basename(path)
|
||||
if check:
|
||||
print(f"ok parse {name} id={doc['id']} alias={doc.get('alias')!r}")
|
||||
continue
|
||||
status, resp = deploy(doc)
|
||||
ok = status == 200
|
||||
rc |= 0 if ok else 1
|
||||
print(f"{'OK ' if ok else 'ERR'} {status} {name} id={doc['id']} {resp}")
|
||||
sys.exit(rc)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in a new issue