diff --git a/_data/navigation.yml b/_data/navigation.yml index c65976863..b4de14d04 100644 --- a/_data/navigation.yml +++ b/_data/navigation.yml @@ -294,16 +294,24 @@ home-automation: # url: /projects/home-automation/knx - title: "KNX Button Selection" url: /projects/home-automation/knx-button-selection - - title: "Raspberry Pi Home Assistant Setup" - url: /projects/home-automation/setup - children: - - title: "Temperature and Humidity" - url: /projects/home-automation/esp8266-dht # Planned pages, enable once they exist: # - title: "Shelly Light Control" # url: /home-automation/shelly-light # - title: "Shelly Rolling Shutter Control" # url: /home-automation/shelly-blinds + - title: "KNX + Home Assistant" + children: + - title: "From ETS to Home Assistant" + url: /projects/home-automation/ets-to-home-assistant + - title: "Velux Windows with KNX Buttons" + url: /projects/home-automation/velux-klf200-knx + # Archived setups, kept for their readers. + - title: "Older projects" + children: + - title: "Raspberry Pi 4 Setup (2022)" + url: /projects/home-automation/setup + - title: "Temperature and Humidity (2019)" + url: /projects/home-automation/esp8266-dht # Cpp books pppucpp: diff --git a/assets/collections/home-automation/ets-to-home-assistant/dashboard-floor.png b/assets/collections/home-automation/ets-to-home-assistant/dashboard-floor.png new file mode 100644 index 000000000..46b4cae4f Binary files /dev/null and b/assets/collections/home-automation/ets-to-home-assistant/dashboard-floor.png differ diff --git a/assets/collections/home-automation/ets-to-home-assistant/dashboard-rooms.png b/assets/collections/home-automation/ets-to-home-assistant/dashboard-rooms.png new file mode 100644 index 000000000..9bb20eb50 Binary files /dev/null and b/assets/collections/home-automation/ets-to-home-assistant/dashboard-rooms.png differ diff --git a/assets/collections/home-automation/ets-to-home-assistant/dashboard-start.png b/assets/collections/home-automation/ets-to-home-assistant/dashboard-start.png new file mode 100644 index 000000000..41bf807a6 Binary files /dev/null and b/assets/collections/home-automation/ets-to-home-assistant/dashboard-start.png differ diff --git a/assets/collections/home-automation/ets-to-home-assistant/two-responders.svg b/assets/collections/home-automation/ets-to-home-assistant/two-responders.svg new file mode 100644 index 000000000..8d34b1b6b --- /dev/null +++ b/assets/collections/home-automation/ets-to-home-assistant/two-responders.svg @@ -0,0 +1,61 @@ + + Two devices answer the same read + Home Assistant sends a GroupValueRead on group address 22/0/1. The weather station answers first with the current value, 1 lx. The visualization server answers 33 milliseconds later with its stale copy, 23 lx. Home Assistant keeps the last answer and shows 23 lx. + + + + + + + + + + + Home + Assistant + 2.1.250 · asks + + + Weather + station + 2.1.30 · K L Ü + + + Visualization + server + 2.1.20 · K L S A + + + + + + + + + + + ① GroupValueRead 22/0/1 + + + every device on the address hears it + + + ② 1 lx · +26 ms + + + shows 1 lx + + + ③ 23 lx, stale · +59 ms + + + shows 23 lx + + + + + The last response wins: Home Assistant + shows 23 lx, although it is dark. + Fix: clear L on the visualization server's object. + + diff --git a/assets/collections/home-automation/velux-klf200-knx/dashboard-roof-windows.png b/assets/collections/home-automation/velux-klf200-knx/dashboard-roof-windows.png new file mode 100644 index 000000000..cfe0f5668 Binary files /dev/null and b/assets/collections/home-automation/velux-klf200-knx/dashboard-roof-windows.png differ diff --git a/assets/collections/home-automation/velux-klf200-knx/dashboard-screens-airing.png b/assets/collections/home-automation/velux-klf200-knx/dashboard-screens-airing.png new file mode 100644 index 000000000..bb2e6903a Binary files /dev/null and b/assets/collections/home-automation/velux-klf200-knx/dashboard-screens-airing.png differ diff --git a/collections/_home-automation/2022-07-23-home-assistant-setup.md b/collections/_home-automation/2022-07-23-home-assistant-setup.md index 41e29be84..e9ece2072 100644 --- a/collections/_home-automation/2022-07-23-home-assistant-setup.md +++ b/collections/_home-automation/2022-07-23-home-assistant-setup.md @@ -1,6 +1,6 @@ --- layout: single -title: "Home Automation - Raspberry Pi Setup" +title: "Raspberry Pi 4 Home Assistant Setup (2022)" permalink: /projects/home-automation/setup/ excerpt: "Setup Home Assistant on an Raspberry Pi with Raspberry Pi OS running on an SSD." date: 2022-07-23 10:00:35 +0100 @@ -12,15 +12,16 @@ toc: true classes: wide # toc_label: "Unscented Kalman Filter" header: - teaser: /assets/collections/home-automation/components/argon-m-2.jpg - overlay_image: /assets/collections/home-automation/components/argon-m-2.jpg + teaser: /assets/collections/home-automation/components/argon-one-m-2.jpg + overlay_image: /assets/collections/home-automation/components/argon-one-m-2.jpg overlay_filter: 0.5 -redirect_from: - - /projects/home-automation/ sidebar: nav: "home-automation" --- +**Archived (2022):** this was my first Home Assistant setup. Home Assistant now runs on a mini PC under Proxmox; see [From ETS to Home Assistant](/projects/home-automation/ets-to-home-assistant/) and [Velux Roof Windows with KNX Buttons](/projects/home-automation/velux-klf200-knx/). The steps below are kept as they were and may be outdated. +{: .notice--info} + This page explains how to setup a Raspberry Pi 4 with 8GB and an SSD to run [Home Assistant](https://www.home-assistant.io/). The case I use for testing is the [Argon One m.2](https://amzn.to/3PRzwxW). Later the Home Assistant setup might be moved to another place. @@ -128,10 +129,7 @@ These instructions will set the Raspberry Pi 4 / 400 to look for a USB boot devi 1. The fan and power button can only work with the driver. You can directly refer to the manual which comes with the case. -
- -
FAN jumper settings (source Waveshare)
-
+ The fan jumper settings are shown in [Waveshare's wiki for the case](https://www.waveshare.com/wiki/PI4-CASE-ARGON-ONE-M.2). 2. Open terminal of Raspberry Pi, install the driver by the following command @@ -192,59 +190,15 @@ The latest Home Assistant 64 bit can also be installed with Raspberry Pi Imager: 1. Connect the bottom of the Argon One m.2 case that contains the m.2 connector board to a PC that has Raspberry Pi Imager installed 2. Inside Raspberry Pi Imager choose OS 3. Navigate to "Other specific-purpose OS" -4. Select "Home assistans and home automation" +4. Select "Home assistants and home automation" 5. Home Assistant 6. Home Assistant OS 8.2 (RPi 4/400) - -
- -
Write Home Assistant to the SSD
-
- 7. Click write and confirm that the data on the SSD will be erased - -
- -
Write Home Assistant to the SSD
-
- 8. This will prepare the data, write and verify the flashing process - -
- -
Write Home Assistant to the SSD
-
- -
- -
Write Home Assistant to the SSD
-
- -
- -
Write Home Assistant to the SSD
-
- -
- -
Write Home Assistant to the SSD
-
- -7. Cli for the writing process to complete - -
- -
Home Assistant write complete
-
- -8. Connect the Argon One m.2 bottom again with the Raspberry Pi and boot it up. -9. Wait until the Home Assistant boot screen appears -10. Access Home Assistant with the browser on you development PC - -
- -
Write Home Assistant to the SSD
-
+9. Wait for the writing process to complete +10. Connect the Argon One m.2 bottom again with the Raspberry Pi and boot it up. +11. Wait until the Home Assistant boot screen appears +12. Access Home Assistant with the browser on your development PC ## References diff --git a/collections/_home-automation/2023-02-08-voltus.md b/collections/_home-automation/2023-02-08-voltus.md index 99e5c5b0c..03ab859de 100644 --- a/collections/_home-automation/2023-02-08-voltus.md +++ b/collections/_home-automation/2023-02-08-voltus.md @@ -15,8 +15,6 @@ header: teaser: /assets/collections/home-automation/voltus/voltus.svg overlay_image: /assets/collections/home-automation/voltus/voltus.svg overlay_filter: 0.5 -redirect_from: - - /projects/home-automation/ sidebar: nav: "home-automation" --- diff --git a/collections/_home-automation/2023-11-01-button-selection.md b/collections/_home-automation/2023-11-01-button-selection.md index ca9f26c1c..0fc6ee3c0 100644 --- a/collections/_home-automation/2023-11-01-button-selection.md +++ b/collections/_home-automation/2023-11-01-button-selection.md @@ -15,8 +15,6 @@ header: teaser: /assets/collections/home-automation/voltus/voltus.svg overlay_image: /assets/collections/home-automation/voltus/voltus.svg overlay_filter: 0.5 -redirect_from: - - /projects/home-automation/ sidebar: nav: "home-automation" --- @@ -33,7 +31,7 @@ The Berker Q.3 series offers a wide array of options, allowing you to customize
- +
Top row shows the Q.1 covers from Berker, while the bottom row shows the Q.3 series.
diff --git a/collections/_home-automation/2026-10-12-ets-to-home-assistant.md b/collections/_home-automation/2026-10-12-ets-to-home-assistant.md new file mode 100644 index 000000000..479504380 --- /dev/null +++ b/collections/_home-automation/2026-10-12-ets-to-home-assistant.md @@ -0,0 +1,312 @@ +--- +layout: single +title: "From ETS to Home Assistant: Generating the KNX Configuration" +permalink: /projects/home-automation/ets-to-home-assistant/ +excerpt: "How a KNX house with hundreds of group addresses gets into Home Assistant: the ETS project, generated YAML packages per room, areas and a phone dashboard, and the read-flag pitfall that makes status values wrong." +date: 2026-10-12 21:00:00 +0200 +categories: [home automation, knx, home assistant] +tags: [home automation, knx, home assistant, ets, xknxproject, yaml, packages, generator, areas, dashboard, read flag, lesen, group monitor, troubleshooting] +comments: true +toc: true +sidebar: + nav: "home-automation" +--- + +Our house runs on KNX: lights and dimmers, screens, heating, window and door contacts, sockets with power metering and a weather station. +Everything was planned in ETS, and a visualization server was the only user interface. +I wanted [Home Assistant](https://www.home-assistant.io/) as the main interface instead, mostly for its phone app and its automations. + +That raised the first practical question: how do hundreds of group addresses get into Home Assistant without typing all of them by hand? +This page compares the options, and describes the route I took: a small generator that turns the ETS project into Home Assistant YAML, one file per room. +It ends with the pitfall that cost me the most time: two devices answering the same read. + +## The options + +| Option | Good for | Where the configuration lives | +|---|---|---| +| Home Assistant's KNX panel | A few dozen entities, created and edited in the UI | Home Assistant's own storage | +| YAML by hand | Small installations, or a few special entities | Your YAML files | +| [ets-to-homeassistant](https://github.com/laurent-martin/ets-to-homeassistant) | Converting a whole `.knxproj` once, structured by ETS functions | Generated YAML | +| [nickol-knx-mcp](https://github.com/NickoScope/nickol-knx-mcp) | Auditing and generating with an AI assistant | Generated YAML | +| An own generator | Many entities, house-specific rules, regenerating after every ETS change | Generated YAML in git | + +**Home Assistant's KNX panel.** You import the ETS project into the panel. Home Assistant parses it with [xknxproject](https://github.com/XKNX/xknxproject) and then knows every group address with its name and data point type. +The group monitor shows telegrams with those names, and you can create entities in the UI and pick their group addresses from the project. +In Home Assistant 2026.9 the UI covers all 17 KNX platforms that YAML supports, from lights and covers to climate and sensors. +For a few dozen entities that is the easiest way, and it needs no YAML at all. + +**ets-to-homeassistant** is a Ruby tool that converts a `.knxproj` into Home Assistant YAML. +It groups group addresses into devices using the ETS *functions* (or the building structure) and the data point types, and accepts a custom script for project-specific fixes. +If your project uses ETS functions consistently, it is a quick start. + +**nickol-knx-mcp** is a newer Python project, also based on xknxproject: an MCP server for AI assistants that audits ETS projects (naming, data point types, command/status pairs) and can generate Home Assistant YAML. +It reads projects read-only and never talks to the bus. It describes itself as a public beta. + +## Why I wrote a generator + +Our project has hundreds of group addresses with very consistent names and numbering. Three things tipped the balance: + +1. **The names carry the structure.** Each group address name starts with a room and device code, followed by the room, the device and the function, for example `GF2-L1 Office - Ceiling light - Switch`. + The numbering is parallel: the middle group is the function (switch, status, brightness, …) and the sub-address is the device. + A few rules turn that into entities. +2. **YAML in git can be reviewed.** Every change to the generator or to the ETS project shows up as a diff of the generated files before it reaches Home Assistant. + After an ETS change I export the project again and regenerate everything; nothing is edited by hand. +3. **Lessons become rules.** Once I learned that a status address only helps when a real device answers it, that became one line in the generator instead of a note somewhere. + +## The pipeline + +``` +ETS project (.knxproj) + → imported in Home Assistant's KNX panel (parsed by xknxproject) + → the generator reads the parsed project + → one YAML package per room + → copied to Home Assistant, configuration check, KNX reload + → each entity assigned to its room (area) through the WebSocket API + → the phone dashboard generated from the areas +``` + +The generator doesn't parse the `.knxproj` itself: it reads the project that Home Assistant has already parsed for its KNX panel. +That saves handling the project password and keeps both on the same parser version. +The catch: the parsed project sits in Home Assistant's internal storage, whose format is not a public interface. A tool for other people should read the `.knxproj` with xknxproject directly. + +The output goes into Home Assistant [packages](https://www.home-assistant.io/docs/configuration/packages/): one file per room, each starting with "generated, do not edit". +Every entity gets a comment with the group addresses it uses, and anything the generator leaves out is printed with the reason, so nothing disappears silently. + +## The rules that do the work + +**Command and status belong together** when they share the device code and the main data point type: the light's switch address and its status, the screen's move and position addresses and its position status. + +**A status address needs a device that answers.** If no device sends on it or answers reads, Home Assistant would read an address nobody serves. The generator only uses status addresses where a device has the transmit or the read flag. +The opposite problem, two devices answering the same address, is [the pitfall below](#pitfall-which-device-answers-the-read). + +**Only real devices count.** An address linked only to the visualization server is not a function of the house. It is left out. + +**Per platform:** +- Lights: switch and status, brightness, colour temperature and colour where the project has them. +- Covers: move, stop, position and position status, for screens and blinds. +- Climate: the room temperature controllers with their set points. +- Binary sensors: window and door contacts, with the polarity of the contact. +- Sensors: temperatures, brightness, wind, power readings. +- Energy: each individual power reading without an energy counter of its own gets an integrated kWh sensor for Home Assistant's Energy dashboard. Actuator totals are left out, so nothing is counted twice. + +For four group addresses of a made-up office light: + +| Group address | Name | Data point type | +|---|---|---| +| 20/0/1 | GF2-L1 Office - Ceiling light - Switch | 1.001 | +| 20/1/1 | GF2-L1 Office - Ceiling light - Status | 1.001 | +| 20/4/1 | GF2-L1 Office - Ceiling light - Brightness | 5.001 | +| 20/5/1 | GF2-L1 Office - Ceiling light - Brightness status | 5.001 | + +the generated package contains: + +```yaml +# Office: generated from the ETS project, do not edit; change the generator and rerun. +knx: + light: + # 20/0/1 GF2-L1 Office - Ceiling light - Switch / 20/1/1 … Status / 20/4/1 … Brightness / 20/5/1 … Brightness status + - name: "Office Ceiling light" + address: "20/0/1" + state_address: "20/1/1" + brightness_address: "20/4/1" + brightness_state_address: "20/5/1" +``` + +The generator itself is still specific to our house: its room table, name patterns and a few overrides. +I may publish a generalized version later, if it turns out to be useful beyond our project. + +## Rooms and the phone dashboard + +Home Assistant groups entities by **area**. A second script puts every generated entity into the area of its room through the WebSocket API. +It only fills empty areas, so a room I assigned by hand stays as it is. + +The phone dashboard is generated from the areas, too: + +- **A start page that shows only what needs attention:** open windows and lights that are on, each list hidden while it is empty, plus a few energy values at the top. +- **Every room on the start page,** with its temperature and buttons for its lights and screens. +- **One page per floor,** with the controls of each room. +- **The weather and the outside lights on their own page.** + +The script saves the dashboard through the API and writes a YAML copy into the repository, so a dashboard change is a reviewable diff as well. + +
+ Phone screenshot of the start page: energy values at the top (outside temperature, PV power and yield, consumption, heat pump, car), a card 'Offen' with a tilted window and an open roof window, and a card 'Licht an' with three lights. + Phone screenshot of the room list on the start page: one card per room on two floors, each with its temperature and buttons for its lights and screens. + Phone screenshot of a floor page: the living room with two lights and two screens, and the kitchen with two lights and a screen, each as a tile with a slider. +
The generated dashboard on a phone, here for an invented demo house built with the same script (in German, like ours): the start page, the rooms, and a floor page.
+
+ +## Pitfall: which device answers the read? + +Generated entities are only as good as the status values behind them. +After the switch, a few values were wrong in a strange way: the twilight value of the weather station stayed at 23 lx all night, although it was pitch dark outside, and other status values flipped back to an old value once an hour. +Nothing was broken on the bus. Two devices answered the same question, and Home Assistant believed whichever answer came last. +The addresses and devices in this section are a made-up example, not our installation. + +### Write, read and response + +KNX devices talk through group addresses with three kinds of telegrams: + +- **GroupValueWrite**: a device sends a new value, for example a button switching a light on, or an actuator reporting its new state on a status address. +- **GroupValueRead**: a device asks "what is the current value of this address?". Visualizations and Home Assistant do this at start-up, and again later to stay in sync. +- **GroupValueResponse**: the answer to a read. Home Assistant and visualizations take it over as the new value; a KNX device does so where its object has the update flag (A, see below). + +A read should get exactly **one** response: from the device that owns the value. +For a light's status, that is the actuator channel that switches the light. For a sensor value, it is the sensor. +Every other device linked to the address may listen, but it should not answer. + +### The flags in ETS + +Which device answers is decided by the flags of each communication object in ETS. +The German ETS shows them as letters: + +| ETS (German) | ETS (English) | Meaning | +|---|---|---| +| K (Kommunikation) | C (Communication) | The object talks on the bus at all. | +| L (Lesen) | R (Read) | The object **answers read requests** with its value. | +| S (Schreiben) | W (Write) | The object accepts writes from the bus. | +| Ü (Übertragen) | T (Transmit) | The object sends its value by itself when it changes. | +| A (Aktualisieren) | U (Update) | The object takes over values from **responses**, not only from writes. | +| I (Lesen bei Init) | I (Read on Init) | The device sends a read for this object when it starts. | + +The rule that prevents the problem: **per group address, only the object that owns the value has L (Lesen).** +Objects that only mirror a value (a visualization, a logic server, a second display) need K and usually A, but not L. + +### An example with two answers + +Take a small installation with three devices: + +| Device | Individual address | Object on `22/0/1` "Twilight" | Flags | +|---|---|---|---| +| Weather station | 2.1.30 | Twilight value (owner) | K, L, Ü | +| Visualization server | 2.1.20 | Twilight value (mirror for its display) | K, **L**, S, A | +| Home Assistant | 2.1.250 | `sensor` entity for `22/0/1` | – | + +The weather station sends the twilight value when it changes, in steps. +The visualization server keeps its own copy to show it, and because its object has L, it answers reads as well. + +Its copy can go stale for many reasons. The server may restart and begin with an old or default value. It may have missed a telegram while it was busy, or its object may not take values over from responses. +The reason hardly matters: once the server answers reads, its stale copy goes on the bus as a proper response, and Home Assistant takes it over like any other answer. + +At start-up, Home Assistant reads the state address of each KNX entity that has one, as long as state synchronization is on, which is the default. With the default `sync_state: true`, it also reads again when it heard nothing on an address for 60 minutes ([KNX integration docs](https://www.home-assistant.io/integrations/knx/)). +At night the twilight value doesn't change, so the station sends nothing, and Home Assistant reads once an hour. Each read gets two answers: + +| Time | Source | Destination | Type | Value | +|---|---|---|---|---| +| 21:14:02.105 | 2.1.250 | 22/0/1 | GroupValueRead | | +| 21:14:02.131 | 2.1.30 | 22/0/1 | GroupValueResponse | 1 lx | +| 21:14:02.164 | 2.1.20 | 22/0/1 | GroupValueResponse | 23 lx | + +Home Assistant keeps the last one, 23 lx. Which answer comes last is a race, so the value can flip between the two at every read. +In the history graph, that looks like a value that jumps at the same minute every hour, or that stays at a daytime value all night. + +
+ Sequence diagram: Home Assistant sends a GroupValueRead on 22/0/1. The weather station answers 1 lx after 26 ms, the visualization server answers its stale 23 lx after 59 ms, and Home Assistant shows the last answer, 23 lx. +
One read, two responses: the stale copy arrives last and wins.
+
+ +The same happens with any status: a light whose status reads "off" while it is on, or a switch for the shading automatic that shows "on" after someone switched it off. + +### Finding the second responder on the bus + +1. Open a group monitor: in ETS under *Diagnostics → Group Monitor*, or the group monitor in Home Assistant's KNX panel (opened from the KNX integration in the settings). +2. Send a read to the address in question: in the ETS group monitor with **Read** under *Group Functions*, or in Home Assistant under *Developer tools → Actions* with `knx.read` and the address. +3. Look at the responses. More than one **GroupValueResponse** from different source addresses means more than one device answers. + +The source address tells you which device it is. If the values differ, you have found the reason for the wrong status. + +### Finding candidates in the ETS project + +A double responder is usually visible in the project before anything goes wrong: two communication objects with the communication and read flags on the same group address. +xknxproject reads the flags from a `.knxproj` export. +This lists every group address with more than one candidate responder, devices with an object that has both K and L: + +```python +from collections import defaultdict + +from xknxproject import XKNXProj + +project = XKNXProj("house.knxproj", password=None).parse() + +candidates = defaultdict(set) # group address → devices whose object has K and L +for obj in project["communication_objects"].values(): + flags = obj["flags"] + if flags["communication"] and flags["read"]: + for address in obj["group_address_links"]: + candidates[address].add(obj["device_address"]) + +for address, devices in sorted(candidates.items()): + if len(devices) > 1: + name = project["group_addresses"][address]["name"] + print(f"{address:10} {name:40} {', '.join(sorted(devices))}") +``` + +For the example above it prints one line: + +``` +22/0/1 Twilight 2.1.20, 2.1.30 +``` + +In our project the list was several hundred lines long, almost all of them with the visualization server next to the real device. +Treat the list as candidates to check in the group monitor, not as faults: + +- A device can legitimately own a value that is also linked elsewhere. +- An object linked to several group addresses responds on its **sending address**, the one marked S in ETS. A read on one of its other addresses is answered on the sending address, not on the address you asked. The script lists such an object under every linked address. + +### The fix + +In ETS, for each address on the list: + +1. Decide which object **owns** the value: the actuator channel, the sensor, or the logic that produces it. +2. On every other object linked to that address, **clear L (Lesen)**. Keep K. Keep S if the device should accept commands on it, and A if it should take over values from responses. Set Ü only where the device really produces the value. +3. Program the changed devices (the application is enough). +4. Repeat the read test: one response. + +Before clearing flags on a visualization or logic server, check that it doesn't need them for its own logic outputs. Only the mirrors of values that another device owns lose L. + +Home Assistant can become a responder too: a KNX `expose`, or an entity with `respond_to_read`, answers reads with Home Assistant's value. +The same rule applies: only where Home Assistant owns the value, and never next to a device that answers on the same address. + +### On the Home Assistant side + +**A KNX light shows "off" until its first status.** +Home Assistant's KNX light reports off while it has no state yet ([source, 2026.9.4](https://github.com/home-assistant/core/blob/2026.9.4/homeassistant/components/knx/light.py#L335): `is_on` returns `bool(state)`). +After a restart, a light shows off until its actuator answers the start-up read or sends a new state. +A light group or template built from those lights is therefore "all off" for a moment. Keep that in mind before an automation acts on it or sends it back to the bus. + +**Fewer reads hide the problem, they don't fix it.** +`sync_state` can be set to `init` (read only at start-up) or `expire `. With fewer reads you see the stale value less often, but the next read still gets two answers. +Fix the flag instead. + +If you consume KNX telegrams in your own templates or automations (`knx_event`, or the `knx.telegram` trigger), each telegram carries its `source`. +Where you can't change the flags yet, accepting a value only from the owner's individual address is a robust stopgap. + +### Checklist + +- [ ] A value jumps at regular intervals, or shows a daytime value at night: suspect a second responder. +- [ ] Group monitor plus one read: how many responses, from which sources? +- [ ] List the group addresses with more than one K + L object in the project. +- [ ] Per address, keep L only on the owner; keep K, S, A and Ü where they belong. +- [ ] Program the devices and repeat the read test. +- [ ] Check that Home Assistant doesn't answer reads where a device owns the value. + +## What I learned + +- **Names are the real interface.** A consistent naming scheme in ETS made the generator possible. A typo in a group address name becomes a typo in the app, so I fix names in ETS and export again rather than patching them in Home Assistant. +- **Renames need care.** Home Assistant keeps an entity's ID when its name changes. When the generator renames an entity, I rename its ID on purpose, and check automations and the dashboard for the old one. +- **Read the status from the right device.** Generated entities are only as good as the status addresses behind them: see [the pitfall above](#pitfall-which-device-answers-the-read). +- **Review before deploy.** Every change goes through a pull request with an independent review and tests against a real Home Assistant version, before anything is copied to the house. + +## Which option fits + +- **Up to a few dozen entities, or you prefer clicking:** Home Assistant's KNX panel. +- **A whole project at once, with ETS functions:** try ets-to-homeassistant first. +- **You want an audit of the project as well:** have a look at nickol-knx-mcp. +- **Hundreds of entities, consistent names, and you want every change reviewed in git:** a small generator of your own on top of xknxproject. + +## My setup + +Home Assistant runs as a virtual machine on [Proxmox](https://www.proxmox.com/) on a Beelink EQ14 mini PC (Intel N150, 16 GB RAM), which I bought directly from [Beelink](https://www.bee-link.com/products/beelink-eq14-n150). +It talks to the bus through a KNX IP interface. + +Tested with Home Assistant 2026.9 and its KNX integration. The ETS flags and the group monitor work the same in ETS 5 and ETS 6. diff --git a/collections/_home-automation/2026-10-12-velux-klf200-knx.md b/collections/_home-automation/2026-10-12-velux-klf200-knx.md new file mode 100644 index 000000000..809e436ad --- /dev/null +++ b/collections/_home-automation/2026-10-12-velux-klf200-knx.md @@ -0,0 +1,226 @@ +--- +layout: single +title: "Velux Roof Windows with KNX Buttons, through Home Assistant and the KLF 200" +permalink: /projects/home-automation/velux-klf200-knx/ +excerpt: "KNX buttons and a KNX visualization drive Velux io-homecontrol windows and screens: Home Assistant as the bridge, with position status back to KNX, a rain rule, and what the end of the KLF 200 means." +date: 2026-10-12 22:00:00 +0200 +categories: [home automation, knx, home assistant] +tags: [home automation, knx, home assistant, velux, klf 200, klf 150, kig 300, velux active, homekit, io-homecontrol, roof window, screen, awning, rain, bridge, automation] +comments: true +toc: true +sidebar: + nav: "home-automation" +--- + +Our roof windows and their outside screens are Velux INTEGRA products. They speak **io-homecontrol**, a radio protocol, not KNX. +The rest of the house is KNX, and the wall buttons and the visualization were planned to move the Velux windows like any other blind. +Something has to translate between the two. + +In our house that is [Home Assistant](https://www.home-assistant.io/): it receives the KNX telegrams, moves the Velux products through a **Velux KLF 200** gateway (bought in 2023), and sends their positions back to KNX. +This page shows that bridge with a small, made-up example you can adapt, and what the end of the KLF 200 means for new installations. + +## Before you buy: the KLF 200 is discontinued + +Velux has replaced the KLF 200 with the **KLF 150**. That is not a drop-in replacement for Home Assistant: + +- The Home Assistant [Velux integration](https://www.home-assistant.io/integrations/velux/) talks to the KLF 200 over the local network. Its documentation says the KLF 150 is **not** supported, "even though Velux markets it as the replacement for the now-discontinued KLF 200", because the KLF 150 has no local API. +- Velux describes the [KLF 150](https://www.velux.de/fachkunden/produkt-informationen/smart-home/elektrozubehoer/klf-150) as an interface for switches and sensors with **potential-free contacts**: it can control 5 products or 5 groups. As far as I can tell from that description, KNX binary outputs could drive it like a set of buttons, but Home Assistant would then know nothing about positions. +So: if you already have a KLF 200, everything below works, and you may still find used units. + +**For a new installation, the route that works with Home Assistant is the VELUX ACTIVE gateway (KIG 300).** +Home Assistant's Velux documentation says to integrate it with the [HomeKit Device](https://www.home-assistant.io/integrations/homekit_controller/) integration, which talks to the gateway locally, without a cloud. +Its windows and screens then appear as cover entities, and the bridge below should work the same way with those entities in place of the KLF 200's. +I haven't run that combination myself. + +Two other routes, for completeness: + +- **Somfy TaHoma:** Somfy's [compatibility list 2025 v1](https://service.somfy.com/downloads/sk_v5/compatibilitylist_tahoma_switch_-_rail_din_2025_v1.pdf) (page 12) lists Velux io roof windows, roller shutters (SSL/SML io) and screens (MML) for the TaHoma switch and TaHoma Rail-DIN S. Home Assistant's [Overkiz](https://www.home-assistant.io/integrations/overkiz/) integration can talk to the TaHoma switch locally. Check your exact products against the list. +- **Directly from KNX:** [OpenKNX OFM-IOHomeControl](https://github.com/OpenKNX/OFM-IOHomeControl) is community firmware for an ESP32 with an 868 MHz radio that speaks io-homecontrol on one side and KNX on the other, without a manufacturer gateway. It is in active development, and the project says most device types are still untested. + +## How the bridge works + +``` +KNX button or visualization + → group address (move, stop, position) + → Home Assistant automation (knx.telegram trigger) + → one script per Velux product + → Velux cover entity → KLF 200 → io-homecontrol → window or screen + +Velux position changes + → Home Assistant automation (state trigger) + → knx.send to the position status address → button LEDs and visualization +``` + +Three design choices made it reliable: + +1. **One script per Velux product, in `restart` mode.** A new command for the same window replaces the one still running; other windows are not affected. + The dispatching automation only starts the script and doesn't wait, so a slow gateway never holds up the next telegram. +2. **Positions are converted once, in both directions.** A KNX blind position (DPT 5.001) counts 0 % as fully up or open; a Home Assistant cover counts 100 as fully open. + Commands from KNX become `100 − value`, and the status back to KNX does the same. +3. **No position, no status.** When the gateway restarts or an entity is unavailable, the cover has no position. + An early version of my bridge turned that missing value into 0 with a default, and then into 100 % for KNX: the visualization would show a closed window, whatever its real position. Only a real position from 0 to 100 goes to the bus. + +## The example + +The example drives one roof window and its screen. All names and addresses are made up: + +| Function | Window | Screen | Data point type | +|---|---|---|---| +| Move (up/down) | 24/0/1 | 24/0/2 | 1.008 | +| Stop | 24/1/1 | 24/1/2 | 1.007 | +| Position | 24/2/1 | 24/2/2 | 5.001 | +| Position status | 24/3/1 | 24/3/2 | 5.001 | + +`cover.roof_window` and `cover.roof_window_screen` are the entities of the Velux integration, and `binary_sensor.rain` is a rain sensor, for example from a KNX weather station. +The `type` on each trigger tells Home Assistant how to decode the value, so it works without importing the ETS project. + +Put it into a [package](https://www.home-assistant.io/docs/configuration/packages/), for example `packages/velux_knx.yaml`: + +{% raw %} +```yaml +# Velux window and screen, driven from KNX through Home Assistant (made-up addresses). +script: + velux_window: + alias: "Velux window: one command at a time" + mode: restart + sequence: &velux_command + - choose: + - conditions: "{{ command == 'stop' }}" + sequence: + - action: cover.stop_cover + target: { entity_id: "{{ cover }}" } + - conditions: "{{ command == 'open' }}" + sequence: + - action: cover.open_cover + target: { entity_id: "{{ cover }}" } + - conditions: "{{ command == 'close' }}" + sequence: + - action: cover.close_cover + target: { entity_id: "{{ cover }}" } + - conditions: "{{ command == 'position' }}" + sequence: + - action: cover.set_cover_position + target: { entity_id: "{{ cover }}" } + data: { position: "{{ position }}" } + velux_screen: + alias: "Velux screen: one command at a time" + mode: restart + sequence: *velux_command + +automation: + - id: velux_from_knx + alias: "Velux: commands from KNX" + mode: queued + max: 20 + triggers: + - trigger: knx.telegram + id: move + destination: ["24/0/1", "24/0/2"] + type: "1.008" + group_value_read: false + group_value_response: false + outgoing: false + - trigger: knx.telegram + id: stop + destination: ["24/1/1", "24/1/2"] + group_value_read: false + group_value_response: false + outgoing: false + - trigger: knx.telegram + id: position + destination: ["24/2/1", "24/2/2"] + type: percent + group_value_read: false + group_value_response: false + outgoing: false + variables: + # The last part of the address is the product. + products: + "1": { cover: cover.roof_window, script: script.velux_window } + "2": { cover: cover.roof_window_screen, script: script.velux_screen } + product: "{{ products[trigger.destination.split('/')[2]] }}" + command: >- + {% if trigger.id == 'move' %}{{ 'open' if trigger.value == 'up' else 'close' }}{% else %}{{ trigger.id }}{% endif %} + actions: + - action: script.turn_on + target: { entity_id: "{{ product.script }}" } + data: + variables: + cover: "{{ product.cover }}" + command: "{{ command }}" + # KNX counts 0 % as open, Home Assistant 100. + position: "{{ 100 - trigger.value if trigger.id == 'position' else 0 }}" + + - id: velux_to_knx + alias: "Velux: position status to KNX" + mode: queued + max: 20 + triggers: + - trigger: state + entity_id: [cover.roof_window, cover.roof_window_screen] + attribute: current_position + variables: + status: { cover.roof_window: "24/3/1", cover.roof_window_screen: "24/3/2" } + position: "{{ state_attr(trigger.entity_id, 'current_position') }}" + conditions: + # Only a real position. Without one (gateway restart, entity unavailable) nothing is sent. + - "{{ has_value(trigger.entity_id) and position is number and 0 <= position <= 100 }}" + actions: + - action: knx.send + data: + address: "{{ status[trigger.entity_id] }}" + payload: "{{ 100 - position }}" + type: percent + + - id: velux_screen_up_in_rain + alias: "Velux: screen up when it starts to rain" + triggers: + - trigger: state + entity_id: binary_sensor.rain + from: "off" + to: "on" + conditions: + - "{{ has_value('cover.roof_window_screen') and not is_state_attr('cover.roof_window_screen', 'current_position', 100) }}" + actions: + - action: cover.open_cover + target: { entity_id: cover.roof_window_screen } +``` +{% endraw %} + +What each part does: + +- **The scripts** run the actual Velux command. The YAML anchor `&velux_command` gives both scripts the same steps without copying them; each still has its own `restart` queue. +- **`velux_from_knx`** listens only to writes on the listed addresses, and not to Home Assistant's own telegrams (`outgoing: false`). It works out the product and the command, and starts that product's script. +- **`velux_to_knx`** sends every real position change back, as KNX percent. Buttons with status LEDs and the visualization show the actual position, also when someone used the Velux remote. +- **`velux_screen_up_in_rain`** raises the screen when it starts to rain, unless it is already up. In our house the screens go up in rain; adapt this to your own rule. + +To test it, press a KNX button or write a value from the ETS group monitor, and watch the automation's trace in Home Assistant. A `knx.send` from Home Assistant itself is ignored on purpose (`outgoing: false`). + +## Airing with guards + +The same bridge carries a second feature: "airing" from a KNX button or the phone. A window opens, a timer runs, and the window closes again afterwards. Two guards proved necessary: + +- **Not in rain.** Velux windows with a rain sensor only open a gap while it rains, and the Home Assistant integration reports that sensor: it reads the window's opening limitation every 5 minutes, so a limitation set by other means also reads as rain. The rain sensor entities are disabled by default; enable them in the entity settings. Airing doesn't start while either the weather station or a window reports rain. +- **No overlapping runs.** A single window, its group and "all windows" can each start an airing. A new one only starts when none of its windows is already in a running one, so one timer never closes a window that another just opened. + +The status LED on the KNX button follows the timer, so you can see from the wall that airing is running. + +## On the phone + +The phone dashboard shows the screens and the airing on the start page, and each roof window with its screen in its room. +The screenshots are from an invented demo house, built with the same dashboard script as ours ([From ETS to Home Assistant](/projects/home-automation/ets-to-home-assistant/#rooms-and-the-phone-dashboard)): + +
+ Phone screenshot: the section 'Beschattung' with the averaged screens per floor, switches for the automatic shading and for lowering at dusk, and a tile for all Velux screens; below, the section 'Lüften' with one tile per roof window, one of them airing with 14:52 minutes left. + Phone screenshot of the upper floor: in each room a light, the roof window with up, stop, down and a position slider, and the roof window's screen with its own buttons and slider. +
Screens and airing on the start page, and the roof windows in their rooms (demo house, German like ours).
+
+ +## What I would check first + +- **The gateway's firmware and connection:** the Home Assistant integration needs KLF 200 firmware newer than 2.0.0.0. The setup has to be finished within five minutes of restarting the gateway, while its Wi-Fi access point is still visible, with Home Assistant on your regular network (see the [integration docs](https://www.home-assistant.io/integrations/velux/)). +- **The direction of positions:** send 25 % from KNX to a screen and check that it moves a quarter of the way down (Home Assistant shows 75), not three quarters. +- **Missing positions:** restart the gateway and watch the status addresses in the group monitor. Nothing should be sent until the positions are back. +- **Who answers reads on the status addresses:** if a visualization server also answers them, the buttons can show an old position. See [which device answers the read](/projects/home-automation/ets-to-home-assistant/#pitfall-which-device-answers-the-read). + +The way the KNX project itself becomes Home Assistant configuration is on [From ETS to Home Assistant](/projects/home-automation/ets-to-home-assistant/). diff --git a/collections/_projects/2022-07-23-home-automation.md b/collections/_projects/2022-07-23-home-automation.md index 1f9bf75c9..fa493f29e 100644 --- a/collections/_projects/2022-07-23-home-automation.md +++ b/collections/_projects/2022-07-23-home-automation.md @@ -1,6 +1,6 @@ --- layout: single -title: "Home Automation - Raspberry Pi Setup" +title: "Home Automation" permalink: /projects/home-automation/ excerpt: "Home Automation based on KNX and Home Assistant." date: 2022-07-23 09:00:35 +0100 @@ -8,28 +8,27 @@ categories: [home automation, raspberry pi, home assistant, knx] tags: [knx, home, home automation, home assistant, home, assistant, raspberry pi, os, raspberry, ssd, setup, argon, one, m.2, case] comments: true use_math: true -toc: true +toc: false classes: wide # toc_label: "Unscented Kalman Filter" header: teaser: /assets/collections/home-automation/knx-wago.jpg overlay_image: /assets/collections/home-automation/knx-wago.jpg overlay_filter: 0.5 -redirect_from: - - /projects/home-automation/ sidebar: nav: "home-automation" --- -Home Automation project based on KNX and Home Assistant. The instructions provide a guide on how to setup everything up. +Home Automation project based on KNX and Home Assistant. The pages in the menu describe how it is set up. The features that this project includes are: - KNX Bus -- Wheater station +- Weather station - Automatic blind control for shading -- Lightning control based on [DALI](https://en.wikipedia.org/wiki/Digital_Addressable_Lighting_Interface) +- Lighting control based on [DALI](https://en.wikipedia.org/wiki/Digital_Addressable_Lighting_Interface) - Multi-room audio based on Sonos -For testing purposes a Raspberry Pi 4B 8GB running Home Assistant is used. For more details see the [setup instructions](/projects/home-automation/setup) including booting from an SSD. +Home Assistant now runs on a mini PC under Proxmox. How the KNX project gets into it is described in [From ETS to Home Assistant](/projects/home-automation/ets-to-home-assistant/), and how KNX buttons drive the Velux roof windows in [Velux Roof Windows with KNX Buttons](/projects/home-automation/velux-klf200-knx/). +The first setup from 2022, on a Raspberry Pi 4 booting from an SSD, stays as an [archived page](/projects/home-automation/setup/).