2026-07-10 01:09:48 +00:00
|
|
|
|
# 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.
|
|
|
|
|
|
|
2026-07-10 02:04:16 +00:00
|
|
|
|
## 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** | Jun–Sep | **14:00–19:00** (2–7 PM) | $0.42 | $0.17 |
|
|
|
|
|
|
| **Winter** | Oct–May | **06:00–09:00** and **17:00–21:00** | $0.32 | $0.13 |
|
|
|
|
|
|
|
|
|
|
|
|
Weekends and **federal** holidays are always off-peak — handled by
|
|
|
|
|
|
`binary_sensor.workday_sensor` (with a Mon–Fri fallback). Heating is **gas**
|
|
|
|
|
|
(not on this electric peak logic), so the winter rules are **cooling-only**.
|
|
|
|
|
|
|
2026-07-10 01:09:48 +00:00
|
|
|
|
## 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 |
|
|
|
|
|
|
|
2026-07-10 02:04:16 +00:00
|
|
|
|
`entity_id` derives from `alias`, not `id`. Reference automations elsewhere in
|
|
|
|
|
|
HA by their `entity_id`.
|
2026-07-10 01:09:48 +00:00
|
|
|
|
|
|
|
|
|
|
## Deploying
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
source ~/.bashrc # provides HA_API_KEY (long-lived token)
|
|
|
|
|
|
python3 deploy.py --check # parse only, no writes
|
2026-07-10 02:04:16 +00:00
|
|
|
|
python3 deploy.py # deploy every automations/*.yaml (in place, by id)
|
2026-07-10 01:09:48 +00:00
|
|
|
|
python3 deploy.py ac_peak_ends # deploy just one by id
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-07-10 02:04:16 +00:00
|
|
|
|
## 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.
|
2026-07-10 01:09:48 +00:00
|
|
|
|
|
|
|
|
|
|
## The rules
|
|
|
|
|
|
|
|
|
|
|
|
All act on `climate.t6_pro_z_wave_programmable_thermostat_with_smartstart`.
|
2026-07-10 02:04:16 +00:00
|
|
|
|
|
|
|
|
|
|
### 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).
|
2026-07-10 01:09:48 +00:00
|
|
|
|
- **Fallbacks:** workday sensor broken → Mon–Fri; forecast broken → assume 88°F
|
2026-07-10 02:04:16 +00:00
|
|
|
|
→ 70°F (`continue_on_error` + `fc is defined and is mapping` guard).
|
2026-07-10 01:09:48 +00:00
|
|
|
|
|
2026-07-10 02:04:16 +00:00
|
|
|
|
### 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).
|
2026-07-10 01:09:48 +00:00
|
|
|
|
|
2026-07-10 02:04:16 +00:00
|
|
|
|
### 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.
|
2026-07-10 01:09:48 +00:00
|
|
|
|
|
2026-07-10 02:04:16 +00:00
|
|
|
|
### 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 6–9 AM winter peak. **No auto-resume yet** (add later). Cooling-only.
|
2026-07-10 01:09:48 +00:00
|
|
|
|
|
2026-07-10 02:04:16 +00:00
|
|
|
|
## Failure-mode design
|
2026-07-10 01:09:48 +00:00
|
|
|
|
|
|
|
|
|
|
| Failure | Symptom | Handled by | Default taken |
|
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
| Workday/holiday sensor unavailable | `states()` ≠ `on`/`off` | `{% if wd in ['on','off'] %}…{% else %} now().weekday()<5` | Mon–Fri |
|
|
|
|
|
|
| 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) |
|
2026-07-10 02:04:16 +00:00
|
|
|
|
| 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) |
|
2026-07-10 01:09:48 +00:00
|
|
|
|
|
2026-07-10 02:04:16 +00:00
|
|
|
|
Fallback and season/cooling logic verified via HA's template engine and
|
|
|
|
|
|
execution traces before each deploy.
|
2026-07-10 01:09:48 +00:00
|
|
|
|
|
|
|
|
|
|
## Changelog
|
|
|
|
|
|
|
|
|
|
|
|
- **2026-07-09 — v1 baseline**: imported the three live automations as-is.
|
2026-07-10 02:04:16 +00:00
|
|
|
|
- **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 (Jun–Sep).
|
|
|
|
|
|
`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).
|