home-assistant/AUTOMATIONS.md
jm 7a0d75d9db Add TOU winter peak-shaving; parameterize by season (v3)
Pepco MD TOU-P has different on-peak windows by season: summer (Jun-Sep)
14:00-19:00, winter (Oct-May) 06:00-09:00 and 17:00-21:00. Heat is gas, so
winter electric peak logic is cooling-only.

* pre-cool: bounded to summer (Jun-Sep); winter has no pre-cool.
* peak-setback (hold) and peak-ends (return): unified across seasons via a
  season self-select (month -> season; triggers carry id: summer/winter and act
  only when trigger.id == season) plus a params map for setpoints. Summer forces
  cool + 82 / returns 74 daily; winter sets 82 / returns 74 ONLY when already in
  cool mode, so it never disturbs the gas furnace.
* new ac_winter_morning_kill: 06:00 winter work days, if in cool mode set
  hvac_mode off to dodge the 6-9am winter peak. No auto-resume yet (planned).

Decision logic verified as a truth table via the template engine across
summer/winter x cooling x workday before commit. Deployed in place (existing
ids unchanged); new id ac_winter_morning_kill added.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 22:04:16 -04:00

5.4 KiB
Raw Permalink Blame History

Home Assistant automations

Source of truth for the AC peak-shaving automations on the HA server (192.168.128.3:8123). This repo is authoritative; HA is a deploy target reached only through its REST config API — HA never sees these files directly.

Utility rate context (Pepco MD, TOU-P, eff. 2026-02-01)

Season (rate) Months On-peak window(s), weekdays only On-peak Off-peak
Summer JunSep 14:0019:00 (27 PM) $0.42 $0.17
Winter OctMay 06:0009:00 and 17:0021:00 $0.32 $0.13

Weekends and federal holidays are always off-peak — handled by binary_sensor.workday_sensor (with a MonFri fallback). Heating is gas (not on this electric peak logic), so the winter rules are cooling-only.

Identity model (how a file becomes a live automation)

 file (git)                        Home Assistant
 automations/ac_peak_setback.yaml  ──deploy.py──▶  POST /api/config/automation/
   id:    ac_peak_setback              (by id)         config/ac_peak_setback
   alias: "AC - Peak setback to 82"                        │
   ...                                                     ▼ registers entity
                                                  automation.ac_peak_setback_to_82
Identifier Example Set by Used by Change it?
file name ac_peak_setback.yaml you git only (HA never sees it) freely
id ac_peak_setback you (in YAML) config API URL, traces, automation.trigger never (primary key)
alias AC - Peak setback to 82 you (in YAML) UI name; seeds entity_id at first create rarely
entity_id automation.ac_peak_setback_to_82 HA, from slugify(alias) dashboards, other automations, scripts sticky once registered

entity_id derives from alias, not id. Reference automations elsewhere in HA by their entity_id.

Deploying

source ~/.bashrc                 # provides HA_API_KEY (long-lived token)
python3 deploy.py --check        # parse only, no writes
python3 deploy.py                # deploy every automations/*.yaml (in place, by id)
python3 deploy.py ac_peak_ends   # deploy just one by id

Parameterization (the "params map" model)

Rather than duplicate summer vs winter rules, each phase is one self-selecting automation:

  • A variables: block computes season from the month (summer = {6,7,8,9}, else winter).
  • Time triggers carry id: summer / id: winter; the body only acts when trigger.id == season, so the off-season time is a harmless no-op.
  • Setpoints come from a params map ({'summer': {'hold':82,'ret':74}, 'winter': {'hold':82,'ret':74}}) so the two seasons can diverge later by editing one line. (Both are equal today.) The map is repeated in ac_peak_setback and ac_peak_ends; keep them in sync.

The rules

All act on climate.t6_pro_z_wave_programmable_thermostat_with_smartstart.

1. ac_precool_before_peak — Pre-cool before peak (summer only)

  • 12:00, summer work days. Fetches today's forecast high and pre-cools: ≥90→68, ≥85→70, ≥80→71, <80→no pre-cool.
  • Winter has no pre-cool (marginal for an evening peak).
  • Fallbacks: workday sensor broken → MonFri; forecast broken → assume 88°F → 70°F (continue_on_error + fc is defined and is mapping guard).

2. ac_peak_setback — Peak setback to 82 (both seasons)

  • 14:00 (summer) / 17:00 (winter), work days.
  • Summer: forces cool and sets 82.
  • Winter: cooling-only — sets 82 only if already in cool mode (never forces mode, never touches gas heat).

3. ac_peak_ends — Peak ends, return to 74 (both seasons)

  • 19:00 (summer) / 21:00 (winter). Sets 74.
  • Summer returns daily; winter returns only if in cool mode.

4. ac_winter_morning_kill — Winter morning peak kill (new in v3)

  • 06:00, winter work days. If in cool mode, sets hvac_mode: off to dodge the 69 AM winter peak. No auto-resume yet (add later). Cooling-only.

Failure-mode design

Failure Symptom Handled by Default taken
Workday/holiday sensor unavailable states()on/off {% if wd in ['on','off'] %}…{% else %} now().weekday()<5 MonFri
Weather service raises action would abort continue_on_error: true run continues
Weather returns empty/None fc undefined / not a mapping {% if fc is defined and fc is mapping %}…{% else %} 88 regular pre-cool (88→70)
Wrong-season trigger fires trigger.id != season applies = trigger.id == season guard no-op
Winter, not cooling (e.g. gas heat on) is_state(climate,'cool') false cooling-only guard no-op (furnace safe)

Fallback and season/cooling logic verified via HA's template engine and execution traces before each deploy.

Changelog

  • 2026-07-09 — v1 baseline: imported the three live automations as-is.
  • 2026-07-09 — v2: added workday + weather fallbacks to pre-cool and setback; removed throwaway debug automations (three TEST spoofs + ac_flap_1758_1820).
  • 2026-07-09 — v3: TOU winter support. Pre-cool bounded to summer (JunSep). ac_peak_setback and ac_peak_ends unified across seasons via a season self-select + params map (summer 14:00/19:00, winter 17:00/21:00), winter cooling-only. Added ac_winter_morning_kill (6 AM winter, cooling→off, no resume yet).