From f41439f82e93f7a97673e00a0892bb878cde274a Mon Sep 17 00:00:00 2001 From: Nikolay Miroshnichenko Date: Sun, 28 Jun 2026 12:01:15 +0200 Subject: [PATCH] Add examples/demo-home: end-to-end worked example A synthetic 239-GA / 47-Function demo .knxproj (ETS6 schema, parseable by xknxproject) with 5 deliberate mistakes, the tool's generated artifacts (project report, Home Assistant entities, ETS XML/CSV), and a ha-brain/ smart layer: circadian lighting, an 8-factor climate setpoint, presence/season/time state machine and statistics. Whitelist the demo .knxproj past the *.knxproj gitignore (synthetic, no real topology/secrets). Co-Authored-By: Claude Opus 4.8 --- .gitignore | 2 + CHANGELOG.md | 7 + examples/demo-home/README.md | 54 +++ examples/demo-home/demo-home.knxproj | Bin 0 -> 99067 bytes .../demo-home/generated/group-addresses.csv | 281 +++++++++++++ .../demo-home/generated/group-addresses.xml | 324 +++++++++++++++ .../generated/home-assistant-knx.yaml | 372 ++++++++++++++++++ .../demo-home/generated/project_report.md | 117 ++++++ examples/demo-home/ha-brain/README.md | 108 +++++ .../ha-brain/packages/00_helpers.yaml | 72 ++++ .../ha-brain/packages/10_context_sensors.yaml | 180 +++++++++ .../ha-brain/packages/20_home_mode.yaml | 165 ++++++++ .../ha-brain/packages/30_climate_engine.yaml | 204 ++++++++++ .../ha-brain/packages/40_lighting_engine.yaml | 255 ++++++++++++ .../ha-brain/packages/50_statistics.yaml | 135 +++++++ 15 files changed, 2276 insertions(+) create mode 100644 examples/demo-home/README.md create mode 100644 examples/demo-home/demo-home.knxproj create mode 100644 examples/demo-home/generated/group-addresses.csv create mode 100644 examples/demo-home/generated/group-addresses.xml create mode 100644 examples/demo-home/generated/home-assistant-knx.yaml create mode 100644 examples/demo-home/generated/project_report.md create mode 100644 examples/demo-home/ha-brain/README.md create mode 100644 examples/demo-home/ha-brain/packages/00_helpers.yaml create mode 100644 examples/demo-home/ha-brain/packages/10_context_sensors.yaml create mode 100644 examples/demo-home/ha-brain/packages/20_home_mode.yaml create mode 100644 examples/demo-home/ha-brain/packages/30_climate_engine.yaml create mode 100644 examples/demo-home/ha-brain/packages/40_lighting_engine.yaml create mode 100644 examples/demo-home/ha-brain/packages/50_statistics.yaml diff --git a/.gitignore b/.gitignore index 6977844..15b7129 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,8 @@ knx-workspace/ *.knxproj *.knxkeys *.keyring +# ...except the curated synthetic demo (no real topology / no secrets) +!examples/demo-home/demo-home.knxproj # OS / editors .DS_Store diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c7bdff..c94f063 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- `examples/demo-home/` — a complete synthetic worked example: a 239-GA / 47-Function demo + `.knxproj`, the tool's generated report + Home Assistant entities + ETS export, and a + `ha-brain/` Home Assistant smart layer (circadian lighting, multi-factor climate, presence/ + season/time logic, statistics). Includes 5 deliberate mistakes to show the checks (and an + honest note on the one the missing-status check doesn't yet catch). + ## [0.1.2] — 2026-06-28 **Home Assistant mapping quality** — a second hardening pass driven by running the tool diff --git a/examples/demo-home/README.md b/examples/demo-home/README.md new file mode 100644 index 0000000..e1039e1 --- /dev/null +++ b/examples/demo-home/README.md @@ -0,0 +1,54 @@ +# Demo House — end-to-end example + +A complete, **synthetic** worked example of the whole stack this project targets: + +``` +demo-home.knxproj → nickol-knx-mcp → generated/ (report · HA entities · ETS export) + (KNX design) (design-time) ↓ + ha-brain/ (Home Assistant smart logic) +``` + +It exists so you can see real input → real output without needing your own project, and as a +showcase of how the KNX (I/O) and Home Assistant (logic) layers fit together. + +## What's here + +| Path | What it is | +|---|---| +| `demo-home.knxproj` | The synthetic project: **239 group addresses, 47 ETS Functions**, 13 zones (living w/ fireplace, kitchen, master + 2 kids bedrooms each with ensuite, guest WC, laundry, 2 corridors, staircase). Lighting (switch/dim/CCT/RGBW), underfloor heating + AC, sensors, scenes. | +| `generated/project_report.md` | `project_report` output — inventory + 🔴🟡🔵 findings + HA mapping preview. | +| `generated/home-assistant-knx.yaml` | `generate_ha_package` output — KNX entities (switch/light/cover/binary_sensor/sensor) + a `review` list. **Nothing is dropped silently.** | +| `generated/group-addresses.xml` / `.csv` | `generate_ets_group_addresses` output — ETS-importable. | +| `ha-brain/` | The Home Assistant **smart layer** on top of these entities — circadian lighting, multi-factor climate, presence/season/time logic, statistics. See its own [README](ha-brain/README.md). | + +## What the tool found (and the deliberate flaws) + +The project **intentionally contains 5 mistakes** so the checks have something to catch — see them +flagged in `generated/project_report.md`: + +| # | Planted mistake | Caught? | +|---|---|---| +| 1 | A group address with **no DPT** (`Living room CO2`, 4/2/1) | ✅ `check_dpt` 🔴 | +| 2 | Two GAs with the **same name, different DPT** (`Kitchen temperature`) | ✅ `check_dpt` | +| 3 | A dimmer with an on/off status but **no brightness status** (`Kitchen worktop LED`) | ⚠️ not flagged* | +| 4 | A switch with **no status** at all (`Guest WC ceiling`) | ✅ `check_missing_status` | +| 5 | A GA with an **empty name** (`2/5/2`) | ✅ `check_naming` 🔴 | + +\* **Honest limitation surfaced by this very demo:** the missing-status check currently asks +"does this control have *a* status?", not "does it have *each expected* status type?". The Kitchen +dimmer has an on/off status, so it isn't flagged for its missing brightness status. Tracked for a +future release. + +Inventory: 239 GAs · 47 Functions · 0 errors that block parsing. The HA generator produced +**19 switches, 13 lights, 6 covers, 13 binary sensors, 63 sensors**, with 18/19 switches getting a +`state_address` paired via ETS Function roles. + +## ⚠️ Caveats (please read) + +- **Synthetic & generated.** This `.knxproj` was authored programmatically (ETS6 schema + `project/22`) — not exported from ETS. It parses cleanly with `xknxproject` (the library this + tool uses) and is intended for **tool validation and demonstration**. It has **not** been + verified to open in ETS6 itself; a real ETS-authored export is the gold standard for that. +- **The flaws are on purpose.** Do not "fix" them — they are the point. +- **`ha-brain/` is a design demo.** Valid Home Assistant YAML built on best practices, but not + deployed against a live bus here; entity IDs assume the names `generate_ha_package` produces. diff --git a/examples/demo-home/demo-home.knxproj b/examples/demo-home/demo-home.knxproj new file mode 100644 index 0000000000000000000000000000000000000000..4b72f0bdba1d771c806c386b25a04721144563af GIT binary patch literal 99067 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/demo-home/generated/home-assistant-knx.yaml b/examples/demo-home/generated/home-assistant-knx.yaml new file mode 100644 index 0000000..535df4f --- /dev/null +++ b/examples/demo-home/generated/home-assistant-knx.yaml @@ -0,0 +1,372 @@ +# Home Assistant KNX package generated by nickol-knx-mcp +# Source project: Nikolay Demo House +# REVIEW before use: command/status pairing is inferred heuristically. +# Multi-GA fixtures (light/cover/climate) and scenes need manual verification. +# 65 group address(es) need manual review (see the 'review' list); nothing is dropped silently. + +knx: + switch: + - name: All lights off + address: 0/1/1 + - name: Living room fireplace on/off + address: 0/3/0 + state_address: 0/4/0 + - name: Kitchen island pendants switch + address: 1/0/12 + state_address: 1/4/12 + - name: Master bath mirror CCT switch + address: 1/0/26 + state_address: 1/4/26 + - name: Kids room 1 ceiling switch + address: 1/0/30 + state_address: 1/4/30 + - name: Kids room 1 RGB accent switch + address: 1/0/31 + state_address: 1/4/31 + - name: Kids bath 1 ceiling switch + address: 1/0/35 + state_address: 1/4/35 + - name: Kids room 2 ceiling switch + address: 1/0/40 + state_address: 1/4/40 + - name: Kids room 2 desk light switch + address: 1/0/41 + state_address: 1/4/41 + - name: Kids bath 2 ceiling switch + address: 1/0/45 + state_address: 1/4/45 + - name: Guest WC ceiling switch + address: 1/0/50 + state_address: 1/4/1 + - name: Laundry ceiling switch + address: 1/0/55 + state_address: 1/4/55 + - name: Corridor GF ceiling switch + address: 1/0/60 + state_address: 1/4/60 + - name: Corridor UF ceiling switch + address: 1/0/61 + state_address: 1/4/61 + - name: Living room AC on/off + address: 3/2/21 + state_address: 3/5/21 + - name: Kitchen AC on/off + address: 3/2/22 + state_address: 3/5/22 + - name: Master bedroom AC on/off + address: 3/2/23 + state_address: 3/5/23 + - name: Kids room 1 AC on/off + address: 3/2/25 + state_address: 3/5/25 + - name: Kids room 2 AC on/off + address: 3/2/27 + state_address: 3/5/27 + light: + - name: Living room cove brightness + brightness_address: 1/2/2 + brightness_state_address: 1/5/2 + address: 1/0/1 + state_address: 1/4/1 + - name: Living room RGBW accent wall brightness + brightness_address: 1/2/3 + brightness_state_address: 1/5/3 + address: 1/0/2 + state_address: 1/4/2 + - name: Kitchen worktop LED brightness + brightness_address: 1/2/11 + brightness_state_address: 1/5/12 + address: 1/0/10 + state_address: 1/4/10 + - name: Kitchen island pendants brightness + brightness_address: 1/2/12 + brightness_state_address: 1/5/12 + address: 1/0/11 + state_address: 1/4/11 + - name: Master bedroom ceiling brightness + brightness_address: 1/2/20 + brightness_state_address: 1/5/20 + address: 1/0/20 + state_address: 1/4/20 + - name: Master bedroom bedside left brightness + brightness_address: 1/2/21 + brightness_state_address: 1/5/21 + address: 1/0/21 + state_address: 1/4/21 + - name: Master bedroom bedside right brightness + brightness_address: 1/2/22 + brightness_state_address: 1/5/22 + address: 1/0/22 + state_address: 1/4/22 + - name: Master bath mirror CCT brightness + brightness_address: 1/2/26 + brightness_state_address: 1/5/26 + address: 1/0/23 + state_address: 1/4/23 + - name: Kids room 1 ceiling brightness + brightness_address: 1/2/30 + brightness_state_address: 1/5/30 + address: 1/0/3 + state_address: 1/4/3 + - name: Kids room 1 RGB accent brightness + brightness_address: 1/2/31 + brightness_state_address: 1/5/31 + address: 1/0/4 + state_address: 1/4/4 + - name: Kids room 2 ceiling brightness + brightness_address: 1/2/40 + brightness_state_address: 1/5/40 + address: 1/0/25 + state_address: 1/4/25 + - name: Staircase LED steps brightness + brightness_address: 1/2/65 + brightness_state_address: 1/5/65 + address: 1/0/65 + state_address: 1/4/65 + - name: '' + brightness_address: 2/5/2 + cover: + - name: All blinds down + move_long_address: 0/1/2 + - name: Living room window blind up/down + move_long_address: 2/0/1 + move_short_address: 2/1/1 + position_address: 2/2/1 + position_state_address: 2/5/1 + - name: Living room terrace blind up/down + move_long_address: 2/0/2 + move_short_address: 2/1/2 + position_address: 2/2/2 + position_state_address: 2/5/20 + - name: Master bedroom blind up/down + move_long_address: 2/0/10 + move_short_address: 2/1/10 + position_address: 2/2/10 + position_state_address: 2/5/10 + - name: Kids room 1 blind up/down + move_long_address: 2/0/20 + move_short_address: 2/1/20 + position_address: 2/2/20 + position_state_address: 2/5/30 + - name: Kids room 2 blind up/down + move_long_address: 2/0/30 + move_short_address: 2/1/30 + position_address: 2/2/30 + position_state_address: 2/6/1 + binary_sensor: + - name: Master bath presence + state_address: 4/4/4 + - name: Kids bath 1 presence + state_address: 4/4/6 + - name: Kids bath 2 presence + state_address: 4/4/8 + - name: Guest WC presence + state_address: 4/4/9 + - name: Laundry presence + state_address: 4/4/10 + - name: Corridor GF presence + state_address: 4/4/11 + - name: Corridor UF presence + state_address: 4/4/12 + - name: Staircase presence + state_address: 4/4/13 + - name: Living room window contact + state_address: 4/5/1 + - name: Master bedroom window contact + state_address: 4/5/3 + - name: Lighting actuator online + state_address: 6/0/1 + - name: Shutter actuator online + state_address: 6/0/2 + - name: HVAC controller fault + state_address: 6/1/1 + sensor: + - name: Kitchen worktop LED colour temp + state_address: 1/3/11 + type: 2byte_unsigned + - name: Master bath mirror CCT colour temp + state_address: 1/3/26 + type: 2byte_unsigned + - name: Kitchen worktop LED colour temp status + state_address: 1/6/11 + type: 2byte_unsigned + - name: Master bath mirror CCT colour temp status + state_address: 1/6/26 + type: 2byte_unsigned + - name: Living room heating setpoint + state_address: 3/0/1 + type: temperature + - name: Kitchen heating setpoint + state_address: 3/0/2 + type: temperature + - name: Master bedroom heating setpoint + state_address: 3/0/3 + type: temperature + - name: Master bath heating setpoint + state_address: 3/0/4 + type: temperature + - name: Kids room 1 heating setpoint + state_address: 3/0/5 + type: temperature + - name: Kids bath 1 heating setpoint + state_address: 3/0/6 + type: temperature + - name: Kids room 2 heating setpoint + state_address: 3/0/7 + type: temperature + - name: Kids bath 2 heating setpoint + state_address: 3/0/8 + type: temperature + - name: Guest WC heating setpoint + state_address: 3/0/9 + type: temperature + - name: Laundry heating setpoint + state_address: 3/0/10 + type: temperature + - name: Corridor GF heating setpoint + state_address: 3/0/11 + type: temperature + - name: Corridor UF heating setpoint + state_address: 3/0/12 + type: temperature + - name: Living room AC setpoint + state_address: 3/0/21 + type: temperature + - name: Kitchen AC setpoint + state_address: 3/0/22 + type: temperature + - name: Master bedroom AC setpoint + state_address: 3/0/23 + type: temperature + - name: Kids room 1 AC setpoint + state_address: 3/0/25 + type: temperature + - name: Kids room 2 AC setpoint + state_address: 3/0/27 + type: temperature + - name: Living room heating setpoint status + state_address: 3/4/1 + type: temperature + - name: Kitchen heating setpoint status + state_address: 3/4/2 + type: temperature + - name: Master bedroom heating setpoint status + state_address: 3/4/3 + type: temperature + - name: Master bath heating setpoint status + state_address: 3/4/4 + type: temperature + - name: Kids room 1 heating setpoint status + state_address: 3/4/5 + type: temperature + - name: Kids bath 1 heating setpoint status + state_address: 3/4/6 + type: temperature + - name: Kids room 2 heating setpoint status + state_address: 3/4/7 + type: temperature + - name: Kids bath 2 heating setpoint status + state_address: 3/4/8 + type: temperature + - name: Guest WC heating setpoint status + state_address: 3/4/9 + type: temperature + - name: Laundry heating setpoint status + state_address: 3/4/10 + type: temperature + - name: Corridor GF heating setpoint status + state_address: 3/4/11 + type: temperature + - name: Corridor UF heating setpoint status + state_address: 3/4/12 + type: temperature + - name: Living room AC setpoint status + state_address: 3/4/21 + type: temperature + - name: Kitchen AC setpoint status + state_address: 3/4/22 + type: temperature + - name: Master bedroom AC setpoint status + state_address: 3/4/23 + type: temperature + - name: Kids room 1 AC setpoint status + state_address: 3/4/25 + type: temperature + - name: Kids room 2 AC setpoint status + state_address: 3/4/27 + type: temperature + - name: Living room temperature + state_address: 4/0/1 + type: temperature + - name: Kitchen temperature + state_address: 4/0/2 + type: temperature + - name: Master bedroom temperature + state_address: 4/0/3 + type: temperature + - name: Master bath temperature + state_address: 4/0/4 + type: temperature + - name: Kids room 1 temperature + state_address: 4/0/5 + type: temperature + - name: Kids bath 1 temperature + state_address: 4/0/6 + type: temperature + - name: Kids room 2 temperature + state_address: 4/0/7 + type: temperature + - name: Kids bath 2 temperature + state_address: 4/0/8 + type: temperature + - name: Kitchen temperature + state_address: 4/0/22 + type: 2byte_float + - name: Master bath humidity + state_address: 4/1/4 + type: humidity + - name: Kids bath 1 humidity + state_address: 4/1/6 + type: humidity + - name: Kids bath 2 humidity + state_address: 4/1/8 + type: humidity + - name: Guest WC humidity + state_address: 4/1/9 + type: humidity + - name: Laundry humidity + state_address: 4/1/10 + type: humidity + - name: Master bedroom CO2 + state_address: 4/2/3 + type: ppm + - name: Kids room 1 CO2 + state_address: 4/2/5 + type: ppm + - name: Kids room 2 CO2 + state_address: 4/2/7 + type: ppm + - name: Living room brightness + state_address: 4/3/1 + type: illuminance + - name: Corridor GF brightness + state_address: 4/3/11 + type: illuminance + - name: Corridor UF brightness + state_address: 4/3/12 + type: illuminance + - name: Staircase brightness + state_address: 4/3/13 + type: illuminance + - name: House total energy + state_address: 5/0/0 + type: active_energy_kwh + - name: Lighting energy + state_address: 5/0/1 + type: active_energy_kwh + - name: HVAC energy + state_address: 5/0/2 + type: active_energy_kwh + - name: House total power + state_address: 5/1/0 + type: power diff --git a/examples/demo-home/generated/project_report.md b/examples/demo-home/generated/project_report.md new file mode 100644 index 0000000..3c14919 --- /dev/null +++ b/examples/demo-home/generated/project_report.md @@ -0,0 +1,117 @@ +# KNX Project Report — Nikolay Demo House + +- **Source:** `demo-home.knxproj` +- **GA style:** ThreeLevel +- **ETS tool version:** 6.1.5686.0 (xknxproject 3.9.0) +- **Last modified:** 2026-06-28T10:00:00Z + + +## 1. Inventory + +- Group addresses: **239** +- Devices: **0** +- Functions: **47** +- Without DPT: **1** +- KNX Data Secure GAs: **0** + + +**By category:** diagnostics=10, energy=2, hvac=77, lighting=100, scene=4, sensor=15, shutter=26, unknown=5 + +**By kind:** command=100, sensor=55, status=81, unknown=3 + + +## 2. Findings + +Totals: 🔴 errors **2**, 🟡 warnings **10**, 🔵 info **0** + + +### 2.1 Naming & structure (2) + +- 🔴 `2/5/2` — Group address has no name. +- 🟡 `4/0/2, 4/0/22` — Name used by 2 group addresses: ['4/0/2', '4/0/22']. + + +### 2.2 Missing status addresses (8) + +- 🟡 `1/0/50` — Function 'Guest WC ceiling' (FT-1) has command GAs but no status/feedback GA. +- 🟡 `0/0/1` — Command 'Scene All off' (DPT 18.001, Scene control) has no status/feedback GA. Home Assistant cannot read real state. +- 🟡 `0/0/2` — Command 'Scene Movie' (DPT 18.001, Scene control) has no status/feedback GA. Home Assistant cannot read real state. +- 🟡 `0/0/3` — Command 'Scene Night' (DPT 18.001, Scene control) has no status/feedback GA. Home Assistant cannot read real state. +- 🟡 `0/0/4` — Command 'Scene Away' (DPT 18.001, Scene control) has no status/feedback GA. Home Assistant cannot read real state. +- 🟡 `0/1/1` — Command 'All lights off' (DPT 1.001, Switch on/off) has no status/feedback GA. Home Assistant cannot read real state. +- 🟡 `0/1/2` — Command 'All blinds down' (DPT 1.008, Up/Down) has no status/feedback GA. Home Assistant cannot read real state. +- 🟡 `2/5/2` — Command '' (DPT 5.001, Scaling 0-100% (brightness/position)) has no status/feedback GA. Home Assistant cannot read real state. + + +### 2.3 DPT consistency (2) + +- 🔴 `4/2/1` — 'Living room CO2' has no DPT assigned. Home Assistant requires a DPT to decode this group address. +- 🟡 `4/0/2, 4/0/22` — Group addresses sharing name 'Kitchen temperature' use different DPTs: ['9.001', '9.002']. + + +## 3. Home Assistant mapping preview + +Entities that can be generated now: switch **19**, light **13**, cover **6**, binary_sensor **13**, sensor **63**. + + +**Needs manual review (65):** + +- `0/1/2` All blinds down — verify_cover_mapping +- `2/0/1` Living room window blind up/down — verify_cover_mapping +- `2/0/2` Living room terrace blind up/down — verify_cover_mapping +- `2/0/10` Master bedroom blind up/down — verify_cover_mapping +- `2/0/20` Kids room 1 blind up/down — verify_cover_mapping +- `2/0/30` Kids room 2 blind up/down — verify_cover_mapping +- `0/1/1` All lights off — switch_without_status +- `0/0/1` Scene All off — manual_scene +- `0/0/2` Scene Movie — manual_scene +- `0/0/3` Scene Night — manual_scene +- `0/0/4` Scene Away — manual_scene +- `1/3/3` Living room RGBW accent wall colour — unmapped_dpt +- `1/3/31` Kids room 1 RGB accent colour — unmapped_dpt +- `1/6/3` Living room RGBW accent wall colour status — unmapped_dpt +- `1/6/31` Kids room 1 RGB accent colour status — unmapped_dpt +- `3/1/1` Living room HVAC mode — manual_climate +- `3/1/2` Kitchen HVAC mode — manual_climate +- `3/1/3` Master bedroom HVAC mode — manual_climate +- `3/1/4` Master bath HVAC mode — manual_climate +- `3/1/5` Kids room 1 HVAC mode — manual_climate +- `3/1/6` Kids bath 1 HVAC mode — manual_climate +- `3/1/7` Kids room 2 HVAC mode — manual_climate +- `3/1/8` Kids bath 2 HVAC mode — manual_climate +- `3/1/9` Guest WC HVAC mode — manual_climate +- `3/1/10` Laundry HVAC mode — manual_climate +- `3/1/11` Corridor GF HVAC mode — manual_climate +- `3/1/12` Corridor UF HVAC mode — manual_climate +- `3/1/21` Living room AC mode — manual_climate +- `3/1/22` Kitchen AC mode — manual_climate +- `3/1/23` Master bedroom AC mode — manual_climate +- `3/1/25` Kids room 1 AC mode — manual_climate +- `3/1/27` Kids room 2 AC mode — manual_climate +- `1/1/2` Living room cove dim — not_mapped +- `1/1/3` Living room RGBW accent wall dim — not_mapped +- `1/1/11` Kitchen worktop LED dim — not_mapped +- `1/1/12` Kitchen island pendants dim — not_mapped +- `1/1/20` Master bedroom ceiling dim — not_mapped +- `1/1/21` Master bedroom bedside left dim — not_mapped +- `1/1/22` Master bedroom bedside right dim — not_mapped +- `1/1/26` Master bath mirror CCT dim — not_mapped +- `1/1/30` Kids room 1 ceiling dim — not_mapped +- `1/1/31` Kids room 1 RGB accent dim — not_mapped +- `1/1/40` Kids room 2 ceiling dim — not_mapped +- `1/1/65` Staircase LED steps dim — not_mapped +- `2/3/1` Living room window blind slat — shutter_slat_unattached +- `2/3/10` Master bedroom blind slat — shutter_slat_unattached +- `2/6/10` Master bedroom blind slat status — shutter_slat_unattached +- `3/3/21` Living room AC fan speed — not_mapped +- `3/3/22` Kitchen AC fan speed — not_mapped +- `3/3/23` Master bedroom AC fan speed — not_mapped +- … and 15 more + + +## 4. Next steps + +1. Resolve 🔴 errors (missing DPT, empty names) in ETS first. +2. Add status/feedback GAs for every flagged command. +3. Re-run this report until errors are clear. +4. Generate ETS CSV/XML and HA YAML, commit to Git, then import into ETS and deploy to Home Assistant. diff --git a/examples/demo-home/ha-brain/README.md b/examples/demo-home/ha-brain/README.md new file mode 100644 index 0000000..e8ae057 --- /dev/null +++ b/examples/demo-home/ha-brain/README.md @@ -0,0 +1,108 @@ +# Demo House — the "brain" (Home Assistant smart layer) + +Sophisticated control logic for the synthetic [`demo-home.knxproj`](../demo-home.knxproj), +built the way the project's architecture demands: + +> **KNX (ETS)** is the body — group addresses are the muscles (commands) and nerves +> (status/sensors). It runs autonomously. **Home Assistant** is the *brain* — all the +> time/season/presence-aware logic and statistics live here, reading **real** KNX state. + +This is a **design demo** (no live bus). It is valid, deployable Home Assistant YAML built on +[official best practices](https://www.home-assistant.io/docs/) — native helpers +(`utility_meter`, `history_stats`, `statistics`, `derivative`, `trend`, `threshold`, `schedule`, +`integration`, `min_max`, `group`) wherever possible, trigger-based template sensors only for +genuinely multi-factor derived context, correct automation modes, `entity_id` (never +`device_id`), and `color_temp_kelvin` (mireds removed in 2026.3). + +--- + +## Architecture — 5 layers + +``` +┌────────────────────────────────────────────────────────────────────┐ +│ L4 STATISTICS energy meters · runtime · degree-hours · KPIs │ 50_statistics +├────────────────────────────────────────────────────────────────────┤ +│ L3 LIGHTING circadian brightness/CCT · motion+presence · scenes │ 40_lighting_engine +│ L2 CLIMATE multi-factor setpoint · changeover · adaptive preheat│ 30_climate_engine +├────────────────────────────────────────────────────────────────────┤ +│ L1 HOME MODE Home/Away/Night/Eco/Guest/Vacation state machine │ 20_home_mode +├────────────────────────────────────────────────────────────────────┤ +│ L0 SENSES season · daypart · weekend · occupancy · comfort │ 10_context_sensors +│ L0 HELPERS input_*/schedule/timer/utility_meter │ 00_helpers +├────────────────────────────────────────────────────────────────────┤ +│ KNX I/O demo-home.knxproj — 239 group addresses │ (ETS layer) +└────────────────────────────────────────────────────────────────────┘ +``` + +Everything flows through **one source of truth**: `input_select.home_mode`. The senses feed the +state machine; the state machine + senses feed the climate/lighting engines; everything is +metered by the statistics layer. + +--- + +## "Depends on everything" — the input matrix + +| Input (sense) | Climate setpoint | Lighting | Mode machine | Statistics | +|---|:---:|:---:|:---:|:---:| +| **Time of day / daypart** (sun elevation) | ✅ profile | ✅ circadian b/CCT | ✅ night | ✅ tariff | +| **Day of week** (weekday/weekend schedule) | ✅ wake later | — | ✅ wake window | ✅ weekly | +| **Season** (month) + **outdoor temp** | ✅ trim + changeover | ✅ floor at night | ✅ heat/cool | ✅ degree-hours | +| **Presence/occupancy** (per zone, motion+hold) | ✅ setback | ✅ motion lights | ✅ Away/Home | ✅ dwell time | +| **Home mode** (Away/Night/Vacation/Eco) | ✅ deep setback / frost | ✅ all-off / sim | (is the state) | ✅ occupied-h | +| **Window contacts** | ✅ suspend heating | — | — | — | +| **CO₂ / air quality** (trend + threshold) | — | — | ✅ → Eco | ✅ exceedance | +| **Lux / darkness** (threshold) | — | ✅ only when dark | — | — | +| **Temperature slope** (derivative) | ✅ adaptive preheat | — | — | ✅ | + +The climate **target setpoint** (`sensor.living_target_setpoint`) is the centrepiece: a single +trigger-based template that folds **8 inputs** into one number — daypart profile → weekend hold → +occupancy setback → home-mode override → seasonal trim / cooling target → window interlock → +clamp. See [`30_climate_engine.yaml`](packages/30_climate_engine.yaml). + +--- + +## Showcase scenarios (for the demo) + +1. **Winter weekday morning.** `season=winter`, `hvac=Heating`. At ~06:40 the **adaptive pre-heat** + (`sensor.living_preheat_minutes = ΔT / rate × cold-penalty`) starts warming the Living room so + it hits comfort exactly when the work-week schedule opens at 07:00. Lights stay off (sun below + horizon handled by circadian + lux gate). +2. **Summer weekend afternoon.** Changeover flips to **Cooling** (outdoor > 22 °C); setpoint + becomes a cool target; weekend holds comfort later; blinds/scenes available. +3. **Everyone leaves.** No presence 20 min → **Away**: heating drops by the away-setback, every + light/switch turns off, the house meter keeps logging on the off-peak tariff. +4. **Bedtime.** Quiet-hours schedule → **Night**: `script.goodnight` ramps everything down, stair + LED to 8 % @ 2200 K, bedrooms cool for sleep. +5. **Poor air.** CO₂ trend rising + threshold high for 15 min → **Eco** + a ventilation nudge. +6. **Vacation.** Frost-guard setpoints + **presence simulation** randomly toggles rooms in the + evening so the house looks lived-in. + +--- + +## Files + +| File | Layer | Contents | +|---|---|---| +| `packages/00_helpers.yaml` | L0 | input_select/number/boolean, schedules, timers, utility_meter | +| `packages/10_context_sensors.yaml` | L0 | season, daypart, weekend, per-zone occupancy, comfort, statistics/derivative/trend/threshold | +| `packages/20_home_mode.yaml` | L1 | the state machine + seasonal heat/cool changeover | +| `packages/30_climate_engine.yaml` | L2 | KNX climate entities + multi-factor setpoint + pre-heat + interlocks | +| `packages/40_lighting_engine.yaml` | L3 | circadian sensors + adaptive motion/presence + scenes + vacation sim | +| `packages/50_statistics.yaml` | L4 | history_stats, integration→utility_meter degree-hours, KPIs, tariffs | + +## Deploy (in a live HA) + +```yaml +# configuration.yaml +homeassistant: + packages: !include_dir_named demo-home-ha/packages +``` + +Then create the matching KNX entities from the demo project with **nickol-knx-mcp** +(`generate_ha_package`) and reconcile entity names. Two climate zones (Living, Master bedroom) are +shown in full; the same per-zone pattern repeats for all 13 zones. + +> **Notes & honest caveats.** Entity IDs assume the slugified names produced by +> `generate_ha_package`. The two-climate-zone set is illustrative — production would template the +> remaining zones. In a live system, the `input_*`/`schedule`/`utility_meter` helpers are normally +> created via the UI/config-flow; here they are YAML so the whole brain is reviewable in one place. diff --git a/examples/demo-home/ha-brain/packages/00_helpers.yaml b/examples/demo-home/ha-brain/packages/00_helpers.yaml new file mode 100644 index 0000000..da8130d --- /dev/null +++ b/examples/demo-home/ha-brain/packages/00_helpers.yaml @@ -0,0 +1,72 @@ +# ============================================================================= +# Demo House — Layer 0: helpers (native HA constructs, not templates) +# The "knobs and registers" the brain reads/writes. Created here as YAML for the +# showcase; in a live system these would normally be UI/config-flow Helpers. +# ============================================================================= + +# ---- The home state machine (single source of truth for "what mode are we in") +input_select: + home_mode: + name: Home mode + icon: mdi:home-heart + options: [Home, Away, Night, Guest, Eco, Vacation] + initial: Home + + hvac_season_mode: + name: HVAC season changeover + icon: mdi:sun-snowflake-variant + options: [Heating, Cooling, Off, Auto] + initial: Auto + +# ---- Tunables exposed to the user (so logic is not hard-coded) +input_number: + comfort_temp_living: { name: Comfort setpoint · Living, min: 18, max: 25, step: 0.5, unit_of_measurement: "°C", initial: 21.5 } + comfort_temp_bedroom: { name: Comfort setpoint · Bedroom, min: 16, max: 24, step: 0.5, unit_of_measurement: "°C", initial: 20.5 } + eco_setback: { name: Eco setback ΔT, min: 0, max: 5, step: 0.5, unit_of_measurement: "°C", initial: 1.5 } + away_setback: { name: Away setback ΔT, min: 0, max: 6, step: 0.5, unit_of_measurement: "°C", initial: 3.0 } + frost_guard: { name: Frost-guard / vacation, min: 7, max: 16, step: 0.5, unit_of_measurement: "°C", initial: 12.0 } + cooling_changeover: { name: Changeover · cool above, min: 18, max: 28, step: 0.5, unit_of_measurement: "°C", initial: 22.0 } + heating_changeover: { name: Changeover · heat below, min: 8, max: 20, step: 0.5, unit_of_measurement: "°C", initial: 15.0 } + lux_dark_threshold: { name: "Lights: dark below (lux)", min: 5, max: 200, step: 5, unit_of_measurement: "lx", initial: 60 } + co2_high_threshold: { name: "CO2 high (ppm)", min: 600, max: 1600, step: 50, unit_of_measurement: "ppm", initial: 1000 } + preheat_rate_c_per_h: { name: Heating rate model (°C/h), min: 0.5, max: 4, step: 0.1, unit_of_measurement: "°C/h", initial: 1.4 } + outdoor_temp_sim: { name: "Outdoor temp (sim, if no weather)", min: -25, max: 40, step: 0.5, unit_of_measurement: "°C", initial: 8.0 } + +input_boolean: + vacation_presence_sim: { name: Vacation presence simulation, icon: mdi:account-clock } + guest_mode: { name: Guest mode, icon: mdi:account-group } + auto_light_enable: { name: Adaptive lighting enabled, icon: mdi:lightbulb-auto, initial: "on" } + +# ---- Schedules (native) — weekday work hours & quiet hours +schedule: + work_week: + name: Work week (occupied-expected) + monday: [{ from: "07:00:00", to: "22:30:00" }] + tuesday: [{ from: "07:00:00", to: "22:30:00" }] + wednesday: [{ from: "07:00:00", to: "22:30:00" }] + thursday: [{ from: "07:00:00", to: "22:30:00" }] + friday: [{ from: "07:00:00", to: "23:30:00" }] + saturday: [{ from: "08:30:00", to: "23:59:00" }] + sunday: [{ from: "08:30:00", to: "22:30:00" }] + quiet_hours: + name: Quiet / night hours + monday: [{ from: "23:00:00", to: "06:30:00" }] + sunday: [{ from: "23:00:00", to: "06:30:00" }] + +# ---- Per-zone occupancy hold timers (armed by motion, read by occupancy sensor) +timer: + occ_living: { name: Occupancy hold · Living, duration: "00:15:00", restore: true } + occ_bedroom: { name: Occupancy hold · Bedroom, duration: "00:20:00", restore: true } + occ_corridor: { name: Occupancy hold · Corridor, duration: "00:03:00", restore: true } + occ_staircase: { name: Occupancy hold · Staircase, duration: "00:02:00", restore: true } + occ_bath_m: { name: Occupancy hold · Master bath, duration: "00:10:00", restore: true } + +# ---- Energy meters (native utility_meter, multi-cycle + tariffs) +utility_meter: + lighting_daily: { source: sensor.lighting_energy, cycle: daily } + lighting_monthly: { source: sensor.lighting_energy, cycle: monthly } + hvac_daily: { source: sensor.hvac_energy, cycle: daily } + hvac_monthly: { source: sensor.hvac_energy, cycle: monthly } + hvac_seasonal: { source: sensor.hvac_energy, cycle: quarterly } + house_daily: { source: sensor.house_total_energy, cycle: daily, tariffs: [peak, offpeak] } + house_monthly: { source: sensor.house_total_energy, cycle: monthly, tariffs: [peak, offpeak] } diff --git a/examples/demo-home/ha-brain/packages/10_context_sensors.yaml b/examples/demo-home/ha-brain/packages/10_context_sensors.yaml new file mode 100644 index 0000000..1676ecb --- /dev/null +++ b/examples/demo-home/ha-brain/packages/10_context_sensors.yaml @@ -0,0 +1,180 @@ +# ============================================================================= +# Demo House — Layer 0b: context / "senses" +# Trigger-based template sensors for genuinely multi-factor derived context, +# plus native integrations (statistics / derivative / trend / threshold) where +# a built-in does the job better than a template. +# ============================================================================= + +template: + # -------- SEASON (meteorological, by month) -------- + - trigger: + - platform: homeassistant + event: start + - platform: time + at: "00:05:00" + - platform: time_pattern + hours: "/6" + sensor: + - name: Season + unique_id: demo_season + icon: mdi:sun-snowflake + state: > + {% set m = now().month %} + {% if m in [12,1,2] %}winter + {% elif m in [3,4,5] %}spring + {% elif m in [6,7,8] %}summer + {% else %}autumn{% endif %} + + # -------- DAYPART (sun elevation + clock, 8 phases) -------- + - trigger: + - platform: homeassistant + event: start + - platform: state + entity_id: sun.sun + - platform: time_pattern + minutes: "/10" + sensor: + - name: Daypart + unique_id: demo_daypart + icon: mdi:theme-light-dark + state: > + {% set elev = state_attr('sun.sun','elevation') | float(0) %} + {% set rising = is_state('sun.sun','above_horizon') and (state_attr('sun.sun','rising') | default(false)) %} + {% set h = now().hour %} + {% if h < 5 %}deep_night + {% elif elev < -6 and h < 12 %}dawn + {% elif elev < 6 and rising %}morning + {% elif elev >= 30 %}midday + {% elif elev >= 6 %}afternoon + {% elif elev >= -6 and not rising %}dusk + {% elif h < 23 %}evening + {% else %}night{% endif %} + + # -------- DAY TYPE -------- + - trigger: + - platform: homeassistant + event: start + - platform: time + at: "00:01:00" + - platform: time_pattern + hours: "/3" + binary_sensor: + - name: Is weekend + unique_id: demo_is_weekend + state: "{{ now().weekday() >= 5 }}" + + # -------- OUTDOOR TEMPERATURE (real weather if present, else sim knob) -------- + - sensor: + - name: Outdoor temperature + unique_id: demo_outdoor_temp + device_class: temperature + unit_of_measurement: "°C" + state: > + {% set w = state_attr('weather.forecast_home','temperature') %} + {{ w if w is not none else states('input_number.outdoor_temp_sim') }} + availability: "{{ states('input_number.outdoor_temp_sim') not in ['unknown','unavailable'] }}" + + # -------- PER-ZONE OCCUPANCY (presence + motion-hold timer + mode) -------- + # Where the demo has a presence GA we trust it; elsewhere we infer from the + # hold timer (armed by motion automations in 40_lighting_engine). + - binary_sensor: + - name: Living room occupied + unique_id: occ_living + device_class: occupancy + delay_off: "00:10:00" + state: > + {{ is_state('input_select.home_mode','Home') + and (is_state('timer.occ_living','active') + or is_state('binary_sensor.corridor_gf_presence','on') + or states('sensor.daypart') in ['evening','dusk']) }} + - name: Master bedroom occupied + unique_id: occ_bedroom + device_class: occupancy + delay_off: "00:15:00" + state: > + {{ is_state('timer.occ_bedroom','active') + or (is_state('input_select.home_mode','Night') + and is_state('binary_sensor.corridor_uf_presence','off')) }} + - name: Master bath occupied + unique_id: occ_bath_m + device_class: occupancy + delay_off: "00:03:00" + state: "{{ is_state('binary_sensor.master_bath_presence','on') or is_state('timer.occ_bath_m','active') }}" + + # -------- COMFORT INDEX per room (temp + humidity + CO2 → 0..100) -------- + - trigger: + - platform: state + entity_id: + - sensor.living_room_temperature + - sensor.master_bath_humidity + - sensor.master_bedroom_co2 + - platform: time_pattern + minutes: "/5" + sensor: + - name: Living room comfort index + unique_id: comfort_living + unit_of_measurement: "%" + icon: mdi:emoticon-happy-outline + state: > + {% set t = states('sensor.living_room_temperature') | float(21) %} + {% set rh = states('sensor.master_bath_humidity') | float(45) %} + {% set co2 = states('sensor.master_bedroom_co2') | float(600) %} + {# penalise deviation from 22°C / 45% RH / 600 ppm #} + {% set pt = 100 - (((t - 22) | abs) * 9) %} + {% set ph = 100 - (((rh - 45) | abs) * 1.5) %} + {% set pc = 100 - ([co2 - 600, 0] | max) / 12 %} + {{ [[ (pt*0.5 + ph*0.2 + pc*0.3), 0] | max, 100] | min | round(0) }} + attributes: + temperature: "{{ states('sensor.living_room_temperature') }}" + co2: "{{ states('sensor.master_bedroom_co2') }}" + +# -------- NATIVE integrations (better than templates here) -------- +sensor: + # rolling 24h climate statistics + - platform: statistics + name: Living temp 24h + entity_id: sensor.living_room_temperature + state_characteristic: mean + max_age: { hours: 24 } + - platform: statistics + name: Bedroom CO2 peak 24h + entity_id: sensor.master_bedroom_co2 + state_characteristic: value_max + max_age: { hours: 24 } + # temperature rate-of-change → drives adaptive pre-heat + - platform: derivative + name: Living temp slope + source: sensor.living_room_temperature + unit_time: h + time_window: "00:30:00" + # average house temperature across zones + - platform: min_max + name: House mean temperature + type: mean + entity_ids: + - sensor.living_room_temperature + - sensor.master_bedroom_temperature + - sensor.kids_room_1_temperature + - sensor.kids_room_2_temperature + +binary_sensor: + # CO2 rising fast (ventilation hint) + - platform: trend + sensors: + bedroom_co2_rising: + entity_id: sensor.master_bedroom_co2 + sample_duration: 900 + max_samples: 10 + min_gradient: 0.15 + device_class: problem + +# threshold helpers (built-in hysteresis) +threshold: + - name: CO2 high + entity_id: sensor.master_bedroom_co2 + upper: 1000 + hysteresis: 75 + - name: Living too dark + entity_id: sensor.living_room_brightness + lower: 60 + hysteresis: 15 diff --git a/examples/demo-home/ha-brain/packages/20_home_mode.yaml b/examples/demo-home/ha-brain/packages/20_home_mode.yaml new file mode 100644 index 0000000..3870e1b --- /dev/null +++ b/examples/demo-home/ha-brain/packages/20_home_mode.yaml @@ -0,0 +1,165 @@ +# ============================================================================= +# Demo House — Layer 1: the home-mode state machine (the "brain stem") +# Everything downstream (climate, light, stats) reads input_select.home_mode. +# Transitions are driven by presence + schedule + time + CO2 + manual. +# ============================================================================= + +# Aggregate presence: any motion/presence GA in the house (native group) +binary_sensor: + - platform: group + name: Anyone present + device_class: occupancy + entities: + - binary_sensor.corridor_gf_presence + - binary_sensor.corridor_uf_presence + - binary_sensor.staircase_presence + - binary_sensor.master_bath_presence + - binary_sensor.kids_bath_1_presence + - binary_sensor.kids_bath_2_presence + - binary_sensor.guest_wc_presence + - binary_sensor.laundry_presence + +automation: + # ---- HOME → AWAY : no presence for 20 min (unless on vacation) ---- + - id: mode_to_away + alias: "Mode · go Away when empty" + mode: restart + trigger: + - platform: state + entity_id: binary_sensor.anyone_present + to: "off" + for: "00:20:00" + condition: + - condition: not + conditions: + - condition: state + entity_id: input_select.home_mode + state: [Away, Vacation] + action: + - service: input_select.select_option + target: { entity_id: input_select.home_mode } + data: { option: Away } + + # ---- AWAY/ECO → HOME : presence returns ---- + - id: mode_to_home + alias: "Mode · return Home on presence" + mode: single + trigger: + - platform: state + entity_id: binary_sensor.anyone_present + to: "on" + condition: + - condition: state + entity_id: input_select.home_mode + state: [Away, Eco] + action: + - service: input_select.select_option + target: { entity_id: input_select.home_mode } + data: { option: "{{ 'Guest' if is_state('input_boolean.guest_mode','on') else 'Home' }}" } + + # ---- HOME → NIGHT : quiet hours begin and we are home ---- + - id: mode_to_night + alias: "Mode · go Night at quiet hours" + mode: single + trigger: + - platform: state + entity_id: schedule.quiet_hours + to: "on" + condition: + - condition: state + entity_id: input_select.home_mode + state: [Home, Guest, Eco] + action: + - service: input_select.select_option + target: { entity_id: input_select.home_mode } + data: { option: Night } + + # ---- NIGHT → HOME : morning wake (work-week window opens + movement) ---- + - id: mode_night_to_home + alias: "Mode · wake from Night" + mode: single + trigger: + - platform: state + entity_id: schedule.work_week + to: "on" + - platform: state + entity_id: binary_sensor.corridor_uf_presence + to: "on" + condition: + - condition: state + entity_id: input_select.home_mode + state: Night + - condition: time + after: "05:30:00" + action: + - service: input_select.select_option + target: { entity_id: input_select.home_mode } + data: { option: Home } + + # ---- → ECO : sustained high CO2 while present (air-quality driven) ---- + - id: mode_to_eco_air + alias: "Mode · Eco on poor air" + mode: single + trigger: + - platform: state + entity_id: binary_sensor.co2_high + to: "on" + for: "00:15:00" + condition: + - condition: state + entity_id: input_select.home_mode + state: Home + action: + - service: input_select.select_option + target: { entity_id: input_select.home_mode } + data: { option: Eco } + - service: notify.persistent_notification + data: + title: Air quality + message: "CO₂ high — switched to Eco; consider ventilating." + + # ---- VACATION mirror from boolean ---- + - id: mode_vacation_sync + alias: "Mode · Vacation from toggle" + mode: single + trigger: + - platform: state + entity_id: input_boolean.vacation_presence_sim + to: "on" + action: + - service: input_select.select_option + target: { entity_id: input_select.home_mode } + data: { option: Vacation } + + # ---- SEASON CHANGEOVER : decide heat vs cool by season + outdoor temp ---- + - id: hvac_changeover + alias: "HVAC · seasonal heat/cool changeover" + mode: restart + trigger: + - platform: state + entity_id: [sensor.season, sensor.outdoor_temperature] + - platform: homeassistant + event: start + - platform: time_pattern + minutes: "/30" + action: + - variables: + season: "{{ states('sensor.season') }}" + outdoor: "{{ states('sensor.outdoor_temperature') | float(10) }}" + cool_above: "{{ states('input_number.cooling_changeover') | float(22) }}" + heat_below: "{{ states('input_number.heating_changeover') | float(15) }}" + - choose: + - conditions: "{{ season == 'winter' or outdoor < heat_below }}" + sequence: + - service: input_select.select_option + target: { entity_id: input_select.hvac_season_mode } + data: { option: Heating } + - conditions: "{{ season == 'summer' or outdoor > cool_above }}" + sequence: + - service: input_select.select_option + target: { entity_id: input_select.hvac_season_mode } + data: { option: Cooling } + default: + - service: input_select.select_option + target: { entity_id: input_select.hvac_season_mode } + data: { option: Off } diff --git a/examples/demo-home/ha-brain/packages/30_climate_engine.yaml b/examples/demo-home/ha-brain/packages/30_climate_engine.yaml new file mode 100644 index 0000000..bfee391 --- /dev/null +++ b/examples/demo-home/ha-brain/packages/30_climate_engine.yaml @@ -0,0 +1,204 @@ +# ============================================================================= +# Demo House — Layer 2: CLIMATE ENGINE (the showpiece) +# KNX climate entities (mapped to demo .knxproj GAs) + a multi-factor target +# setpoint that depends on: season · daypart · day-type · per-zone occupancy · +# home-mode · window contacts · outdoor temp · CO2 · comfort. Plus adaptive +# pre-heat (rate model) and window/air interlocks. Two zones shown in full; +# the same pattern repeats for all 13 zones. +# ============================================================================= + +knx: + climate: + - name: "Living room HVAC" + temperature_address: "4/0/1" # current room temp (9.001) + target_temperature_address: "3/0/1" # setpoint command (9.001) + target_temperature_state_address: "3/4/1" # setpoint status + operation_mode_address: "3/1/1" # HVAC mode (20.102) + operation_mode_state_address: "3/1/1" + command_value_state_address: "3/6/1" # floor valve % (5.001) + min_temp: 12 + max_temp: 28 + - name: "Master bedroom HVAC" + temperature_address: "4/0/3" + target_temperature_address: "3/0/3" + target_temperature_state_address: "3/4/3" + operation_mode_address: "3/1/3" + operation_mode_state_address: "3/1/3" + command_value_state_address: "3/6/3" + min_temp: 12 + max_temp: 28 + +template: + # ----------------------------------------------------------------------- # + # THE multi-factor target setpoint. Reads "everything". # + # ----------------------------------------------------------------------- # + - trigger: + - platform: state + entity_id: + - input_select.home_mode + - input_select.hvac_season_mode + - sensor.daypart + - sensor.season + - binary_sensor.is_weekend + - binary_sensor.living_room_occupied + - binary_sensor.living_room_window_contact + - sensor.outdoor_temperature + - platform: time_pattern + minutes: "/5" + - platform: homeassistant + event: start + sensor: + - name: Living target setpoint + unique_id: living_target_setpoint + device_class: temperature + unit_of_measurement: "°C" + state: > + {% set comfort = states('input_number.comfort_temp_living') | float(21.5) %} + {% set mode = states('input_select.home_mode') %} + {% set hvac = states('input_select.hvac_season_mode') %} + {% set season = states('sensor.season') %} + {% set dp = states('sensor.daypart') %} + {% set weekend = is_state('binary_sensor.is_weekend','on') %} + {% set occ = is_state('binary_sensor.living_room_occupied','on') %} + {% set window = is_state('binary_sensor.living_room_window_contact','on') %} + {% set eco = states('input_number.eco_setback') | float(1.5) %} + {% set away = states('input_number.away_setback') | float(3.0) %} + {% set frost = states('input_number.frost_guard') | float(12.0) %} + + {# --- 1. daypart base profile (heating reference) --- #} + {% set prof = { + 'deep_night': comfort - 3.0, 'dawn': comfort - 1.5, 'morning': comfort, + 'midday': comfort - 0.5, 'afternoon': comfort - 0.5, 'evening': comfort + 0.5, + 'dusk': comfort, 'night': comfort - 2.5 } %} + {% set t = prof.get(dp, comfort) %} + + {# --- 2. weekend holds morning comfort longer --- #} + {% if weekend and dp in ['morning','midday'] %}{% set t = comfort + 0.3 %}{% endif %} + + {# --- 3. occupancy setback --- #} + {% if not occ %}{% set t = t - eco %}{% endif %} + + {# --- 4. home-mode overrides --- #} + {% if mode == 'Away' %}{% set t = [t - away, 16.5] | max %} + {% elif mode == 'Vacation' %}{% set t = frost %} + {% elif mode == 'Eco' %}{% set t = t - eco %} + {% elif mode == 'Night' %}{% set t = t - 0.5 %}{% endif %} + + {# --- 5. seasonal trim (winter slightly warmer, summer becomes cool target) --- #} + {% if season == 'winter' %}{% set t = t + 0.3 %}{% endif %} + {% if hvac == 'Cooling' %}{% set t = [comfort + 2.0, 24.0] | max %}{% endif %} + + {# --- 6. window open → suspend (frost guard) --- #} + {% if window %}{% set t = frost %}{% endif %} + + {{ [[t, 12.0] | max, 28.0] | min | round(1) }} + attributes: + basis: "{{ states('sensor.daypart') }} / {{ states('input_select.home_mode') }} / {{ states('sensor.season') }}" + cooling: "{{ is_state('input_select.hvac_season_mode','Cooling') }}" + + - name: Bedroom target setpoint + unique_id: bedroom_target_setpoint + device_class: temperature + unit_of_measurement: "°C" + state: > + {% set comfort = states('input_number.comfort_temp_bedroom') | float(20.5) %} + {% set mode = states('input_select.home_mode') %} + {% set dp = states('sensor.daypart') %} + {% set occ = is_state('binary_sensor.master_bedroom_occupied','on') %} + {% set frost = states('input_number.frost_guard') | float(12.0) %} + {# bedrooms: cool for sleep, warm for wake #} + {% set prof = { 'deep_night': comfort - 2.0, 'dawn': comfort + 0.5, 'morning': comfort + 0.5, + 'night': comfort - 1.5 } %} + {% set t = prof.get(dp, comfort - 1.0) %} + {% if mode == 'Vacation' %}{% set t = frost %} + {% elif mode == 'Away' %}{% set t = comfort - 3.0 %} + {% elif mode == 'Night' and occ %}{% set t = comfort - 1.0 %}{% endif %} + {{ [[t, 12.0] | max, 26.0] | min | round(1) }} + + # ----------------------------------------------------------------------- # + # Adaptive pre-heat lead time = ΔT / heating-rate, stretched when cold. # + # ----------------------------------------------------------------------- # + - sensor: + - name: Living preheat minutes + unique_id: living_preheat_minutes + unit_of_measurement: min + icon: mdi:fan-clock + state: > + {% set cur = states('sensor.living_room_temperature') | float(20) %} + {% set tgt = states('sensor.living_target_setpoint') | float(21) %} + {% set rate = states('input_number.preheat_rate_c_per_h') | float(1.4) %} + {% set outdoor = states('sensor.outdoor_temperature') | float(8) %} + {% set delta = [tgt - cur, 0] | max %} + {# colder outside ⇒ slower warm-up ⇒ longer lead #} + {% set penalty = [1 + (15 - outdoor) / 40, 1] | max %} + {{ (delta / rate * 60 * penalty) | round(0) }} + +automation: + # ---- Push computed setpoint to the KNX climate entity on any change ---- + - id: climate_apply_living + alias: "Climate · apply Living setpoint" + mode: restart + trigger: + - platform: state + entity_id: sensor.living_target_setpoint + condition: + - condition: template + value_template: "{{ states('sensor.living_target_setpoint') | float(0) > 0 }}" + action: + - service: climate.set_temperature + target: { entity_id: climate.living_room_hvac } + data: + temperature: "{{ states('sensor.living_target_setpoint') | float }}" + + - id: climate_apply_bedroom + alias: "Climate · apply Bedroom setpoint" + mode: restart + trigger: + - platform: state + entity_id: sensor.bedroom_target_setpoint + action: + - service: climate.set_temperature + target: { entity_id: climate.master_bedroom_hvac } + data: + temperature: "{{ states('sensor.bedroom_target_setpoint') | float }}" + + # ---- Window interlock: open window pauses heating immediately ---- + - id: climate_window_interlock + alias: "Climate · window-open interlock" + mode: parallel + trigger: + - platform: state + entity_id: binary_sensor.living_room_window_contact + to: "on" + action: + - service: climate.set_temperature + target: { entity_id: climate.living_room_hvac } + data: { temperature: "{{ states('input_number.frost_guard') | float(12) }}" } + - service: notify.persistent_notification + data: { message: "Living window open — heating paused." } + + # ---- Adaptive pre-heat: warm up before the work-week window opens ---- + - id: climate_preheat + alias: "Climate · adaptive pre-heat" + mode: single + trigger: + - platform: time_pattern + minutes: "/10" + condition: + - condition: state + entity_id: input_select.hvac_season_mode + state: Heating + - condition: state + entity_id: input_select.home_mode + state: [Night, Away] + # within the lead window before 07:00 occupancy + - condition: template + value_template: > + {% set lead = states('sensor.living_preheat_minutes') | float(0) %} + {% set wake = today_at('07:00') %} + {% set start = wake - timedelta(minutes=lead) %} + {{ start <= now() < wake and lead > 0 }} + action: + - service: climate.set_temperature + target: { entity_id: climate.living_room_hvac } + data: { temperature: "{{ states('input_number.comfort_temp_living') | float(21.5) }}" } diff --git a/examples/demo-home/ha-brain/packages/40_lighting_engine.yaml b/examples/demo-home/ha-brain/packages/40_lighting_engine.yaml new file mode 100644 index 0000000..08ad245 --- /dev/null +++ b/examples/demo-home/ha-brain/packages/40_lighting_engine.yaml @@ -0,0 +1,255 @@ +# ============================================================================= +# Demo House — Layer 3: LIGHTING ENGINE +# Circadian (human-centric) brightness + colour temperature, adaptive motion/ +# presence lighting (only when dark / allowed by mode), scene orchestration and +# vacation presence simulation. Uses color_temp_kelvin (mireds removed in 2026.3). +# ============================================================================= + +template: + - trigger: + - platform: state + entity_id: [sun.sun, sensor.daypart, input_select.home_mode] + - platform: time_pattern + minutes: "/10" + - platform: homeassistant + event: start + sensor: + # Brightness 1..100 %, follows the sun, floors low at night. + - name: Circadian brightness + unique_id: circadian_brightness + unit_of_measurement: "%" + icon: mdi:brightness-percent + state: > + {% set elev = state_attr('sun.sun','elevation') | float(0) %} + {% set mode = states('input_select.home_mode') %} + {% if mode in ['Night'] %}{{ 8 }} + {% elif mode == 'Vacation' %}{{ 0 }} + {% else %} + {% set b = 100 if elev >= 25 else (40 + elev * 2.4) %} + {{ [[b, 12] | max, 100] | min | round(0) }} + {% endif %} + # Colour temperature: warm at night (2200K) → cool at midday (5000K). + - name: Circadian color temp + unique_id: circadian_color_temp_kelvin + unit_of_measurement: K + icon: mdi:temperature-kelvin + state: > + {% set elev = state_attr('sun.sun','elevation') | float(0) %} + {% set dp = states('sensor.daypart') %} + {% if dp in ['deep_night','night'] %}{{ 2200 }} + {% elif dp in ['evening','dusk'] %}{{ 2700 }} + {% else %}{{ [[2700 + elev * 80, 2700] | max, 5000] | min | round(0) }}{% endif %} + +script: + # Morning ramp: simulate a sunrise on bedroom + corridor over ~10 min. + morning_ramp: + alias: "Scene · Morning ramp" + mode: restart + sequence: + - repeat: + count: 10 + sequence: + - service: light.turn_on + target: { entity_id: light.master_bedroom_ceiling } + data: + brightness_pct: "{{ repeat.index * 10 }}" + color_temp_kelvin: "{{ 2200 + repeat.index * 200 }}" + - delay: "00:01:00" + + goodnight: + alias: "Scene · Goodnight" + mode: single + sequence: + - service: light.turn_off + target: + entity_id: + - light.living_room_cove + - light.living_room_rgbw_accent_wall + - light.kitchen_island_pendants + - service: switch.turn_off + target: { entity_id: switch.living_room_ceiling_spots } + - delay: "00:00:05" + - service: light.turn_on + target: { entity_id: light.staircase_led_steps } + data: { brightness_pct: 10, color_temp_kelvin: 2200 } + - service: input_select.select_option + target: { entity_id: input_select.home_mode } + data: { option: Night } + + movie_mode: + alias: "Scene · Movie (Living)" + mode: single + sequence: + - service: switch.turn_off + target: { entity_id: switch.living_room_ceiling_spots } + - service: light.turn_on + target: { entity_id: light.living_room_cove } + data: { brightness_pct: 15, color_temp_kelvin: 2300 } + - service: light.turn_on + target: { entity_id: light.living_room_rgbw_accent_wall } + data: { brightness_pct: 40, rgbw_color: [60, 10, 120, 0] } + - service: cover.set_cover_position + target: { entity_id: cover.living_room_window_blind } + data: { position: 0 } + +automation: + # ---- Adaptive motion lights (corridors) — only when dark; dim at night ---- + - id: light_corridor_gf + alias: "Light · Corridor GF (motion+adaptive)" + mode: restart + trigger: + - platform: state + entity_id: binary_sensor.corridor_gf_presence + to: "on" + condition: + - condition: state + entity_id: input_boolean.auto_light_enable + state: "on" + - condition: or + conditions: + - condition: state + entity_id: binary_sensor.living_too_dark + state: "on" + - condition: state + entity_id: input_select.home_mode + state: Night + action: + - service: timer.start + target: { entity_id: timer.occ_corridor } + - service: switch.turn_on + target: { entity_id: switch.corridor_gf_ceiling } + - id: light_corridor_gf_off + alias: "Light · Corridor GF off after hold" + mode: single + trigger: + - platform: event + event_type: timer.finished + event_data: { entity_id: timer.occ_corridor } + action: + - service: switch.turn_off + target: { entity_id: switch.corridor_gf_ceiling } + + # ---- Staircase: adaptive dimmable, very dim at night ---- + - id: light_staircase + alias: "Light · Staircase (motion+adaptive)" + mode: restart + trigger: + - platform: state + entity_id: binary_sensor.staircase_presence + to: "on" + condition: + - condition: state + entity_id: input_boolean.auto_light_enable + state: "on" + action: + - service: timer.start + target: { entity_id: timer.occ_staircase } + - service: light.turn_on + target: { entity_id: light.staircase_led_steps } + data: + brightness_pct: > + {{ 8 if is_state('input_select.home_mode','Night') + else states('sensor.circadian_brightness') | int(60) }} + color_temp_kelvin: "{{ states('sensor.circadian_color_temp_kelvin') | int(2700) }}" + - id: light_staircase_off + alias: "Light · Staircase off after hold" + mode: single + trigger: + - platform: event + event_type: timer.finished + event_data: { entity_id: timer.occ_staircase } + action: + - service: light.turn_off + target: { entity_id: light.staircase_led_steps } + + # ---- Master bath: presence light, fan-free, off after hold ---- + - id: light_bath_master + alias: "Light · Master bath (presence)" + mode: restart + trigger: + - platform: state + entity_id: binary_sensor.master_bath_presence + to: "on" + action: + - service: timer.start + target: { entity_id: timer.occ_bath_m } + - service: switch.turn_on + target: { entity_id: switch.master_bath_ceiling } + - id: light_bath_master_off + alias: "Light · Master bath off" + mode: single + trigger: + - platform: event + event_type: timer.finished + event_data: { entity_id: timer.occ_bath_m } + action: + - service: switch.turn_off + target: { entity_id: switch.master_bath_ceiling } + + # ---- Living room: when occupied in the evening, set cove to circadian ---- + - id: light_living_circadian + alias: "Light · Living circadian when occupied" + mode: restart + trigger: + - platform: state + entity_id: binary_sensor.living_room_occupied + to: "on" + - platform: state + entity_id: sensor.circadian_brightness + condition: + - condition: state + entity_id: binary_sensor.living_room_occupied + state: "on" + - condition: state + entity_id: input_select.home_mode + state: [Home, Guest] + - condition: state + entity_id: binary_sensor.living_too_dark + state: "on" + action: + - service: light.turn_on + target: { entity_id: light.living_room_cove } + data: + brightness_pct: "{{ states('sensor.circadian_brightness') | int(60) }}" + + # ---- Whole-home reactions to mode changes ---- + - id: light_mode_reactions + alias: "Light · react to home mode" + mode: queued + trigger: + - platform: state + entity_id: input_select.home_mode + action: + - choose: + - conditions: "{{ trigger.to_state.state == 'Away' }}" + sequence: + - service: light.turn_off + target: { entity_id: all } + - service: switch.turn_off + target: { entity_id: all } + - conditions: "{{ trigger.to_state.state == 'Night' }}" + sequence: + - service: script.goodnight + + # ---- Vacation presence simulation: random rooms on/off in the evening ---- + - id: light_vacation_sim + alias: "Light · Vacation simulation" + mode: single + trigger: + - platform: time_pattern + minutes: "/20" + condition: + - condition: state + entity_id: input_select.home_mode + state: Vacation + - condition: state + entity_id: sensor.daypart + state: [evening, dusk, night] + action: + - service: "{{ ['switch.turn_on','switch.turn_off'] | random }}" + target: + entity_id: > + {{ [ 'switch.living_room_ceiling_spots', + 'switch.kitchen_main_ceiling', + 'light.master_bedroom_ceiling', + 'switch.corridor_gf_ceiling' ] | random }} diff --git a/examples/demo-home/ha-brain/packages/50_statistics.yaml b/examples/demo-home/ha-brain/packages/50_statistics.yaml new file mode 100644 index 0000000..02bf572 --- /dev/null +++ b/examples/demo-home/ha-brain/packages/50_statistics.yaml @@ -0,0 +1,135 @@ +# ============================================================================= +# Demo House — Layer 4: STATISTICS & ANALYTICS +# utility_meter (in 00_helpers) + history_stats runtime counters + derived KPIs. +# "Collect statistics depending on everything": per-room runtime, presence dwell, +# heating load, comfort, energy-per-occupied-hour, seasonal degree-hours. +# ============================================================================= + +# Heating-active flags (valve % > 5) feed runtime stats +template: + - binary_sensor: + - name: Living heating active + unique_id: living_heating_active + device_class: heat + state: "{{ states('sensor.living_room_floor_valve_status') | float(0) > 5 }}" + - name: Bedroom heating active + unique_id: bedroom_heating_active + device_class: heat + state: "{{ states('sensor.master_bedroom_floor_valve_status') | float(0) > 5 }}" + + # Instantaneous heating demand (°C) — integrated natively below into degree-hours + - sensor: + - name: Heating demand + unique_id: heating_demand + unit_of_measurement: "°C" + icon: mdi:thermometer-lines + state: > + {% set outdoor = states('sensor.outdoor_temperature') | float(8) %} + {{ [18 - outdoor, 0] | max if is_state('input_select.hvac_season_mode','Heating') else 0 }} + + # ---- Derived KPIs ---- + - trigger: + - platform: time_pattern + minutes: "/15" + - platform: homeassistant + event: start + sensor: + # Energy efficiency: HVAC kWh today per occupied-hour today + - name: Energy per occupied hour + unique_id: energy_per_occupied_hour + unit_of_measurement: "kWh/h" + icon: mdi:lightning-bolt-outline + state: > + {% set kwh = states('sensor.hvac_daily') | float(0) %} + {% set occh = states('sensor.house_occupied_hours_today') | float(0) %} + {{ (kwh / occh) | round(3) if occh > 0.2 else 0 }} + + # House comfort score = mean of room comfort indices + - name: House comfort score + unique_id: house_comfort_score + unit_of_measurement: "%" + icon: mdi:gauge + state: > + {{ states('sensor.living_room_comfort_index') | float(70) | round(0) }} + +# ---- Native integration of heating demand → degree-hours, reset daily ---- +sensor: + - platform: integration + name: Heating degree hours raw + source: sensor.heating_demand + unit_time: h + method: left + + # ---- Runtime counters (native history_stats) ---- + - platform: history_stats + name: Living lights on today + entity_id: light.living_room_cove + state: "on" + type: time + start: "{{ today_at('00:00') }}" + end: "{{ now() }}" + + - platform: history_stats + name: Living heating hours today + entity_id: binary_sensor.living_heating_active + state: "on" + type: time + start: "{{ today_at('00:00') }}" + end: "{{ now() }}" + + - platform: history_stats + name: Bedroom heating hours today + entity_id: binary_sensor.bedroom_heating_active + state: "on" + type: time + start: "{{ today_at('00:00') }}" + end: "{{ now() }}" + + - platform: history_stats + name: House occupied hours today + entity_id: binary_sensor.anyone_present + state: "on" + type: time + start: "{{ today_at('00:00') }}" + end: "{{ now() }}" + + - platform: history_stats + name: Master bath usage count today + entity_id: binary_sensor.master_bath_presence + state: "on" + type: count + start: "{{ today_at('00:00') }}" + end: "{{ now() }}" + + # weekly heating runtime (trend across the week) + - platform: history_stats + name: Living heating hours this week + entity_id: binary_sensor.living_heating_active + state: "on" + type: time + start: "{{ now().replace(hour=0, minute=0, second=0) - timedelta(days=now().weekday()) }}" + end: "{{ now() }}" + +automation: + # ---- Tariff switching for the house meter (peak 07–23, offpeak otherwise) ---- + - id: stats_tariff_peak + alias: "Stats · tariff → peak" + mode: single + trigger: [{ platform: time, at: "07:00:00" }] + action: + - service: utility_meter.select_tariff + target: { entity_id: [select.house_daily, select.house_monthly] } + data: { tariff: peak } + - id: stats_tariff_offpeak + alias: "Stats · tariff → offpeak" + mode: single + trigger: [{ platform: time, at: "23:00:00" }] + action: + - service: utility_meter.select_tariff + target: { entity_id: [select.house_daily, select.house_monthly] } + data: { tariff: offpeak } + +# utility_meter gives the degree-hours a clean daily/seasonal reset (native) +utility_meter: + heating_degree_hours_daily: { source: sensor.heating_degree_hours_raw, cycle: daily } + heating_degree_hours_seasonal: { source: sensor.heating_degree_hours_raw, cycle: quarterly }