Fertige Präsenzmelder aus dem Handel messen meist nur ein, zwei Werte und lassen sich kaum in ein durchdachtes Smart-Home-Konzept einbinden. Ich wollte einen DIY-Multisensor, der wirklich alles liefert, was ich für sinnvolle Automationen brauche – und das für unter 30 € pro Stück.
Deshalb habe ich mir einen eigenen Multisensor gebaut: ein ESP32-S3 SuperMini, kombiniert mit einem LD2410C mmWave-Radar für echte Präsenzerkennung (nicht nur Bewegung!), einem BH1750-Helligkeitssensor und einem BME280-Klimasensor für Temperatur, Luftfeuchtigkeit und Luftdruck. Alle Werte landen per ESPHome direkt und zuverlässig in Home Assistant.
In diesem Beitrag zeige ich dir den kompletten Aufbau – vom Anlöten der ersten Stiftleiste bis zur fertigen ESPHome-Konfiguration und den Automationen in Home Assistant. Aktuell läuft mein Sensor noch im Testaufbau per USB-C auf dem Schreibtisch. Als Ausblick zeige ich dir aber schon, wie es danach weitergeht: mit einer festen 230V-Stromversorgung über ein HLK-PM01-Netzteil und einem eigenen 3D-gedruckten Gehäuse für den dauerhaften Einbau.
🎯 Ziel des Projekts
Der Sensor soll am Ende Folgendes leisten:
✅ Präsenz erkennen (nicht nur Bewegung) – per LD2410C mmWave-Radar
✅ Helligkeit messen – per BH1750
✅ Temperatur, Luftfeuchtigkeit und Luftdruck messen – per BME280
✅ Alle Werte live in Home Assistant zur Verfügung stellen
✅ Sich wahlweise per USB-C oder dauerhaft über 230V betreiben lassen
✅ Als Basis für intelligente Licht-, Heizungs- und Lüftungsautomationen dienen
🧭 Die Vorbereitung
Bevor der Lötkolben angeht, lohnt sich etwas Vorarbeit. Diese vier Schritte haben mir den Einstieg deutlich erleichtert:
1️⃣ Verständnis aufbauen
LD2410C und ESPHome waren für mich beides Neuland. Bevor ich bestellt habe, habe ich mir folgende Quellen angeschaut: die offizielle ESPHome-Dokumentation zur ld2410-Komponente, ein paar Praxisvideos zum LD2410C auf YouTube (wichtig, um ein Gefühl für Reichweite und Fehlalarme zu bekommen) sowie Diskussionen im ESPHome- bzw. Home-Assistant-Forum, in denen andere Nutzer ihre Konfigurationen und Stolpersteine teilen. Das hat mir geholfen, vorab realistische Erwartungen an Reichweite, Empfindlichkeit und I²C-Verkabelung zu entwickeln, statt alles per Trial-and-Error herauszufinden.
2️⃣ Bauteile bestellen
Bei der Bestellung standen für mich zwei Fragen im Vordergrund: Wo bestelle ich, und worauf muss ich bei den Sensoren konkret achten?
AliExpress vs. Amazon: Auf AliExpress bekommt man ESP32-S3, LD2410C, BH1750 und BME280 zusammen oft für weniger als die Hälfte des Amazon-Preises – dafür sind 2-4 Wochen Lieferzeit realistisch, und bei Reklamationen dauert es entsprechend länger. Amazon lohnt sich, wenn du schnell testen willst oder ein Bauteil kurzfristig ersetzen musst; für den Rest würde ich – wenn Zeit kein Problem ist – AliExpress empfehlen.
BME280 vs. BMP280: Hier lohnt sich ein zweiter Blick auf die Artikelbeschreibung. Viele günstige Angebote sind als „BME280“ gelistet, liefern aber tatsächlich einen BMP280 – und der misst keine Luftfeuchtigkeit. Sowohl das Modulfoto (BME280-Chips haben meist eine glänzende, leicht erhabene Sensorkappe) als auch Kundenbewertungen geben oft einen Hinweis, bevor man bestellt. Im Zweifel hilft nur: Bewertungen mit Fotos durchsuchen oder direkt beim Verkäufer nachfragen.
3️⃣ Werkzeug bereitlegen
Für den Zusammenbau reicht einfaches Werkzeug: Lötkolben mit feiner Spitze, dünnes Lötzinn, eine dritte Hand oder Platinenklemme, ein kleines Multimeter zum Prüfen der Verbindungen und ein Satz Dupont-Kabel für die spätere Verdrahtung zwischen den Modulen.
4️⃣ Home Assistant vorbereiten
Damit später nichts im Weg steht, habe ich vorab in Home Assistant unter Einstellungen → Add-ons → Add-on Store nach dem ESPHome Device Builder gesucht und ihn installiert. So kann ich direkt nach dem Verlöten mit der Konfiguration starten, ohne erst noch Grundlagen einrichten zu müssen.
Bevor verdrahtet wird, werden an allen Modulen zunächst Stiftleisten angelötet. Wichtig: Für die ersten Tests kommt noch kein Netzteil zum Einsatz – der ESP32 wird ausschließlich per USB-C versorgt.
Nachdem die Verdrahtung steht, folgt die Konfiguration in ESPHome. Zuerst wird in Home Assistant unter Einstellungen → Add-ons → Add-on Store der ESPHome Device Builder installiert und gestartet. Danach ein neues Gerät anlegen (Name: zimmer_sensor, Board: ESP32-S3) und das WLAN eintragen.
Hinweis: Einige BME280-Module nutzen die I²C-Adresse 0x77 statt 0x76. Falls der Sensor nicht erkannt wird, im ESPHome-Log nach der gefundenen I²C-Adresse suchen und die Adresse in der Konfiguration entsprechend anpassen.
Anschließend wird die Firmware geflasht: ESP32 per USB-C an den PC anschließen, in ESPHome auf Install → Plug into this computer klicken und warten, bis SUCCESS erscheint. Nach wenigen Sekunden taucht das neue Gerät in Home Assistant unter Einstellungen → Geräte & Dienste automatisch zur Einrichtung auf.
🏠 Integration in Home Assistant
Für das Dashboard zeige ich mir folgende Entitäten an:
Präsenz
Bewegungsdistanz
Präsenzdistanz
Bewegungsenergie
Präsenzenergie
Helligkeit
Temperatur
Luftfeuchtigkeit
Luftdruck
Damit der Sensor nicht nur Werte liefert, sondern auch etwas tut, habe ich direkt zwei einfache Automationen eingerichtet: Das Licht geht an, wenn Präsenz erkannt wird und es zu dunkel ist – und wieder aus, sobald für zwei Minuten keine Präsenz mehr erkannt wird.
Aktuell steht mein Sensor noch offen verdrahtet auf dem Schreibtisch und wird per USB-C versorgt – perfekt, um die Konfiguration zu testen und die Automationen zu verfeinern, bevor er fest verbaut wird. Für den dauerhaften Einbau sind zwei weitere Schritte geplant:
1️⃣ Feste 230V-Stromversorgung mit HLK-PM01
Für den Festeinbau ersetzt ein HLK-PM01-Netzteil (230V → 5V) das USB-Netzteil:
Quelle
Ziel
230V L
HLK-PM01 L
230V N
HLK-PM01 N
HLK-PM01 +5V
ESP32 5V
HLK-PM01 GND
ESP32 GND
Das LD2410C hängt dabei weiterhin direkt an 5V und GND des ESP32.
2️⃣ 3D-gedrucktes Gehäuse
Für den Festeinbau plane ich ein eigenes Gehäuse mit rund 90 × 60 × 35 mm, in das ESP32-S3, LD2410C, BH1750, BME280 und das HLK-PM01-Netzteil passen. Gedruckt wird es aus PLA, PETG oder ASA – das LD2410C funktioniert laut Datenblatt problemlos durch Kunststoff hindurch, eine Aussparung ist also nicht nötig.
Mit diesem DIY-Multisensor bekommt Home Assistant einen selbstgebauten WLAN-Raumsensor auf Basis eines ESP32-S3, der mittels LD2410C echte Präsenz erkennt und zusätzlich Helligkeit, Temperatur, Luftfeuchtigkeit und Luftdruck misst. Alle Daten werden über ESPHome direkt an Home Assistant übertragen und lassen sich für intelligente Automationen wie Licht-, Heizungs- oder Lüftungssteuerungen nutzen. Je nach Ausführung – per USB-C oder dauerhaft über ein 230V-Netzteil – kostet der Sensor etwa 20-30 € pro Stück.
Eine Werkbank aus dem Baumarkt gibt es für jeden Geschmack – stabil, massiv und gleichzeitig gut aussehend wird es dann aber meistens teuer oder das Ergebnis passt einfach nicht zu den eigenen Anforderungen. Ich wollte eine Werkbank, die genau das kann: massiv genug, um wirklich jedes Werkzeug abzukönnen, und optisch so, dass sie auch etwas her macht. Die Lösung lag praktisch im Vorgarten – beim Abriss eines Nebengebäudes sind mir alte, kräftige Balken übrig geblieben, die ich dafür wiederverwenden konnte.
In diesem Beitrag zeige ich den Bauprozesses: von der Frage, ob das vorhandene Altholz überhaupt reicht, über den Zuschnitt mit der Kettensäge bis zum Schleifen und Ölen der Balken. Im zweiten Schritt ging es dann an das Zusammenbauen und die Auswahl der richtigen Arbeitsplatte.
Inspiriert hat mich das Video über den Bau einer Werkbank von den „Helden der Werkstatt„.
📸Der Ursprung der alten Balken
📸Die alten Balken vom Teilabriss des Nebengebäudes
🔧 Ausgangslage
Der Plan für die Werkbank stand fest: vier senkrechte Beine (Bauteil 1) und mehrere waagerechte Balken, die den Rahmen oben und unten aussteifen (Bauteile 2 bis 7). Bevor ich mit dem Bau begonnen habe, musste ich aber erst klären: Reicht das vorhandene Altholz aus dem Abriss überhaupt, oder muss ich zusätzlich Holz kaufen?
Zur Verfügung standen mir sechs Balken unterschiedlicher Länge und mit zwei verschiedenen Querschnitten:
Balken A: 425 × 10 × 10 cm
Balken B: 387 × 8 × 13,5 cm
Balken C: 386 × 8 × 13,5 cm
Balken D: 220 × 8 × 13,5 cm
Balken E: 286 × 8 × 13,5 cm
Balken F: 290 × 8 × 13,5 cm
👉 Die Herausforderung: Balken A ist als einziger 10×10 cm stark und war für die vier senkrechten Beine der Werkbank vorgesehen. Die restlichen fünf Balken mit 8×13,5 cm sollten alle waagerechten Streben liefern.
📐 Der Zuschnittplan
Um möglichst wenig Verschnitt zu haben, habe ich vor dem ersten Sägeschnitt genau durchgerechnet, welches Bauteil aus welchem Balken kommt – inklusive Sägeblattbreite (ca. 3–5 mm pro Schnitt).
🪵 Die Pfosten (10×10 cm)
Balken A (425 cm):
4× Bauteil 1 (Länge: 90 cm) – die vier Beine der Werkbank
Restlänge: ca. 65 cm
🪵 Die waagerechten Balken (8×13,5 cm)
Balken B (387 cm) – obere Längsseiten:
2× Bauteil 2 (Länge: 160 cm)
Restlänge: ca. 67 cm
Balken C (386 cm) – untere Längsseiten:
2× Bauteil 5 (Länge: 144 cm)
Restlänge: ca. 98 cm
Balken D (220 cm) – obere Querstreben:
2× Bauteil 3 (Länge: 100 cm)
Restlänge: ca. 20 cm
Balken E (286 cm) – obere Mitteltraverse + eine untere Querstrebe:
1× Bauteil 4 (Länge: 104 cm)
1× Bauteil 6 (Länge: 120 cm)
Restlänge: ca. 62 cm
Balken F (290 cm) – zweite untere Querstrebe + untere Längsstrebe:
1× Bauteil 6 (Länge: 120 cm)
1× Bauteil 7 (Länge: 144 cm)
Restlänge: ca. 26 cm
💡 Ergebnis: Bei jedem Balken bleiben mindestens 20 cm Puffer übrig – genug Reserve für kleine Messfehler oder ungerade Kanten. Das Altholz vom Abriss hat also komplett gereicht, kein einziger Balken musste nachgekauft werden.
📸 Übersicht des Zuschnittplans mit allen Bauteilen
🪚 Zuschnitt mit der Kettensäge
Da ich für Balken dieser Stärke keine stationäre Kappsäge besitze, kam die Kettensäge zum Einsatz – eingespannt in einer Kippvorrichtung, die durch simples Kippen einen geraden, rechtwinkligen Schnitt ermöglichen soll, ohne freihändig sägen zu müssen.
👉 Mein Ablauf pro Balken:
Enden säumen – beide Balkenenden zuerst sauber kappen, da die alten Kanten vom Abriss oft rissig oder beschädigt waren. So entsteht eine exakte Null-Linie zum Messen.
Erstes Bauteil anzeichnen und absägen
Zweites Bauteil vom verbleibenden Reststück messen und absägen
⚠️ Wichtiger Punkt bei diesem Ablauf: Nacheinander anzeichnen, sägen, messen, sägen – nicht alle Schnitte vorab auf einmal einzeichnen. Sonst „frisst“ die Kettenbreite bei jedem Schnitt etwas vom Maß, und die hinteren Bauteile werden zu kurz.
Zwei weitere Punkte, auf die ich geachtet habe:
Bleistiftstrich auf der Abfallseite treffen, nicht mittig – sonst fehlt am Ende die halbe Kettenbreite (ca. 3–4 mm) an der Gesamtlänge.
Kettenspannung im Blick behalten – durch die starre Führung in der Kippvorrichtung wirken andere Hebelkräfte auf das Schwert als beim freihändigen Schneiden.
Der Verschnitt durch die breitere Kettensäge (5–9 mm statt wenige mm bei der Kreissäge) war dank der eingeplanten Pufferlängen von 20 cm und mehr an keiner Stelle ein Problem.
⚠️Aber: Trotz Kippvorrichtung hat der Schnitt mit der Kettensäge in der Praxis leider keinen wirklich rechte Winkel ergeben. Da das so nicht optimal funktioniert hat, musste ich alle Balken noch einmal nachbessern – dieses Mal mit der Kappsäge. Der ursprüngliche Plan, alles allein mit der Kettensäge zu schneiden, ging also nicht ganz auf, aber am Ende war das Ergebnis sehr gut. Dadurch sind alle Bauteile zwar etwas kürzer geworden, was aber dank der eingeplanten Pufferlängen insgesamt nichts ausgemacht hat.
📸 Die Kippvorrichtung für die Kettensäge im Einsatz
🧽 Schleifen mit dem Exzenterschleifer
Nach dem Zuschnitt ging es an die Oberfläche. Mit einem Exzenterschleifer habe ich die großen Balkenflächen so gut wie möglich bearbeitet, bis eine wirklich glatte, gleichmäßige Oberfläche entstanden ist – gerade bei Altholz vom Abriss zahlt sich hier gründliche Arbeit aus, da alte Balken oft eine raue oder unebene Oberfläche mitbringen.
⚠️ Wichtig im Nachgang: Die Balken müssen vor dem nächsten Schritt komplett entstaubt werden. Feinster Schleifstaub setzt sich in den Poren fest – bleibt er dort, verhindert er, dass das Öl im nächsten Schritt gleichmäßig einzieht.
📸 Die Balken beim Schleifen mit dem Exzenterschleifer
🛢️ Ölen mit Hartwachsöl
Für die Oberflächenbehandlung habe ich mich bewusst gegen Lack und für ein Hartwachsöl entschieden. Lack blättert bei einer Werkbank irgendwann ab, wenn mal ein Werkzeug draufknallt – Öl lässt sich dagegen jederzeit partiell nachölen, ohne die ganze Bank abschleifen zu müssen. Hartwachsöl vereint dabei die Vorteile eines Öls, das tief ins Holz einzieht, mit denen von natürlichen Wachsen, die einen dünnen, robusten Schutzfilm auf der Oberfläche bilden.
Mein Ablauf
Entstauben (siehe oben) – Voraussetzung für ein gleichmäßiges Ergebnis
Auftragen mit einem weichen Küchenschwamm – das Öl wurde in kreisenden Bewegungen ins Holz einmassiert
Ca. 30 Minuten einwirken lassen
Abwischen mit einem weichen Baumwolltuch – jeder Balken wurde nochmal komplett abgewischt, um Rückstände vom Hartwachsöl zu entfernen. Ohne diesen Schritt bleibt die Oberfläche klebrig, statt trocken und griffig abzuschließen.
⚠️ Sicherheitshinweis: Mit Öl getränkte Schwämme und Tücher können sich selbst entzünden. Niemals zusammenknautschen und in den Müll werfen – flach im Freien austrocknen lassen oder in einem luftdichten Metallbehälter aufbewahren.
Wie viel Öl braucht man?
Bei ca. 11–12 m² Gesamtoberfläche (alle Balken zusammen) und einem Verbrauch von grob 90–110 ml pro m² für zwei Anstriche kommt man auf ca. 1,1–1,2 Liter Gesamtbedarf. Da frisch geschliffenes Altholz – vor allem an den vielen Stirnseiten – überdurchschnittlich viel Öl aufsaugt, lohnt sich ein Puffer: lieber 1,5 Liter (oder zwei 1-Liter-Dosen) einplanen statt knapp auf eine einzelne 1-Liter-Dose zu kalkulieren.
📸 Die Balken beim Ölen mit dem Küchenschwamm
📸 Die Balken vorher und nach dem einölen
Nachdem das Hartwachsöl ein paar Tage lang wirklich gut durchtrocknen konnte, ging es an den nächsten großen Schritt: den Zusammenbau der Werkbank.
🔩 Vorbereitung: Schrauben vorbohren und versenken
Bei der Stärke der Balken war schnell klar: Ohne Vorbohren geht hier nichts. Zusätzlich wollte ich, dass die Schraubenköpfe versenkt werden, damit am Ende alles wirklich saubere aussieht – keine vorstehenden Köpfe, an denen man sich später stößt oder hängen bleibt.
👉 Die größere Herausforderung war aber die Planung: An mehreren Stellen laufen die Schrauben aus zwei unterschiedlichen Seiten fast auf gleicher Höhe durch denselben Balken. Bevor ich überhaupt zum Bohrer gegriffen habe, musste ich mir also genau überlegen, wo welche Schraube durch den Balken geht, damit sich die Bohrungen im Inneren nicht gegenseitig kreuzen.
Nachdem alles saubere auf den Balken eingezeichnet war, ging es in zwei Schritten ans Bohren:
Forstnerbohrer (Ø 20 mm), ca. 4 cm tief – für die Vertiefung, in der später der Schraubenkopf inklusive Tellerkopf komplett im Holz verschwindet.
Spiralbohrer (Ø 5 mm, 200 mm lang), komplett durch den Balken – als Führung für die eigentliche Schraube.
Vorbereitend habe ich an jeder Stelle direkt eine 8×140 mm Holzschraube mit 20 mm Tellerkopf eingesetzt. Bei dieser Länge und Stärke konnte ich mir sicher sein, dass später alles bombenfest zusammenhält.
📸 Die vorgebohrten Löcher mit vormontierten Schrauben
📏 Erneute Maßkontrolle vor der Montage
An dieser Stelle holte mich noch einmal die Kettensägen-Nachbesserung ein (siehe oben): Weil ich die Balken erst mit der Kettensäge zugeschnitten und danach mit der Kappsäge nachgearbeitet hatte, waren alle Bauteile minimal kürzer geworden. Das bedeutete, dass ich vor der Montage noch einmal alle Maße gegeneinander prüfen musste, damit am Ende auch wirklich alles zusammenpasst.
Dabei stellte sich schnell heraus: Zwei der oberen Balken mussten noch einmal um 6 mm gekürzt werden. Nach dem Kürzen habe ich die neuen Schnittkanten natürlich noch einmal mit Hartwachsöl behandelt, damit auch dort kein ungeschütztes Holz offen blieb.
📸 Die Einzelteile grob zusammengelegt – ein erster Überblick, wie die Werkbank aus den Bauteilen aufgebaut ist
🔨 Der Zusammenbau des Gestells
Fertig zusammengebaut bringt das Gestell gut 150 kg auf die Waage – zu schwer, um es irgendwo draußen zusammenzubauen und dann noch an seinen Platz zu wuchten. Deshalb habe ich das Gestell direkt in der Werkstatt aufgebaut, an der Stelle, an der die Werkbank später auch stehen sollte.
Das Verschrauben der einzelnen Teile war stellenweise deutlich schwieriger als gedacht: Die alten Balken sind teilweise extrem hart, und Schrauben dieser Größe hineinzudrehen ist entsprechend kein Spaziergang.
⚠️ Werkzeug-Odyssee: Mein Akkuschrauber hat hier schnell aufgegeben. Also musste die Bohrmaschine mit Bit-Aufsatz übernehmen – doch auch die lief irgendwann heiß. Am Ende habe ich die letzten Umdrehungen mit einer Ratsche und passendem Bit-Aufsatz von Hand festgezogen.
💡 Warum das wichtig ist: Gerade die letzten Millimeter entscheiden über die Stabilität. Erst dabei zieht sich der äußere Balken wirklich fest an den anderen heran – und es entsteht eine wirklich stabile Verbindung. Auf Leim habe ich komplett verzichtet: Bei der Größe und Anzahl der Schrauben ist die Verbindung auch ohne stabil genug.
📸 Das fertig zusammengebaute Gestell der Werkbank
🪵 Die Arbeitsplatte: Auswahl und Zuschnitt
Erst nachdem das Gestell endgültig stand, konnte ich die Arbeitsplatte exakt ausmessen. Durch die kleinen Anpassungen bei den unteren Balken kam am Ende ein Maß von 179 × 119 cm heraus.
Meine Wahl fiel auf eine Multiplexplatte in 21 mm Stärke – stabil, formstabil und optisch passend zu den massiven Balken darunter. Zugeschnitten habe ich sie mir direkt im Baumarkt.
⚠️ Das böse Erwachen kam erst beim Verladen: Die fertig zugeschnittene Platte war rund 2 cm zu breit, um sie ins Auto zu bekommen. Also ging es noch einmal zurück in den Baumarkt, um die Platte auf der langen Seite in zwei Teile durchschneiden zu lassen.
💡 Das war zum Glück kein Problem für die spätere Stabilität: Im Gestell sitzt oben genau an dieser Stelle ein zusätzlicher Querbalken, auf dem sich beide Plattenhälften an der Stoßstelle saubere festschrauben lassen.
Befestigt habe ich die Multiplexplatte mit 5×60 mm Schrauben. Auch hier wurde jedes Loch zuerst mit einem kleinen Bohrer vorgebohrt und anschließend mit einem Senkbohrer-Satz mit einstellbarem Tiefenanschlag nachbearbeitet, damit die Schraubköpfe am Ende plan mit der Plattenoberfläche abschließen.
📸 Die Montage der Multiplexplatte auf dem Gestell
🛢️ Ölen der Arbeitsplatte & Fertigstellung
Der letzte Schritt war dann naheliegend: Auch die Multiplexplatte habe ich mit demselben Hartwachsöl behandelt wie zuvor schon die Balken. Zum Abschluss noch einmal mit einem weichen Baumwolltuch abgerieben – und dann war die Werkbank endlich fertig.
📸 Das Einlassen der Multiplexplatte mit Hartwachsöl
📸 Die komplett fertige Werkbank
✅ Fazit
Für mich ist das Ergebnis wirklich sehr gut geworden. Das einzige „Problem“ sind die alten Balken selbst: Auch wenn man sie noch so sauber zuschneidet, sind viele davon leicht in sich verdreht, wodurch die Passgenauigkeit an manchen Stellen nicht ganz optimal ist. Aber genau das macht für mich den Charme einer selbstgebauten Werkbank aus Altholz aus.
Wenn du selbst schon mal mit Altholz gebaut hast oder Erfahrungen mit dem Vorbohren und Versenken bei besonders harten Balken teilen willst, freue ich mich über deinen Kommentar! 👇
Wer Home Assistant schon eine Weile nutzt, kennt das Gefühl: Das Dashboard funktioniert. Es schaltet. Es zeigt an. Aber es sieht aus wie ein Flickenteppich. Hier ein Standard-Button, dort eine custom:button-card mit zufälliger Farbe, und drüben eine Tile-Card, die sich nicht ganz einordnen lässt. Alles selbst gebaut, alles irgendwie gewachsen – und wenn man nach drei Wochen etwas ändert, hat man schon vergessen, warum damals welche Farbe gewählt wurde.
Ich kenne dieses Problem gut. Mein Dashboard hat sich über Monate entwickelt: Mähroboter, Bewässerung, Jacuzzi, Saugroboter, Lichtsteuerung – überall eigene Karten, eigene Stile, eigene Logiken. Bis mir irgendwann aufgefallen ist: Ich selbst weiß manchmal nicht auf den ersten Blick, ob ein Button gerade aktiv oder deaktiviert ist – und ob Grün hier „läuft“ oder „Automatik an“ bedeutet.
Das war der Moment, in dem ich beschlossen habe, einen Dashboard Button Style Guide für mein gesamtes Home Assistant zu entwickeln. Nicht als akademische Übung – sondern weil ein konsequentes visuelles System das Bedienen meines Smart Homes jeden Tag einfacher macht. In diesem Beitrag zeige ich dir, wie mein Style Guide aufgebaut ist, welche Typen ich definiert habe und warum das auch für dich Sinn ergibt.
🎨 Warum ein Style Guide in Home Assistant Sinn macht
🔧 Das Problem ohne Styleguide
Home Assistant ist unglaublich flexibel. Genau das ist Stärke und Schwäche zugleich. Man kann jeden Button individuell gestalten – und tut es auch, weil jede Karte aus einem anderen Kontext, einer anderen Anforderung oder einfach einer anderen Laune entstanden ist.
👉 Das Ergebnis nach 3 Jahr aktivem Dashboard-Basteln:
❌ Gleiche Funktion, fünf verschiedene Optiken je nach Bereich
❌ Farben ohne Bedeutung – Grün ist manchmal „an“, manchmal „Automatik aktiv“, manchmal einfach „schön“
❌ Neue Karten passen nie wirklich zum Rest
❌ Gäste (oder die Familie) verstehen das Dashboard nicht intuitiv
🎯 Was ein Style Guide löst
Ein Style Guide ist im Kern eine einfache Vereinbarung mit sich selbst: Gleiche Bedeutung = gleiche Optik. Nichts mehr, nichts weniger.
Konkret bedeutet das:
✅ Grün bedeutet immer: Gerät ist aktiv / läuft
✅ Rot bedeutet immer: Automatisierung ist deaktiviert – Achtung!
✅ Violett bedeutet immer: Anzeigewert, nicht steuerbar
✅ Blau bedeutet immer: einstellbarer Wert oder Uhrzeit
✅ Neue Karten bauen sich schneller, weil das Rezept schon existiert
Das ist kein Perfektionismus. Es ist einfach weniger Denken zur Laufzeit – für dich und für alle anderen, die das Dashboard nutzen.
🧩 Die fünf Button-Typen
Mein Style Guide basiert auf custom:button-card und kennt fünf reguläre Typen plus drei Sonderfälle. Jeder Typ hat eine feste Farbbedeutung und ein klares Einsatzgebiet.
🟢 Typ A – Schaltbutton (Gerät an/aus)
Das ist der häufigste Button im Dashboard. Er schaltet ein Gerät oder eine Funktion ein und aus – und zeigt den Zustand klar über die Hintergrundfarbe an.
⬛ Aus / Inaktiv: Dunkelgrau (#3a3a3a), graues Icon, grauer Text
🟢 An / Aktiv: Dunkelgrün (#2e7d32), weißes Icon, weißer Text
Einsatzbeispiele: Dusche, Filterpumpe, Rasensprenger, Heizautomatik – also alles, was ein einfaches Ein/Aus hat.
⚠️ Wichtiger Hinweis bei Gardena-Ventilen: Valves nutzen value: open / value: closed – nichton/off! Das ist ein klassischer Fehler beim Übertragen der Vorlage.
🔴 Typ B – Automatisierungs-Schalter
Dieser Typ ist speziell für das Aktivieren und Deaktivieren von Home Assistant-Automationen gedacht. Hier ist Rot kein Fehler – Rot bedeutet: Automatik ist aus, du musst selbst eingreifen.
🟢 Aktiv: Grün (#2e7d32) – Automatisierung läuft
🔴 Deaktiviert: Rot (#c62828) – Automatisierung ist bewusst ausgeschaltet
Der Unterschied zu Typ A ist wichtig: Ein normaler Gerät-Button (Typ A) ist im Aus-Zustand grau – das ist neutral. Eine deaktivierte Automatisierung ist rot – das ist ein Signal, weil das System dann unbeaufsichtigt läuft.
Überall dort, wo man einen Wert sehen und per Tipp verändern will, kommt Typ C zum Einsatz. Der Hintergrund wechselt dynamisch in Abhängigkeit vom Wert – von Blau über Gelb bis Rot.
⬛ Wert = 0: Dunkelgrau – deaktiviert
🔵 Niedriger Wert: Blau (#1565c0) – normal / sicher
Einsatzbeispiele: Bewässerungsdauer in Minuten, PV-Schwellwert in kW, Temperaturziel in Grad.
👉 Die Schwellwerte für die Farbskala sind bewusst kontextabhängig: 15 Minuten Bewässerung ist „normal“, 15 kW Leistung wäre „kritisch“. Der Style ist gleich – die Logik dahinter passt sich dem Kontext an.
Manchmal will man nur einen Sensorwert anzeigen – ohne dass der Benutzer etwas tun kann oder soll. Typ D ist immer dunkelviolett (#4a148c) und hat keinen Pointer-Cursor.
Das Violett ist bewusst gewählt: Es taucht nirgendwo sonst im Dashboard auf und signalisiert damit eindeutig: Hier ist nichts zu drücken.
Der Zeit-Button zeigt eine konfigurierbare Uhrzeit an. Ein Tipp öffnet den nativen HA-Picker zur Änderung. Er ist immer blau (#1565c0) – derselbe Blauton wie Typ C – und damit als „einstellbar“ erkennbar.
Jedes größere System – Jacuzzi, Bewässerung, Heizung – hat genau einen zentralen Hauptschalter. Der Master-Switch weicht absichtlich von Typ A ab: Im Aus-Zustand ist er nicht grau, sondern rot.
Das ist eine bewusste Designentscheidung: Wenn das gesamte System ausgeschaltet ist, soll das sichtbar sein – nicht in eine neutrale Graufläche verschwinden.
Manche Aktionen haben keinen Zustand – sie werden einfach ausgeführt. Mähroboter starten, Saugroboter zurück zur Station schicken, eine Szene aktivieren. Diese Buttons sehen immer gleich aus, unabhängig davon was danach passiert.
Das Grundprinzip: Grauer Hintergrund (wie Typ A inaktiv), aber das Icon trägt die Bedeutungsfarbe.
Außenbeleuchtung bekommt eine eigene Farbbehandlung. Aktives Licht ist gelb (#ffd54f) – nicht grün. Das ist eine bewusste semantische Entscheidung: Gelb erinnert an Licht, an Helligkeit. Grün würde hier keine zusätzliche Information liefern und wäre verwechselbar mit einem normalen Schalter.
Diese kleine Abweichung zeigt, dass ein Style Guide kein starres Korsett ist – er hat Regeln, und er hat begründete Ausnahmen.
type: custom:button-cardentity: light.garten_garagename: Garageicon: mdi:outdoor-lamptap_action:action: nonehold_action:action: nonedouble_tap_action:action: nonestyles:card: - height: 60px - padding: 6px 10px - border-radius: 10px - cursor: defaultgrid: - grid-template-areas: '"i n" "i s"' - grid-template-columns: 32px auto - align-items: center - column-gap: 8pxicon: - width: 22px - height: 22pxstate: - value: "on"styles:card: - background: "#ffd54f"# Gelb = Licht an (bewusste Abweichung von Typ A) - border: 2px solid#fffffficon: - color: "#4c3c00"# Dunkelbraun für Kontrast auf Gelbname: - color: "#4c3c00" - value: "off"styles:card: - background: "#3a3a3a" - border: 1px solid#666icon: - color: "#9e9e9e"name: - color: "#cccccc"
🎨 Die Farbpalette auf einen Blick
Farbe
Hex-Code
Bedeutung
⬛ Dunkelgrau
#3a3a3a
Inaktiv / Aus (Typ A)
🟢 Dunkelgrün
#2e7d32
Aktiv / An (Typ A + B)
🔴 Dunkelrot
#c62828
Automation deaktiviert (Typ B), kritisch (Typ C)
🔵 Dunkelblau
#1565c0
Einstellbar / Zeit (Typ C + E)
🟡 Dunkelgelb
#f9a825
Warnstufe (Typ C), Pause (Momentary)
🟣 Dunkelviolett
#4a148c
Read-only Anzeige (Typ D)
💛 Gelb
#ffd54f
Licht aktiv (Licht-Status)
💡 Tipps für die Umsetzung
1. Mit einer View beginnen
Nicht alles auf einmal umbauen. Ich habe mit dem Jacuzzi-Dashboard angefangen – das war quasi die Referenz-Implementierung. Erst als ich sicher war, dass der Style funktioniert, habe ich ihn auf andere Views übertragen.
2. Den Styleguide als Dokument führen
Klingt nach Overkill, ist es aber nicht. Ein einfaches Markdown-Dokument mit den Typ-Definitionen, Farb-Codes und YAML-Vorlagen spart enorm viel Zeit – besonders wenn man drei Monate später eine neue Karte baut und sich nicht mehr an die genauen Hex-Codes erinnert.
3. Abweichungen begründen, nicht einfach machen
Das Gelb für Licht ist eine begründete Ausnahme – Gelb kommuniziert etwas, das Grün nicht könnte. Eine Ausnahme ohne Begründung ist hingegen einfach Inkonsistenz. Wenn du abweichst: Frage dich warum. Wenn die Antwort „weil es hier besser aussieht“ ist, ist das kein guter Grund.
🎨 Ergebnis
Hier einmal eine Daschboard wie es bisher ausgeschaut hat und dann das Ergebnis nach der Umsetzung vom Style Guide:
Alt:
Neu:
✅ Fazit
Ein Style Guide für Home Assistant klingt nach mehr Arbeit. In Wirklichkeit ist er weniger Arbeit – verteilt auf die Zukunft. Jede neue Karte entsteht schneller, weil das Rezept schon steht. Jeder Blick aufs Dashboard braucht weniger Interpretation, weil die Farben sprechen.
Das Wichtigste ist nicht Perfektion – sondern Konsistenz. Ein einfaches System, das überall gleich funktioniert, schlägt jeden aufwändigen Individualstil.
Hast du einen eigenen Style Guide für dein Dashboard entwickelt? Oder kämpfst du noch mit dem Flickenteppich? Schreib es gerne in die Kommentare – ich bin gespannt, welche Ansätze andere verfolgen.
Wer meinen letzten Beitrag gelesen hat, weiß: Unser Bestway Lay-Z-Spa Cabo ist voll in Home Assistant integriert und heizt quasi kostenlos mit PV-Überschuss, doch was noch fehlte war die Beschreibung wie diese Wellness Oase entstanden ist.
Bis Anfang dieses Jahres hatten wir jahrelang einen klassischen Gartenpool in unterschiedlichen Ausführungen. Da die Kinder ausgezogen sind, stand der Pool am Ende fast nur noch ungenutzt herum, machte aber jede Menge Arbeit. Die Entscheidung stand fest: Der Pool fliegt raus und stattdessen bauen wir an der gleichen Stelle ein großes, gemütliches Holzdeck zum Relaxen.
Und weil man nach handwerklicher Arbeit im Garten oder zum Abkühlen im Sommer einen Platz braucht, sollte zusätzlich zu dem neuen Holzdeck noch ein Jacuzzi und eine 40-Liter-Solardusche Platz finden.
Hier ist der ungeschönte Baubericht von den Erdarbeiten über die statischen Tücken eines Sandkastens bis hin zur perfekten millimetergenauen Anpassung an die Rasenkanten.
📸 Aus alt mach neu
🛠️ Phase 1: Die Abriss- und Erdarbeiten (Eine Heidenarbeit)
Der Platz, den ich mir für das neue Holzdeck ausgesucht hatte, war ein alter, gepflasterter Bereich von ca. 10 m². Schön war er schon lange nicht mehr und genutzt wurde er dementsprechend auch nicht. Den Jacuzzi einfach auf die alten Beton-Pflastersteine zu stellen, kam für mich überhaupt nicht infrage – das hätte optisch einfach nichts hergemacht und wäre spätestens bei der Montage der Solardusche zu einem echten Problem geworden.
Schritt 1: Die kompletten Beton-Pflastersteine auf den 10 m² herausreißen. Wer schon mal Betonpflaster geschleppt hat, weiß, wie verdammt schwer die Dinger sind.
Schritt 2: Darunter lag eine 2 bis 4 cm dicke Schicht alter Split. Auch die musste komplett weichen.
Als der Split weg war, kam eine Überraschung zum Vorschein: Der Großteil des Platzes war darunter komplett betoniert! Zuerst dachte ich an eine alte Sickergrube, doch meine Schwiegermutter brachte Licht ins Dunkel: Früher war hier einmal ein Freisitz.
Das Sandkasten-Problem in der Mitte
Die eigentliche Schmerzstelle zeigte sich genau in der Mitte dieses alten Freisitzes: Eine exakte Aussparung im Beton von 1,40 m × 1,40 m. Hier war früher der Sandkasten! Heute war das Loch einfach mit Erde gefüllt.
Statisch gesehen der absolute Albtraum für ein Whirlpool-Fundament. Ein gefüllter Jacuzzi wiegt rund 1,1 Tonnen auf einer winzigen Fläche. Hätte ich meine Tragkonstruktion einfach auf dieses Erdloch gestellt, wären die Stelzlager über kurz oder lang im weichen Boden eingesunken. Die Terrasse hätte sich unweigerlich verzogen.
Die Lösung: Ich habe das Erdloch gut 9 cm tief ausgehoben, eine Schicht frischen Split eingebracht und verdichtet. Darauf habe ich dann die alten Pflastersteine, die ich vorher herausgerissen hatte, im Loch sauber neu verlegt. Das Ergebnis: Eine durchgehend stabile, tragfähige Ebene aus Beton und Pflaster.
📐 Phase 2: Die Unterkonstruktion – Statik trifft auf Gefälle
Da der Untergrund nach vorne hin leicht abschüssig war, stand ich vor der nächsten Herausforderung. Das Deck musste am Ende absolut waagerecht sein – besonders für den Whirlpool, da das Wasser sonst schief im Becken steht.
Aus diesem Grund kamen zwei verschiedene Größen von verstellbaren Stelzlagern (Balkenlager PRO von gartenwelt_riegelsberger) zum Einsatz:
Im hinteren (höheren) Bereich: Größe 27–40 mm (90 Stück)
Im vorderen (tieferen) Bereich zum Ausgleichen: Größe 40–64 mm (40 Stück)
Insgesamt knapp 130 Stelzlager sorgen nun dafür, dass das Deck perfekt in Waage liegt.
Warum so viele Stelzlager und so enge Abstände?
Vielleicht fragst du dich, warum man für ein 10 m² Deck so extrem viele Stelzlager und Balken braucht. Die Antwort liegt wieder im enormen Gewicht des Jacuzzis.
Normalerweise verlegt man die Unterkonstruktion bei Terrassendielen in einem Abstand von 40 bis 50 cm. Unter dem Whirlpool habe ich das Raster jedoch massiv verdichtet und die Balken (Kiefer KDI, 45 × 70 mm, flach hingelegt für eine optimale Auflagefläche) alle 20 cm gesetzt! Dadurch verteilt sich das Gewicht des Pools auf unzählige Kreuzungspunkte. Auf jedes einzelne Stelzlager drückt am Ende nur ein kleiner Bruchteil des Gewichts. Hier biegt sich garantiert nichts durch.
🚫 Warum wir die teuren Abstandshalter nicht verbaut haben
Im Laufe der Planung hatte ich überlegt, spezielle Terrassendielen-Abstandshalter (Kunststoff-Clips zwischen Balken und Diele) zu kaufen. Bei über 500 benötigten Kreuzungspunkten hätte ich jedoch locker 600 bis 700 Stück kaufen müssen. Das stand finanziell in absolut keinem Verhältnis mehr zum Nutzen.
Wir haben das Ganze handwerklich clever und extrem günstig gelöst:
Konstruktiver Holzschutz durch Gefälle: Nach hinten wird das Deck von einer Mauer begrenzt, nach vorne schließt es an bestehende Rasenkantensteine an. Die Unterkonstruktion wurde so ausgerichtet, dass das Wasser durch ein minimales Gefälle in den Fugen ablaufen kann und keine Staunässe zwischen Dielen und Balken entsteht.
Fugenkeile statt Dauerelemente: Um beim Verschrauben der Douglasie-Dielen (2500 × 124 × 21 mm) überall exakt die wichtigen 5 mm Fugenbreite einzuhalten, kamen einfache, wiederverwendbare Kunststoff-Steckkreuze aus dem Baumarkt zum Einsatz. Nach dem Festschrauben zieht man sie einfach wieder raus – Kostenpunkt: keine 10 Euro.
🚿 Das Statik-Highlight: Die Solardusche bombenfest montiert
Ein weiteres wichtiges Element auf dem Deck ist die CCLIFE Solardusche mit 40 Litern Tankvolumen. Vollgefüllt bringt das gute Stück gut 45 bis 50 kg auf die Waage. Da die Dusche über 2 Meter hoch ist, wirken durch Hebelkräfte (Wind, Festhalten beim Duschen) enorme Kräfte auf den Standfuß. Schraubt man diese einfach nur in die 21 mm dünnen Dielen, reißen die Schrauben beim ersten Sturm aus.
Glücklicherweise lag genau an der Stelle, wo die Dusche hin sollte, ein Unterkonstruktionsbalken darunter. Ich habe direkt unter diese Stelle zwei zusätzliches Stelzlager gesetzt, um den Druck nach unten abzufangen und auch noch 3 Querbalken eingefügt.
Der Schrauben-Größen-Krimi
Die Dielen und flach liegenden Balken haben zusammen eine Stärke von 66 mm (21 mm Diele + 45 mm Balken). Der Standfuß der Dusche schlägt noch einmal mit 10 mm zu Buche. Macht eine Gesamttiefe von 76 mm.
Da es im Baumarkt keine 70 mm Schrauben in Edelstahl A2 gab, musste ich zu 80 mm Sechskant-Schlüsselschrauben greifen. Um zu verhindern, dass die Schraube unten aus dem Balken herausschaut und das darüberliegende Kunststoff-Stelzlager zerstört, habe ich einen einfachen Handwerkertrick angewendet: Ich habe unter jeden Schraubenkopf am Fuß der Dusche 3 dicke Edelstahl-Unterlegscheiben (M8) gestapelt. Dadurch wanderte die Schraube ca. 5 mm nach oben, sitzt perfekt tief im Holz und das Stelzlager bleibt absolut unberührt.
📐 Phase 3: Die millimetergenauen Randarbeiten
Eine Terrasse wirkt erst dann richtig professionell, wenn die Anschlüsse an die Umgebung stimmen. Da der Platz zu den Seiten hin nicht perfekt rechtwinklig verlief, musste an den Rändern Maßarbeit her:
Die schräge Seite: Hier habe ich im Zuge des Baus neue Rasenkantensteine gesetzt. Jede einzelne Douglasie-Diele wurde dann auf dieser Seite mit der Kappsäge im exakten Winkel so zugeschnitten, dass sie gut mit dem Kantenstein abschließt.
Die andere (gerade) Seite: Auch hier war der Verlauf der alten Pflastersteine ungenau. Ich habe zuerst die Terrassendielen komplett verlegt und danach Teile des angrenzenden Pflasters entfernt. Am Schluss habe ich die Pflastersteine mit dem Winkelschleifer und einer Diamanttrennscheibe so exakt zugeschnitten, dass sie nun nahtlos und ohne hässliche Lücken an die Dielen anstoßen.
📋 Materialliste: Was wurde für das Projekt benötigt?
Für alle, die ein ähnliches Projekt planen, habe ich hier noch einmal das gesamte benötigte Material und die wichtigsten Werkzeuge zusammengefasst:
Der Aufwand hat sich definitiv gelohnt. Die Kombination aus dem warmen Holzdeck, dem perfekt eingelassenen Jacuzzi und der Solardusche hat den ehemals hässlichen Freisitz komplett verwandelt. Dank der extrem dichten Unterkonstruktion steht der Whirlpool absolut sicher, und die Solardusche übersteht dank des Schrauben-Tricks auch den stärksten Windstoß.
Wenn du wissen willst, wie ich diesen Jacuzzi jetzt so automatisiert habe, dass er komplett kostenlos mit Solarstrom aufheizt, schau dir unbedingt meinen Artikel zur Home Assistant Jacuzzi-Integration an!
Hast du Fragen zum Unterbau, den Stelzlagern oder dem statischen Aufbau? Schreib es mir gerne unten in die Kommentare! 👇
Und ganz am Ende noch das Wichtigste … macht Euch vorher einen Plan von dem was ihr vorhabt, dann könnt ihr sehr sicher sein, dass ihr an „fast“ alles gedacht habt!
Wer einen Garten hat, kennt das Gefühl: Nach einem langen Tag einfach in den Jacuzzi sinken, warmes Wasser, Ruhe – pure Erholung. Doch bis es so weit ist, wartet man entweder ewig auf die richtige Wassertemperatur, oder man verschwendet Energie, weil der Whirlpool stundenlang unnötig heizt.
Ich habe das Problem gelöst – mit Home Assistant. In diesem Beitrag zeige ich dir, wie ich meinen Bestway Jacuzzi und meine Gartendusche vollständig automatisiert habe. Der Jacuzzi heizt nur dann, wenn genug Photovoltaik-Überschuss vorhanden ist – also praktisch kostenlos. Die Dusche schaltet sich nach einer konfigurierbaren Zeit automatisch ab. Beides lässt sich bequem per Dashboard steuern und überwachen.
Und wen es interessiert , es gibt noch einen Blogbeitrag, wie das Holzdeck und die Dusche entstanden sind ➡️ Vom alten Pool zum Wellness-Deck
📸 Dashboard der Wellness-Oase im Garten mit Jacuzzi und Dusche
🛁 Teil 1: Der smarte Jacuzzi
🔧 Ausgangsproblem
Der Bestway Lay-Z-Spa bringt von Haus aus keine nennenswerte Automatisierung mit. Man schaltet ihn ein, stellt die Zieltemperatur ein – und wartet. Das Aufheizen dauert je nach Ausgangstemperatur mehrere Stunden und kostet entsprechend Strom.
👉 Meine Probleme waren:
Heizen kostet Strom – idealerweise sollte das nur bei PV-Überschuss passieren
Wasser muss regelmäßig gefiltert werden – auch wenn die Heizung gerade nicht läuft
War der Jacuzzi manuell eingeschaltet, sollte die Automatisierung das nicht ignorieren oder gar den Spa unkontrolliert ausschalten
🔗 Die Bestway-Integration für Home Assistant
Die Grundlage für die gesamte Steuerung ist die Bestway-Integration für Home Assistant, die den Jacuzzi über die Cloud-API von Bestway einbindet. Die Installation erfolgt über HACS.
📸 Screenshot der Bestway-Integration
⚠️ Wichtiger Hinweis zum Modell: Mein konkretes Modell, der Bestway LAY-Z-SPA Energiespar-Whirlpool Cabo HydroJet, ist in der offiziellen Liste der unterstützten Geräte nicht aufgeführt. Die Integration funktioniert dennoch – jedoch nicht vollständig.
Das grundsätzliche Problem: Mehrere Entitäten liefern über die API keine zuverlässige Rückmeldung über den tatsächlichen Gerätezustand. Befehle lassen sich senden – ob sie angekommen sind und was das Gerät jetzt wirklich macht, meldet die API aber nicht immer zurück. Besonders deutlich wird das an der Filterpumpe: Hier musste ich sogar auf einen Mechanismus mit drei Ausschaltversuchen zurückgreifen, weil ein einzelner Befehl nicht garantiert zuverlässig ausgeführt wird.
Überall wo die API-Rückmeldung nicht verlässlich ist, habe ich denselben Workaround-Ansatz gewählt: interne Booleans als Zustandsspeicher, die von den Automationen selbst gesetzt werden – unabhängig davon was die API zurückmeldet. Das bekannteste Beispiel ist der Template-Switchswitch.v8_jacuzzi_spa_filter_ui in Kombination mit input_boolean.v8_jacuzzi_filter_state: Der Boolean ist die verlässliche Zustandsquelle für das Dashboard, nicht die API-Entität direkt.
Wer denselben oder einen ähnlichen Bestway-Jacuzzi besitzt und mit ähnlichen API-Problemen kämpft: Dieser Ansatz – eigene Zustandsspeicher statt API-Feedback – hat sich bei mir bewährt. Schreib gerne in die Kommentare welches Modell du hast und welche Erfahrungen du gemacht hast.
🎯 Ziele meiner Automatisierung
☀️ Jacuzzi heizt nur bei ausreichendem PV-Überschuss (≥ 2 kW für 2 Minuten)
⏹️ Automatisches Abschalten wenn Überschuss zu gering (< 1 kW für 3 Minuten)
⏱️ Mindestlaufzeit von 30 Minuten schützt vor ständigem Ein-/Ausschalten
💧 Regelmäßige Filterzyklen – unabhängig davon ob die Heizung läuft
🧠 War der Spa manuell eingeschaltet: Automatisierung greift nur in Heizung und Filter ein – Spa bleibt an
📧 E-Mail-Benachrichtigungen bei jedem automatischen Start, Stopp und bei Fehlern
📊 Übersichtliches Dashboard zur Steuerung und Statusanzeige
🌡️ Zieltemperatur erreicht → alles automatisch ausschalten, Neustart erst wenn das Wasser 2 °C abgekühlt ist und PV-Überschuss wieder ausreicht
💧 Regelmäßige Filterzyklen – unabhängig davon ob die Heizung läuft
🔌 Voraussetzungen – benötigte Entitäten
Diese Entitäten werden durch die Bestway-Integration in Home Assistant bereitgestellt und müssen bereits existieren:
switch.v8_jacuzzi_spa_power – Hauptschalter des Spas
sensor.em_available_for_heatpump – PV-Überschuss aus dem Energy Manager (mehr dazu weiter unten)
⚡ Zusammenspiel mit dem Energy Manager
Das Herzstück der PV-Steuerung ist der Sensor sensor.em_available_for_heatpump aus meinem Energy Manager Package. Dieser Sensor berechnet in Echtzeit, wie viel Solarstrom tatsächlich für weitere Verbraucher verfügbar ist:
Der entscheidende Vorteil: Läuft meine Wärmepumpe gerade, ist ihr Verbrauch bereits in der Haushaltslast enthalten. Der Sensor sinkt automatisch. Der Jacuzzi startet dann nur noch bei echtem Zusatzüberschuss – kein separater Konfliktschutz zwischen Wärmepumpe und Jacuzzi nötig.
🧩 Helper – automatisch durch das Package erstellt
Das Package erstellt beim Start alle benötigten Helfer automatisch. Kein manuelles Anlegen notwendig.
🔘 Steuer-Flags
input_boolean.jacuzzi_pv_auto – PV-Heizautomatik ein/aus (Standard: an). Perfekt für den Urlaubsfall.
input_boolean.jacuzzi_filter_auto – Filter-Automatik ein/aus (Standard: an). Unabhängig von der Heizautomatik.
input_boolean.jacuzzi_spa_started_by_pv – Merkt sich ob der Spa durch die PV-Automatik eingeschaltet wurde. Verhindert, dass ein manuell eingeschalteter Spa automatisch wieder ausgeschaltet wird.
input_boolean.jacuzzi_temp_reached – Merkt sich ob der letzte Stopp durch das Erreichen der Zieltemperatur ausgelöst wurde. Verhindert, dass der Spa sofor
t neu startet, bevor das Wasser 2 °C abgekühlt ist.
⚙️ Schwellenwerte & Konfiguration
input_number.jacuzzi_pv_start – PV-Überschuss zum Einschalten (Standard: 2,0 kW)
input_number.jacuzzi_pv_stop – PV-Überschuss zum Abschalten (Standard: 1,0 kW)
input_number.jacuzzi_min_runtime – Mindestlaufzeit vor automatischem Stopp (Standard: 30 Min)
input_number.jacuzzi_filter_duration – Dauer eines Filterzyklus (Standard: 60 Min)
🕐 Filterzeiten
input_datetime.jacuzzi_filter_time_1 – Erste tägliche Filterzeit (z. B. 08:00 Uhr)
input_datetime.jacuzzi_filter_time_2 – Zweite tägliche Filterzeit (z. B. 20:00 Uhr)
💡 Tipp: Die Heizphasen tagsüber liefern automatisch zusätzliche Filterzeit – die zwei konfigurierten Zyklen dienen als Mindestabsicherung für Tage ohne PV-Überschuss.
🤖 Die 8 Automationen im Überblick
1️⃣ PV-Überschuss EIN
Sobald der verfügbare PV-Überschuss die Start-Schwelle (Standard: 2 kW) für mindestens 2 Minuten überschreitet, startet der Jacuzzi automatisch. Der Debounce von 2 Minuten verhindert, dass kurze Schwankungen im Energieangebot den Spa ständig ein- und ausschalten.
👉 Besonderheit: War der Spa bereits manuell eingeschaltet, schaltet die Automatisierung ihn nicht nochmal ein – sie setzt nur Heiztemperatur und Filter. Der jacuzzi_spa_started_by_pv-Boolean merkt sich dabei, ob die Automatisierung den Spa selbst gestartet hat.
2️⃣ PV-Überschuss AUS
Fällt der Überschuss für 3 Minuten unter die Stop-Schwelle (Standard: 1 kW), schaltet die Automatisierung Heizung und Filter ab. Zusätzlich greift eine Mindestlaufzeit: Der Spa läuft immer mindestens 30 Minuten, bevor er automatisch gestoppt werden kann.
👉 War der Spa vor dem PV-Start bereits manuell eingeschaltet, wird er nicht ausgeschaltet – nur Heizung und Filter stoppen.
3️⃣ Filter-Zyklus
Täglich zu den zwei konfigurierten Zeiten startet automatisch ein Filterzyklus – unabhängig davon ob die Heizung gerade läuft. Die Automation ist dabei smart:
Spa war aus → Spa kurz einschalten, Filter starten, nach Ablauf der Filterdauer: Filter stoppen, Spa wieder ausschalten (außer die Heizung ist inzwischen aktiv)
Spa war bereits an → nur Filter starten und nach Ablauf stoppen
Filter läuft bereits (z. B. wegen Heizung) → Zyklus wird übersprungen
4️⃣ Auto-Modus deaktiviert
Schaltet man jacuzzi_pv_auto manuell aus während der Spa läuft, stoppt diese Automation sauber Heizung und Filter. Den Spa selbst schaltet sie nur aus wenn er ursprünglich durch die PV-Automatik gestartet wurde – war er manuell eingeschaltet, bleibt er an.
5️⃣ Fehler-Benachrichtigung
Meldet der Spa einen Fehler (z. B. den gefürchteten E02 bei verstopfter Filterkartusche), kommt sofort eine E-Mail-Benachrichtigung und ein Eintrag im System-Log.
💡 Tipp zur Filterpflege: Die Filterkartusche sollte spätestens alle 2–3 Tage gereinigt werden, sonst kommt es schnell zu Fehlern durch schlechten Durchfluss.
6️⃣ Zieltemperatur sofort übernehmen
Ändert man die Zieltemperatur im Dashboard während der Spa bereits läuft, soll die neue Temperatur sofort an den Thermostat weitergegeben werden – ohne auf den nächsten automatischen Start warten zu müssen.
Diese Automation überwacht input_number.jacuzzi_target_temp auf Änderungen. Ist der Spa zu diesem Zeitpunkt eingeschaltet, wird die neue Temperatur unmittelbar per climate.set_temperature gesetzt. Ist der Spa aus, passiert nichts – beim nächsten Einschalten greift dann wie gewohnt die PV-EIN-Automation.
7️⃣ Zieltemperatur erreicht – alles ausschalten
Damit der Jacuzzi nicht eingeschaltet bleibt, wenn die Zieltemperatur erreicht ist und unnötiger Stromverbrauch verbraucht wird, gibt es diese zusätzliche Automatisierung.
Diese neue Automation löst das Problem mit einer Hysterese-Regelung: Sobald climate.v8_jacuzzi_spa_thermostat in den Zustand idle
wechselt (= Zieltemperatur ist erreicht), schaltet die Automation alles ab: Heizung off, Filter 3× off, Spa Power off. Gleichzeitig wird der interne Merker jacuzzi_temp
_reached gesetzt.
👉 Die Automation greift nur wenn jacuzzi_pv_auto aktiv ist – bei manuellem Betrieb bleibt das normale Thermostat-Verhalten (heat → idle → heat) erhalten.
8️⃣ Temperatur gesunken – automatischer Neustart
Das Gegenstück zu Automation 7: Sobald die Wassertemperatur um 2 °C unter die Zieltemperatur gefallen ist – und das für mindestens 1 Minute stabil bleibt –
startet der Jacuzzi automatisch neu, sofern alle Bedingungen erfüllt sind:
PV-Auto ist aktiv
Der Merker jacuzzi_temp_reached ist gesetzt (kein versehentlicher Neustart aus anderen Szenarien)
Spa ist verbunden und fehlerfrei
PV-Überschuss ist wieder ausreichend (≥ Start-Schwelle)
💡 Wichtig: Reicht der PV-Überschuss beim 2-°C-Abfall noch nicht aus, bleibt der Spa aus und wartet. Sobald der Überschuss wieder steigt und die Temperatur noch 2 °C unter Ziel liegt, springt entweder diese Automation an – oder die normale PV-EIN-Automation übernimmt. Ein Doppelstart wird durch den jacuzzi_temp_reached-Merker sicher verhindert.
🖥️ Das Jacuzzi-Dashboard
Das Jacuzzi-Dashboard ist ein extra Dashboard aufgebaut und nutzt das Theme Nordic. Es ist über eine klickbaren Heading direkt aus meinem Hauptdashboard ereichbar. Alle Buttons basieren auf custom:button-card.
📸 Screenshot des Jacuzzi-Dashboard
Steuer-Buttons
Die Steuer-Buttons sind in einem 2-Spalten-Grid angeordnet. Jeder Button zeigt Icon, Name und Zustand in einer kompakten 60px-Karte:
Jacuzzi Power – grün wenn an, dunkel wenn aus. Tippen schaltet den Spa direkt um.
Heizung – rot bei aktiver Heizung, orange im Idle-Modus (Zieltemp erreicht, Thermostat wartet), dunkel wenn aus. Tippen ruft das Script jacuzzi_heat_toggle auf, das Heizung und Filter gemeinsam schaltet.
Filterpumpe – blau wenn aktiv, dunkel wenn aus. Arbeitet über den Template-Switch switch.v8_jacuzzi_spa_filter_ui als verlässliche Zustandsquelle (siehe API-Hinweis oben).
Bubble-Modus – orange wenn MAX, dunkel wenn OFF. Tippen wechselt direkt zwischen OFF und MAX.
Massagefunktion – lila wenn aktiv, dunkel wenn aus.
📸 Die fünf Steuer-Buttons – verschiedene Zustände sichtbar
📌 Übersichts-Widget auf dem Hauptdashboard
Auf meinem zentralen Tablet-Dashboard habe ich zusätzlich ein kompaktes 4-Button-Widget eingebaut, das auf einen Blick den wichtigsten Status von Jacuzzi und Dusche zeigt – ohne in die Detailansicht wechseln zu müssen. Die vier Buttons sind in einem 2-Spalten-Grid angeordnet:
Jacuzzi Power – Hauptschalter, grün wenn an
Jacuzzi Status – Der Button zeigt z. B. „Heizung AN · Filter AN · Massage AN“ wenn mehrere Funktionen aktiv sind, oder schlicht „Alles aus“. Er reagiert nicht auf Tippen – er dient nur der Anzeige. Die Kartenfarbe wechselt zu Blau sobald mindestens eine Funktion aktiv ist.
Dusche – Ventilstatus, „Ein“ oder „Aus“
Jacuzzi Heizung – zeigt Ist- und Solltemperatur direkt im Button: „Ist: 34°C / Soll: 37°C“ – so sieht man sofort wie weit der Jacuzzi noch aufheizen muss
Der Heizungs-Button ist dabei besonders praktisch: Dank einem state_display-Template werden Ist- und Zieltemperatur direkt in der Statuszeile angezeigt. Kein Aufklappen, kein More-Info nötig.
📸 4-Button-Übersichts-Widgets auf dem Hauptdashboard
⚡ Stromverbrauch im Blick
Auf der Jacuzzi-Detailseite habe ich drei Verbrauchs-Anzeigen eingebaut, damit ich jederzeit sehe was der Jacuzzi gerade und in den letzten Tagen verbraucht hat:
Gauge – aktueller Verbrauch: Zeigt die aktuelle Leistungsaufnahme in Watt an. Farblich abgestuft: grün (Normalbetrieb), gelb (erhöht), rot (Volllast).
Stündliche Übersicht (24h): Ein Balkendiagramm auf Stundenbasis zeigt den Leistungsverlauf der letzten 24 Stunden – ideal um zu sehen, wann die Heizphasen und Filterzyklen stattgefunden haben.
14-Tage-Übersicht: Ein Tagesbalken-Diagramm der letzten 14 Tage zeigt den täglichen Energieverbrauch in kWh. Auf einen Blick sieht man, an welchen Tagen der Jacuzzi viel oder wenig PV-Überschuss genutzt hat.
Meine Gartendusche ist Teil meiner Gardena Gartenbewässerung. Die gesamte Bewässerungsanlage wird über die Gardena Integration in Home Assistant eingebunden – damit lassen sich alle Ventile der Anlage direkt aus HA heraus steuern.
Die Gartendusche hat dabei ein eigenes dediziertes Ventil in der Bewässerungsanlage, das unabhängig von den anderen Bewässerungskreisen angesteuert werden kann. In Home Assistant erscheint es als valve.morpheus_reich_valve_4 bzw. Dusche nach Umbenennung.
📸 Screenshot der Gardena Integration in Home Assistant
Das Problem: Wer kennt das nicht – man dreht die Dusche auf, geht kurz ins Haus und vergisst sie oder man nutzt die Dusche nur kurz und es liegt die ganze Zeit der Wassserdruck an. Gerade bei einer Gartendusche, die über ein steuerbares Ventil läuft, ist eine automatische Abschaltung ein Muss.
Außerdem liefert das Ventil intern die Zustände „open“ und „closed“ – für ein deutschsprachiges Dashboard möchte ich lieber „Ein“ und „Aus“ sehen.
🎯 Ziele
⏱️ Dusche schaltet sich nach konfigurierbarer Zeit automatisch ab (Standard: 60 Minuten)
🔢 Laufzeit per Eingabefeld einstellbar (5–180 Minuten) – kein Schieberegler
🔄 Öffnet man die Dusche erneut während der Timer läuft, startet der Countdown von vorne
🏷️ Dashboard-Button zeigt „Ein“ / „Aus“ statt der internen Zustände „open“ / „closed“
🧩 Helper
input_number.dusche_laufzeit – Einstellbare Laufzeit bis zum automatischen Abschalten. Standard: 30 Min, Bereich: 5–180 Min, Eingabe als Zahlenfeld (kein Schieberegler).
🤖 Die Automation: Auto-Aus nach Laufzeit
Die Automation ist einfach und zuverlässig aufgebaut:
Trigger: Ventil wird geöffnet
Action: Warte die konfigurierte Laufzeit, prüfe ob das Ventil noch offen ist, schließe es dann automatisch
mode: restart – Wird die Dusche erneut geöffnet während der Timer läuft, startet der Countdown neu. Es läuft immer nur ein Timer gleichzeitig.
Die Condition vor dem Schließen ist wichtig: Hat jemand die Dusche in der Zwischenzeit manuell geschlossen, passiert beim automatischen Schließversuch nichts – kein unnötiger Schaltvorgang.
🖥️ Die Dashboard-Button
Die Button folgt dem gleichen Design wie die Jacuzzi-Buttons: grüner Hintergrund wenn die Dusche läuft, dunkler Hintergrund wenn sie geschlossen ist. Der interne Zustand „open“ / „closed“ wird per state_display-Template in „Ein“ / „Aus“ übersetzt. Und dann noch der Button über den die Laufzeit eingestellt werden kann.
📸 Screenshot des Dusche-Buttons im Dashboard – sowie der Laufzeit
Mit relativ wenig Aufwand ist aus einem gewöhnlichen Garten-Jacuzzi und einer Dusche eine vollständig smarte Wellness-Oase geworden. Der Jacuzzi heizt praktisch kostenlos mit Solarstrom, filtert das Wasser zuverlässig auch an sonnenarmen Tagen – und schaltet sich bei manueller Nutzung einfach gar nicht erst in den Weg. Die Gartendusche vergisst sich nicht mehr von selbst.
Was mich dabei am meisten überzeugt: Das System ist robust gegenüber manuellen Eingriffen. Wer den Spa von Hand einschaltet, wird nicht von der Automatisierung überrascht. Wer die Dusche früher zudreht als geplant, muss sich nicht um den laufenden Timer sorgen. Die Automatisierung hilft – sie übernimmt nicht die Kontrolle.
Hast du ähnliche Geräte im Garten? Ich freue mich über Fragen und Feedback in den Kommentaren! 👇
Als Besitzer eines automatischen Mähroboters steht man vor der Herausforderung, ihn möglichst effizient, wetterabhängig und zeitschonend zu steuern.
In meinem Smart Home nutze ich Home Assistant, um meinen Green Cruiser Mähroboter (Gardena smart SILENO city, 500 m²) perfekt in meinen Alltag zu integrieren.
Heute möchte ich dir meine finale Automatisierungslösung vorstellen, die ich über mehrere Wochen optimiert habe. Ich erkläre dir genau, wie die Steuerung funktioniert, welche Automatisierungen ich dafür eingesetzt habe, welche Helfer-Entitäten dabei eine wichtige Rolle spielen und wie die Benutzeroberfläche zur Steuerung aufgebaut ist.
🔧 Ausgangsproblem
Der Gardena Mähroboter bringt von Haus aus ein eigenes Scheduling-System mit.
Dieses steuert:
wann der Roboter fährt
wann er lädt
wann er pausiert
👉 Problem:
Zeitplan aktiv → ✅ gutes Ladeverhalten, ❌ automatisches Starten
Zeitplan deaktiviert → ✅ keine Eigenstarts, ❌ Ladeverhalten teilweise suboptimal
Zusätzlich:
der Zeitplan lässt sich nur „pausieren“
wird aber im Hintergrund teilweise wieder automatisch aktiviert
🎯 Ziele meiner Automatisierung
✅ Mähen nur in festgelegten Zeitfenstern
🌧️ Kein Mähen bei Regen
🔋 Automatisches Fortsetzen des Mähens nach dem Aufladen
⚡ Manuelles, spontanes Mähen für eine selbstgewählte Dauer unabhängig von Zeitfenstern
🛑 Verhindern unerlaubter Starts außerhalb erlaubter Zeitfenster ohne expliziten Home Assistant-Trigger
📊 Übersichtliche Bedienelemente und Statusanzeigen in Home Assistant
👉 Home Assistant soll die einzige Steuerinstanz sein – ohne unkontrolliertes Eigenverhalten des Roboters
🧩 Architektur meines Systems
Das gesamte System basiert auf fünf zentralen Bausteinen:
Damit die Automatisierungen flexibel und nutzerfreundlich sind, verwende ich verschiedene Helper in Home Assistant:
🔘 1. Steuer-Flags
▶️ input_boolean.mahroboter_spontan_akti Steuert den manuellen, spontanen Mähmodus. Wird dieser aktiviert, läuft der Mäher unabhängig vom Zeitfenster.
📌 input_boolean.mahroboter_hat_gemaeht Merkt sich, ob der Mäher bereits gemäht hat – wichtig, um nach vollständigem Akku-Laden richtig zu entscheiden.
⏱ 2. Zeitsteuerung
🕒 input_datetime.mahroboter_startzeit und input_datetime.mahroboter_endzeit Dienen zur Definition der erlaubten Mähzeitfenster. So kann man jederzeit bequem per UI Start- und Endzeit anpassen.
⏳ 3. Spontanmodus Parameter
⏱ input_number.mahroboter_spontan_dauer Einstellbarer Timer, wie lange das spontane Mähen laufen soll.
📅 input_datetime.mahroboter_ende_spontan Speichert das Ende des spontanen Mähvorgangs zeitlich ab, damit es in der UI angezeigt werden kann.
Diese Helper sind essentiell, um dynamisch und intelligent auf Zeit, Regen und Batterielevel reagieren zu können.
✅ klare Freigabe / Sperre ✅ direkt im Dashboard sichtbar ✅ einfache Integration in Automationen
⚙️ Die fünf finalen Automatisierungen im Detail
1. 🌱 Intelligente Regen-, Akku- und Zeitsteuerung
Diese Automatisierung sorgt dafür, dass der Green Cruiser nur im definierten Zeitfenster mäht, bei Regen sofort ins Dock zurückkehrt und nach vollem Akku-Aufladen sein Mähen fortsetzt.
Trigger:
⏰ Startzeit erreicht (input_datetime.mahroboter_startzeit)
🏁 Endzeit erreicht (input_datetime.mahroboter_endzeit)
🌧 Regen beginnt
☀️ Regen ist mindestens 2 Stunden vorbei
🔋 Akku voll (>95%)
Bedingungen & Ablauf:
➡️ Start bei passenden Bedingungen innerhalb Zeitfenster
🚫 Kein Start bei Regen
🔄 Fortsetzung nach dem Laden
🛑 Rückkehr bei Regen oder Endzeit
Diese Logik sorgt für maximale Effizienz: Kein Mähen bei Regen, nur im definierten Fenster, und automatische Fortsetzung nach Laden.
👉 Diese Automation funktioniert unabhängig vom Zeitfenster
→ daher war eine zusätzliche Schutzlogik notwendig
3. 🔋 Spontan – Neustart nach Laden
Während einer aktiven Spontan-Session kann der Roboter zwischendurch laden. Damit er danach automatisch weitermäht, gibt es diese eigenständige Automation.
⚠️ Warum eine eigene Automation? Früher war der Akku-Trigger in „Spontan mähen für Dauer“ integriert. Durch den mode: restart brach ein Akku-Trigger den laufenden Delay ab – der Code, der spontan_akti auf OFF setzt, wurde nie erreicht. Das Flag blieb dauerhaft ON und blockierte die gesamte Zeitsteuerung.
Trigger:
🔋 Akku > 95 %
Bedingungen:
▶️ spontan_akti ist ON
📌 hat_gemaeht ist ON
🏠 Roboter ist docked
⏱ Endzeit der Session noch nicht erreicht
Ablauf:
🚀 Start-Button drücken
👉 Wichtig:mode: single verhindert parallele Ausführungen und greift nicht in den laufenden Delay von Automation 2 ein.
alias: "Mähroboter: Spontan – Neustart nach Laden"description: Startet nach vollständigem Laden neu wenn Spontan-Session noch aktiv isttriggers: - trigger: numeric_stateentity_id: sensor.green_cruiser_battery_level_2above: 95conditions: - condition: stateentity_id: input_boolean.mahroboter_spontan_aktistate: "on" - condition: stateentity_id: input_boolean.mahroboter_hat_gemaehtstate: "on" - condition: stateentity_id: lawn_mower.green_cruiser_lawn_mowerstate: docked - condition: templatevalue_template: > {{ now() < states('input_datetime.mahroboter_ende_spontan') | as_datetime }}actions: - action: button.presstarget:entity_id: button.green_cruiser_start_mowing_nowmode: single
4. 🛡 Unerlaubten Start blockieren
Manche Mäher starten auch mal spontan oder ungewollt. Diese Automatisierung verhindert genau das – auch unter realen Bedingungen mit verzögerten Zustandswechseln und Cloud-abhängigem Verhalten.
⏳ kurze Wartezeit, bis der Mäher in einem stabilen Zustand ist
🔁 Stop-Befehl wird gezielt und mehrfach gesende
🧠 robuste Logik, die nicht von einem einzelnen Zustand abhängig ist
🏠 Rückfahrt zum Dock wird aktiv erzwungen
Im praktischen Einsatz hat sich gezeigt, dass der Mähroboter nach dem Start nicht sofort auf Steuerbefehle reagiert und intern verschiedene Zwischenzustände durchläuft (z. B. Start, Bewegung, Positionierung).
Daher wurde die Automatisierung bewusst so aufgebaut, dass:
⏱ genügend Zeit für stabile Zustandswechsel eingeplant wird
🔁 Stop-Befehle wiederholt gesendet werden (Retry-Mechanismus)
📡 auch Cloud- oder Integrationsverzögerungen berücksichtigt werden
🔍 Debug-Informationen zur Analyse ausgegeben werde
👉 Dadurch wird sichergestellt, dass der Mähroboter auch bei Änderungen durch Firmware- oder Integrations-Updates zuverlässig gestoppt wird.
alias: "Mähroboter: Unerlaubten Start blockieren mit Notifications"description: ""triggers: - trigger: stateentity_id: lawn_mower.green_cruiser_lawn_mowerto: mowingconditions: - condition: stateentity_id: input_boolean.mahroboter_spontan_aktistate: "off" - condition: templatevalue_template: > {% set now_time = now().time() %} {% set start = strptime(states('input_datetime.mahroboter_startzeit'), '%H:%M:%S').time() %} {% set end = strptime(states('input_datetime.mahroboter_endzeit'), '%H:%M:%S').time() %} {% if start <= end %}{{ now_time < start or now_time > end }} {% else %}{{ now_time > end and now_time < start }}{% endif %}actions: - data:level: warningmessage: > MC: Fremdstart erkannt → adaptive Prüfung startet State={{ states('lawn_mower.green_cruiser_lawn_mower') }}, Time={{ now() }}action: system_log.write - data:title: Achtung - Mähroboter unerlaubter Startmessage: > Der Mähroboter hat außerhalb der erlaubten Zeit gestartet ({{ now().strftime('%H:%M') }} Uhr).target: [email protected]action: notify.kottlowski_gmail_com - repeat:while: - condition: templatevalue_template: > {{ states('lawn_mower.green_cruiser_lawn_mower') not in ['docked', 'charging', 'returning'] }} - condition: templatevalue_template: | {{ repeat.index <= 6 }}sequence: - target:entity_id: button.green_cruiser_return_to_dock_2action: button.press - data:level: warningmessage: > MC: STOP Versuch {{ repeat.index }} State={{ states('lawn_mower.green_cruiser_lawn_mower') }}, Time={{ now() }}action: system_log.write - wait_template: > {{ states('lawn_mower.green_cruiser_lawn_mower') in ['docked', 'charging', 'returning'] }}timeout: "00:00:15" - wait_template: > {{ states('lawn_mower.green_cruiser_lawn_mower') in ['docked', 'charging'] }}timeout: "00:05:00"continue_on_timeout: true - choose: - conditions: - condition: templatevalue_template: > {{ states('lawn_mower.green_cruiser_lawn_mower') in ['docked', 'charging'] }}sequence: - data:level: infomessage: | MC: Roboter erfolgreich zurück zur Basis Time={{ now() }}action: system_log.writedefault: - data:level: errormessage: | MC: FEHLER – Roboter konnte nicht gestoppt werden Time={{ now() }}action: system_log.write - data:title: Achtung - Mähroboter konnte nicht gestoppt werden!message: > Der Mähroboter mäht weiterhin außerhalb der erlaubten Zeit und konnte nicht automatisch gestoppt werden!target: [email protected]action: notify.kottlowski_gmail_commode: restart
🧠 Ergebnis
👉 Der Zeitplan bleibt aktiv 👉 das Ladeverhalten bleibt optimal 👉 aber jede unerwünschte Bewegung wird sofort gestoppt
5. 🔄 Spontan-Flag zurücksetzen
Nach der praktischen Nutzung stellte sich heraus, dass input_boolean.mahroboter_spontan_akti in bestimmten Situationen nicht automatisch zurückgesetzt wird – zum Beispiel wenn der Mähroboter beim Aktivieren des Spontanmodus nicht docked war, oder wenn Home Assistant während einer laufenden Session neu gestartet wurde.
Das Flag bleibt dann dauerhaft ON und blockiert die automatische Zeitsteuerung, weil diese spontan_akti = off als erste Bedingung prüft.
Trigger:
🏠 Roboter wechselt zu „docked“ oder „charging“
Bedingungen:
▶️ spontan_akti ist ON
⏱ Die geplante Endzeit (mahroboter_spontan_ende) ist bereits verstrichen
Ablauf:
⏳ 30 Sekunden warten (State stabilisieren)
🔴 spontan_akti = OFF setzen
👉 Wichtig: Diese Automation greift nicht während einer aktiven laufenden Session (solange die Zeit noch nicht abgelaufen ist) – der automatische Neustart nach dem Laden funktioniert dadurch weiterhin korrekt.
alias: "Mähroboter: Spontan-Flag zurücksetzen"description: >- Setzt spontan_akti zurück wenn Roboter andockt und die geplante Zeit abgelaufen ist, oder wenn die Zeit abläuft und der Roboter bereits docked isttriggers: - trigger: stateentity_id: lawn_mower.green_cruiser_lawn_mowerto: - docked - charging - trigger: templatevalue_template: > {{ states('input_boolean.mahroboter_spontan_akti') == 'on' and now() >= states('input_datetime.mahroboter_ende_spontan') | as_datetime }}conditions: - condition: stateentity_id: input_boolean.mahroboter_spontan_aktistate: "on" - condition: templatevalue_template: > {{ now() >= states('input_datetime.mahroboter_ende_spontan') | as_datetime }}actions: - delay: "00:00:30" - action: input_boolean.turn_offtarget:entity_id: input_boolean.mahroboter_spontan_aktimode: restart
🧠 Besonderheit
Dieses Sicherheitsnetz verhindert, dass ein hängendes Flag die gesamte automatische Steuerung dauerhaft blockiert – unabhängig davon, warum die Hauptautomation die Rücksetzung verpasst hat (HA-Neustart, mode:restart-Unterbrechung, unterbrochene Sequenz).
📊 Die Steuerungsoberfläche (UI) für den Mähroboter
Um die Steuerung einfach und intuitiv zu halten, habe ich in Home Assistant eine übersichtliche View aufgebaut.
🕒 Zeit-Fenster Steuerung
🌦 Wetterfreigabe
⚙️ Automationsstatus
▶️ Manueller Start
📊 Status & Batterie
🎮 Direkte Steuerung
Der Aufbau nutzt Custom Button Cards, die das UI optisch ansprechend und übersichtlich gestalten.
Mit diesen Automatisierungen steuere ich meinen Mähroboter effizient, wetterabhängig und flexibel in Home Assistant.
Die Kombination aus Zeitsteuerung, Regenwettererkennung, Akkustatus und manueller Spontansteuerung sorgt für maximale Automatisierung mit Benutzerkomfort.
Die Helper erlauben individuelle Anpassungen ohne Codeänderung und die UI macht die Bedienung kinderleicht.
Ich hoffe, dieser umfassende Einblick in meine Mähroboter-Steuerung zeigt dir, wie smart und flexibel man selbst vermeintlich simple Geräte in Home Assistant automatisieren kann!
⚠️ Problem: Der Gardena Zeitplan als „unsichtbarer Gegenspieler“
Im Laufe der Umsetzung bin ich auf ein zentrales Problem gestoßen, das vermutlich viele Gardena-Nutzer kennen: Der in der App konfigurierte Zeitplan übernimmt nicht nur die Steuerung des Mähzeitraums, sondern beeinflusst auch das interne Verhalten des Roboters deutlich stärker als zunächst gedacht.
Wird der Zeitplan aktiv genutzt, sorgt er zwar für ein stabiles Ladeverhalten, jedoch startet der Mähroboter automatisch zu den im Plan definierten Zeiten – unabhängig davon, ob Home Assistant gerade andere Bedingungen vorgibt.
Eine einfache Lösung wie das „Pausieren“ des Zeitplans führt dabei nicht zum Ziel, da dieser Zustand von der Cloud bzw. vom Gerät selbst jederzeit wieder aufgehoben werden kann. In der Praxis bedeutet das: Der Mähroboter kann plötzlich wieder eigenständig starten, z. B. früh am Morgen, obwohl dies im Smart Home so gar nicht vorgesehen ist.
Auf der anderen Seite führt ein vollständiges Deaktivieren oder Entfernen des Zeitplans häufig dazu, dass der Roboter nicht mehr zuverlässig vollständig lädt, da die interne Energiemanagement-Logik ebenfalls daran gekoppelt ist.
👉 Es entsteht also ein klassischer Zielkonflikt:
✅ Zeitplan aktiv → stabiles Laden, aber ungewollte Starts
❌ Zeitplan aus → volle Kontrolle über Home Assistant, aber suboptimales Ladeverhalten
Die Lösung dieses Problems ist kein simples Umschalten, sondern eine gezielte Kombination aus beiden Welten:
✅ Der Zeitplan bleibt aktiv (für optimales Ladeverhalten)
✅ Home Assistant übernimmt die eigentliche Steuerung
🛡 Unerwünschte Starts werden aktiv erkannt und sofort korrigiert
Genau aus diesem Grund ist die dritte Automatisierung „Unerlaubten Start blockieren“ ein essenzieller Bestandteil der Gesamtarchitektur. Sie fungiert als Schutzmechanismus, der sicherstellt, dass ausschließlich Home Assistant bestimmt, wann der Roboter tatsächlich mäht.
In diesem Beitrag teile ich meine Erfahrungen und die Umsetzung eines Energy Managers in Home Assistant (HA) für meine Bosch Wärmepumpe Compress 5800i AW 12 ORE‑T in Kombination mit einer PV‑Anlage und einer Tesla Powerwall.
🎉Energiemanagement – Motivation
Bosch bietet einen kostenpflichtigen Energiemanager an, der die Wärmepumpe abhängig vom PV-Überschuss steuert:
Viel PV-Strom-Überschuss → Wärmepumpe läuft intensiver Kein PV-Überschuss → Wärmepumpe im Normalmodus
Ich wollte das in Home Assistant selbst umsetzen: Ein umfassendes System, das PV-Überschuss, Hausverbrauch und Powerwall-Ladestatus intelligent auswertet und steuert. Dabei sollten auch Temperaturbedingungen berücksichtigt werden. Das System sollte flexibel über Schalter und Zahlen steuerbar sein und automatisch die Wärmepumpe je nach Restüberschuss regeln.
🧰Ausgangslage
Seit etwa einem Jahr habe ich die Bosch Wärmepumpe und eine PV-Anlage installiert. Die PV-Anlage und die Tesla Powerwall ließen sich problemlos in HA integrieren (z.B. mit der „SMA Solar“ und der “Tesla Powerwall” Integration). Die Wärmepumpe von Bosch war allerdings schwieriger: Bosch stellt keine offizielle Schnittstelle für die über 150 Entitäten der Wärmepumpe zur Verfügung.
👑Lösung: EMS-Bus-Gateway von BBQKees Electronics
Nach längerer Recherche bin ich auf das EMS-Bus-Gateway von BBQKees Electronics gestoßen. Dieses Gateway bietet eine umfangreiche Knowledge Base und lässt sich gut über Matter in HA integrieren. So habe ich endlich Zugriff auf die Wärmepumpen-Daten bekommen.
📟Dashboard
Mein erstes Ziel war ein Dashboard mit den wichtigsten Wärmepumpen-Daten. Dabei hat mir die Webseite bosch-buderus-wp.github.io sehr geholfen.
Hier der zugehörige Auszug aus der RAW-Konfigurationsdatei:
🧱Bestandteile und Funktionen meines Energy Manager Packages
1) Helpers / Controls – Was ist definiert und wofür?
Im Package werden diverse Input‑Helper verwendet, um den Energy Manager flexibel steuerbar zu machen. Ich erkläre sie nacheinander:
Input Booleans
input_boolean.energy_manager_enabled
Beschreibt, ob der Energy Manager grundsätzlich aktiv ist. Wird verwendet als Hauptschalter: viele Automationen und Logik prüfen diesen Zustand.
input_boolean.energy_manager_temp_ok
Signalisiert, ob die Außentemperatur (bzw. der Luftansaugtemperatur der WP) in einem Bereich liegt, in dem das Energiemanagement arbeiten soll. Das verhindert sinnlose Heizaktivitäten bei sehr niedrigen Außentemperaturen.
input_boolean.energy_manager_force_zero
Ein Start‑/Initialisierungs‑Flag: beim Start von HA wird temporär gesetzt, damit die gemittelten Sensoren nicht wegen initialer fehlender Rohdaten fälschlicherweise Werte liefern. Wird automatisch wieder ausgeschaltet.
input_boolean.em_allow_aux
Erlaubt die Nutzung des Aux‑Heaters (Nachheizung). Standardmäßig aus, kann manuell eingeschaltet werden, wenn Zusatzheizung erwünscht ist.
input_boolean.em_dhw_charging
Kennzeichnet, ob eine gezielte Boiler‑/DHW‑Ladung via PV‑Überschuss aktuell aktiv ist.
input_boolean.em_safe_mode
Ein Schutzschalter, um den Manager in einen sicheren Zustand zu versetzen (Stopschalter, z. B. während Reset oder Wartung).
Input Select
input_select.energy_manager_mode
Umschaltbar zwischen „Sommer“ und „Winter“. In Sommer wird der Energiemanager weitestgehend deaktiviert (z. B. keine Heizaktivitäten), in Winter wird er aktiviert und die zugehörigen Automationen eingeschaltet.
Input Numbers
Diese Zahlen erlauben die Feinabstimmung des Managers ohne Anpassung der YAML‑Logik:
pw_max_charge – max. gewünschte Ladeleistung für die Powerwall (kW).
energy_manager_temp_start / energy_manager_temp_stop – Temperatur‑Hysterese für energy_manager_temp_ok (Start‑ und Stop‑Schwellen).
em_flow_temp_min / em_flow_temp_max – zulässiger Bereich für die Vorlauftemperatur, in dem der Manager die WP steuert.
em_flow_temp_step_k – Schrittgröße (°C) für das schrittweise Erhöhen der ausgewählten Vorlauftemperatur (Ramping).
em_min_runtime – minimale Kompressor‑Laufzeit (Minuten) (in der aktuellen Config als Parameter geplant, wird aber nicht direkt in allen Automationen verwendet).
em_pv_threshold_start / em_pv_threshold_stop – Grenzwerte (kW) für bestimmte PV‑Aktionen (Start / Stop), z. B. für DHW‑Ladung (werden aber in weiteren Logiken die tatsächlichen Avail‑Schwellwerte verwendet).
em_dhw_max_temp / em_dhw_min_temp – maximale/minimale Boiler‑Temperatur für DHW‑Ladung und für Blockierung des Aux‑Heaters.
em_dhw_charge_min_duration – minimale Ladezeit für DHW‑Ladevorgänge (Minuten) zur Vermeidung zu kurzer Schaltzyklen.
em_avail_start / em_avail_stop – Schwellen zur Aktivierung/Deaktivierung der Wärmepumpen‑Regelung basierend auf verfügbarer überschüssiger Leistung (kW).
2) Filter Sensoren (Glättung)
Rohdaten der PV‑Anlage, des Hausverbrauchs und der Batterie können stark schwanken. Um „flattern“ (häufiges An‑/Abschalten) zu vermeiden, werden diese Werte geglättet:
sensor.em_pv_power_smoothed
Quelle: sensor.my_home_solar_energie
Filter: lowpass (time_constant: 2, precision: 2).
Zweck: Glättet kurzzeitige PV‑Spitzen/-Tiefen. Niedriger Time Constant (2s) sorgt für relativ schnelle Reaktion bei gleichzeitigem Dämpfen von kurzen Ausschlägen.
Zweck: Glättet den Hausverbrauch mit einfachem gleitendem Mittelwert über 3 Messpunkte (reduziert kurzzeitige Peaks).
sensor.em_powerwall_battery_power_smoothed
Quelle: sensor.my_home_batterie_leistung
Filter: lowpass (time_constant: 6).
Zweck: Batterieströme können stark schwanken (z. B. Wechsel zwischen Laden/Entladen). Ein längeres Lowpass (6s) sorgt für stabile Erkennung der Ladeleistung.
Warum Glätten? Weil Steuerentscheidungen (z. B. Vorlauf‑Temp erhöhen, DHW starten) bittere Konsequenzen haben können (häufiges Schalten, unnötige Laufzeiten). Glättung reduziert Noise und setzt auf „echte“ Trends.
3) Template Sensors (Kernlogik)
Die Template‑Sensoren bilden das Herz des EM: Sie berechnen korrigierte Werte, Überschuss, verfügbarer Anteil für die WP und daraus das Ziel für die Vorlauftemperatur.
em_pv_power_corrected (kW)
Logik: Nimmt entweder den geglätteten PV‑Wert (sensor.em_pv_power_smoothed) oder 0, falls das Force‑Zero‑Flag aktiv ist (input_boolean.energy_manager_force_zero) oder der rohe Wert nahe 0 liegt. Damit schützt man vor falschen Messungen beim Systemstart und vermeidet winzige Rauschwerte.
em_home_load_corrected (kW)
Entspricht dem obigen Prinzip für Hausverbrauch (sensor.em_home_load_smoothed). Ziel: saubere Basis für Überschussberechnung.
em_powerwall_charge_corrected (kW)
Korrigiert die Powerwall‑Leistung: returns 0 wenn Force‑Zero gesetzt oder wenn die absolute Rohleistung < 0.02 kW (Schwellenwert). Ansonsten verwendet es den geglätteten Wert. Zusätzlich wird in em_pw_current_charge der absolute Wert genommen (positive Ladeleistung).
em_energy_surplus (kW)
Überschuss = em_pv_power_corrected − em_home_load_corrected. Der zentrale Kennwert, der anzeigt, wieviel Leistung theoretisch zur Verfügung steht.
em_pw_max_charge (kW)
Schlicht die Eingabe (input_number.pw_max_charge). Praktisch Referenzwert zur Begrenzung der Batterieladung durch andere Logiken (derzeit als Informativ verwendet).
em_pw_current_charge (kW)
Absoluter (gegenseitiger) Ladewert der Batterie – wieviel gerade in die Powerwall fließt (kW). Wichtig beim Verteilen des Überschusses: wenn die Batterie schon lädt, reduziert sie den Überschuss, der der WP zur Verfügung steht.
em_available_for_heatpump (kW)
Zentrale Logik: Verfügbarer Überschuss für die Wärmepumpe = max(0, surplus − current_battery_charge). Implementiert eine einfache Regel: Wird PV‑Überschuss erzeugt, nutzt zuerst die Batterie das Laden (wenn sie gerade lädt). Der Rest geht an die WP. Negative oder fehlende Überschüsse werden als 0 betrachtet. Es gibt ein implizites kleines Threshold‑Handling (pw_curr > 0.02 wird als „nutzt Batterie“ angenommen).
em_target_flow_temp (°C)
Hier wird aus em_available_for_heatpump eine Zielvorlauftemperatur berechnet. Formel: linearer Interpolationsansatz zwischen em_flow_temp_min und em_flow_temp_max, basierend auf einem angenommenen maximalen Heizleistungsbudget (in der Config ist max_power = 6 kW als Referenz). Ratio = clamp(avail / max_power, 0, 1). Zieltemp = min_t + (max_t − min_t) * ratio. Ergebnis ist stufenlos und wird in 0.1°C gerundet.
em_current_flow_temp (°C)
Liest die aktuelle WP‑Vorlauftemperatur (sensor.boiler_current_flow_temperature). Wird benötigt, um zu entscheiden, ob erhöht werden soll.
em_dhw_boiler_temp (°C)
Aktuelle Boiler‑Temperatur (für Warmwasser). Grundlage für DHW‑Ladungsentscheidungen.
em_dhw_charge_state
Ein einfacher on/off‑Sensor, abgeleitet vom input_boolean.em_dhw_charging, dient dem Dashboard und anderen Automationen als bequeme Sicht auf den DHW‑Lademodus.
Diese Sensoren liefern zusammen die Basis, auf der Automationen entscheiden, ob die WP angehoben wird, ob Boiler geladen wird oder ob Aux‑Heater blockiert wird.
4) Binary Sensor: wp_laufzeit_startbedingung
Der Binary Sensor fasst mehrere Bedingungen zusammen und liefert ein klares „laufen / nicht laufen“ Signal, ob die WP laufen (im Sinne: die Vorlauftemperatur erhöht werden soll).
Zustand (true), wenn:
Energy Manager ist aktiviert (energy_manager_enabled = on)
Temperatur‑Bedingung ist ok (energy_manager_temp_ok = on)
Safe Mode ist aus (em_safe_mode = off)
Verfügbare Überschussleistung (em_available_for_heatpump) ist größer als em_avail_start
Target Vorlauftemperatur ist deutlich (> current + hysteresis 0.2°C) über der aktuellen Vorlauftemperatur
Der Sensor hat zahlreiche Attribute, z. B. em_enabled, avail_for_hp_kW, target_flow_temp_C, current_flow_temp_C und eine berechnete “would_set_to_C” Angabe die zeigt, auf welche Temperatur die WP gesetzt würde (current + step, mit Cap auf target und max). Außerdem liefert das Attribut “reason” eine hilfreiche Diagnosen‑Kette (z. B. warum gerade nicht gestartet wird), was fürs Troubleshooting sehr praktisch ist.
Dieser history_stats‑Sensor wertet die Laufzeit eines Binary‑Sensors aus (in meinem Fall binary_sensor.wp_laufzeit_startbedingung) und berechnet daraus, wie lange dieser heute bzw. im aktuellen Monat auf on stand. Das Ergebnis ist eine Laufzeit in Dezimalstunden (z. B. 1.75 = 1 h 45 min).
Ich habe diesen Sensor in einer separaten Datei angelegt:
Erst dadurch wird sensor.wp_laufzeit_bei_uberschuss_heute_neu erzeugt und kann anschließend im Dashboard (z. B. in der button-card) verwendet werden. Die JavaScript‑Logik in der Card dient dann nur noch dazu, den Stundenwert optisch als hh:mm:ss darzustellen (siehe Scrennshot unten)
6) Automatisierungen – Funktionen im Detail
Das Package enthält mehrere Automationen. Ich erläutere kurz jede einzelne samt Triggern, Bedingungen und Zweck.
EM Temp OK Status aktualisieren
Zweck: Setzt input_boolean.energy_manager_temp_ok abhängig von der gemessenen Lufteinlasstemperatur sensor.boiler_air_inlet_temperature_tl2 mit Debounce (60 s).
Logik:
Auslöser: jede Änderung der inlet‑Temp.
Wenn Temp < energy_manager_temp_start → nach 60 s nochmals prüfen und dann input_boolean.energy_manager_temp_ok = on (Manager darf arbeiten).
Wenn Temp > energy_manager_temp_stop → nach 60 s prüfen und input_boolean.energy_manager_temp_ok = off (zu warm / nicht heizrelevant).
Hinweis: Der Start/Stop ist hystereseartig gestaltet (separierte Start/Stop‑Schwellen), um Flattern zu vermeiden.
EM: Vorlauftemperatur regeln
Zweck: Erhöht stufenweise die ausgewählte Vorlauftemperatur (number.boiler_selected_flow_temperature) – nur Erhöhungen, kein aktives Absenken. Das vermeidet unnötige Absenk‑Schaltungen der WP und ermöglicht sanftes Ramping.
Trigger: Änderung von em_available_for_heatpump, em_current_flow_temp oder den Eingangs‑Booleans.
Berechnet target_temp_raw und current_temp sowie step und max_t.
Wenn target > current → Kandidat = current + step; cappe auf target und max_t → setze number.boiler_selected_flow_temperature auf new_temp_capped.
Schreibt einen Logeintrag mit Änderung.
Warum nur Erhöhungen? Absenken sollte – je nach WP – eher vom Gerät selbst oder durch separate Strategien übernommen werden, um kurze zyklische Belastungen zu vermeiden.
EM: DHW Ladung steuern
Zweck: Startet / Stoppt Boiler‑DHW‑Ladung basierend auf PV‑Überschuss, Boiler‑Temperatur, Hysterese und Mindestladezeit.
Trigger: Änderungen an em_available_for_heatpump, Boiler‑Temp, Manager‑Flags.
Logik:
Start: Wenn pv_avail > em_avail_start UND Boiler Temp < em_dhw_max_temp UND (noch nicht ladend oder bereits ladend, aber Mindestdauer erfüllt) → input_boolean.em_dhw_charging = on, switch.boiler_dhw_one_time_charging = on.
Stop: Wenn Boiler Temp >= em_dhw_max_temp ODER pv_avail < em_avail_stop UND Mindestladezeit erfüllt → stoppe DHW‑Ladung und setze Flag off.
Die Mindestladezeit verhindert zu kurzes Ein/Aus‑Schalten; die Start/Stop‑Schwellen gewährleisten saubere Schaltbedingungen mit Hysterese.
EM: Aux‑Heater Steuerung
Zweck: Blockiert oder erlaubt den Aux‑Heater basierend auf em_allow_aux, Boiler Temp, DHW‑Ladestatus und PV‑Verfügbarkeit.
Logik:
Wenn Aux nicht erlaubt (em_allow_aux = off), Boiler Temp über em_dhw_min_temp, DHW charging aktiv und PV avail > em_avail_stop → schalte Aux‑Heater aus (switch.boiler_aux_heater_only = off).
Wenn em_allow_aux = on → schalte Aux‑Heater an.
Zusätzlich werden Logeinträge geschrieben – praktisch beim Debugging und zur Nachverfolgung der Steuerung.
EM: Force smoothed zero on startup
Zweck: Diese Automation verhindert initiale Fehlinterpretationen der geglätteten Sensoren direkt nach einem Home Assistant Neustart. Oft sind Rohsensoren zu Beginn noch „unknown“ oder liefern spurige Werte — das Force‑Zero‑Flag sorgt dafür, dass die auf Glättung basierenden Sensoren zunächst 0 melden, bis verlässliche Rohdaten vorliegen.
Logik im Ablauf:
Beim Start von Home Assistant wird input_boolean.energy_manager_force_zero kurz gesetzt.
Die Automation wartet, bis mindestens einer der Rohsensoren (PV, Hauslast, Batterie) initialisiert ist; Timeout sind 20 Sekunden.
Nach einer kleinen Verzögerung werden die geglätteten Sensoren sowie die wichtigsten Template‑Sensoren einmalig per homeassistant.update_entity aktualisiert, um konsistente Startwerte zu erzwingen.
Anschließend wird das Force‑Zero‑Flag wieder deaktiviert und das System arbeitet mit echten Werten weiter.
Nutzen: Robustheit beim Systemstart — keine spontanen Aktionen (z. B. Boiler‑Start) aufgrund falscher Anfangswerte.
EM: Aux‑Heater max Laufzeit
Zweck: Failsafe gegen dauerhaft eingeschalteten Aux‑Heater. Wenn der Aux‑Heater manuell oder durch andere Logiken eingeschaltet wurde, schaltet diese Automation ihn nach 20 Minuten automatisch aus.
Warum notwendig: Aux‑Heater sind in der Regel elektrische Zusatzheizungen mit hohem Verbrauch. Ein unbeabsichtigtes oder klemmendes Relais könnte ansonsten unnötig hohe Kosten verursachen oder die Energieeffizienz komplett zunichte machen.
EM: Aux‑Heater aus bei keinem PV-Überschuss
Zweck: Verhindert das Einschalten des Aux‑Heaters, wenn kein ausreichender PV‑Überschuss mehr vorhanden ist.
Logik:
Wenn der verfügbare PV-Überschuss (sensor.em_available_for_heatpump) unter 0,3 kW fällt und für mindestens 2 Minuten bleibt, wird der Aux‑Heater automatisch ausgeschaltet.
Das schützt vor unnötiger Netzstromaufnahme durch den Aux‑Heater, der nur bei Überschuss laufen soll.
EM: Moduswechsel ausführen
Zweck: Schaltet den Energy Manager automatisch in den Sommer‑ oder Wintermodus um, wenn die Auswahl im input_select.energy_manager_mode geändert wird.
In der Automation wird das Script em_set_mode ausgelöst, welches die notwendigen Input Booleans, Automationen und Werte entsprechend setzt.
EM: HARD Restore Modus nach Neustart
Zweck: Nach einem Home Assistant Neustart wird der zuletzt gewählte Modus aus dem input_select.energy_manager_mode gelesen und über das Script em_set_mode hart wiederhergestellt.
So wird sichergestellt, dass der Manager nicht in einem unerwarteten Zustand startet.
💾Package Struktur
YAML
# config/packages/energy_manager.yaml# --- Helpers / Controls ---input_boolean:energy_manager_enabled:name: "Energy Manager aktiv"initial: onenergy_manager_temp_ok:name: "Energy Manager: Temp OK"initial: onenergy_manager_force_zero:name: "Energy Manager: Force smoothed zero"initial: officon: mdi:power-offem_allow_aux:name: "EM: Aux-Heater erlauben"initial: offem_dhw_charging:name: "EM DHW Ladung aktiv"initial: offem_safe_mode:name: "EM Safe Mode"initial: officon: mdi:shield-alertinput_select:energy_manager_mode:name: "Energy Manager Modus"options: - "Sommer" - "Winter"input_number:pw_max_charge:name: "Powerwall max Ladeleistung (kW)"initial: 4.6min: 0max: 11.0step: 0.1unit_of_measurement: kWenergy_manager_temp_start:name: "EM Temp Start (°C)"initial: 14min: -40max: 40step: 0.5energy_manager_temp_stop:name: "EM Temp Stop (°C)"initial: 17min: -40max: 40step: 0.5em_flow_temp_min:name: "EM Vorlauf Minimum (°C)"initial: 38min: 20max: 45step: 0.5em_flow_temp_max:name: "EM Vorlauf Maximum (°C)"initial: 50min: 40max: 60step: 0.5em_flow_temp_step_k:name: "EM Vorlauf Änderung pro Intervall (°C)"initial: 0.5min: 0.1max: 2step: 0.1em_min_runtime:name: "EM Kompressor Mindestlaufzeit (Minuten)"initial: 15min: 5max: 60step: 1em_pv_threshold_start:name: "EM PV Überschuss Startschwelle (kW)"initial: 3min: 0max: 5step: 0.1em_pv_threshold_stop:name: "EM PV Überschuss Stoppschwelle (kW)"initial: 1min: 0max: 5step: 0.1em_dhw_max_temp:name: "EM DHW Max Temperatur (°C)"initial: 55min: 45max: 70step: 0.5em_dhw_min_temp:name: "EM DHW Min Temperatur (°C)"initial: 45min: 35max: 55step: 0.5em_dhw_charge_min_duration:name: "EM DHW Mindestladezeit (Minuten)"initial: 20min: 5max: 60step: 1em_avail_start:name: "EM Avail Start (kW)"initial: 0.6min: 0max: 10step: 0.1em_avail_stop:name: "EM Avail Stop (kW)"initial: 0.4min: 0max: 10step: 0.1# --- Scripts ---script:em_set_mode:alias: "EM: Modus anwenden"mode: singlesequence: - variables:mode: "{{ states('input_select.energy_manager_mode') }}" - choose: - conditions: - condition: templatevalue_template: "{{ mode == 'Winter' }}"sequence: - service: input_boolean.turn_ontarget:entity_id: input_boolean.energy_manager_enabled - service: input_boolean.turn_ontarget:entity_id: input_boolean.energy_manager_temp_ok - service: input_boolean.turn_offtarget:entity_id: input_boolean.em_safe_mode - service: number.set_valuetarget:entity_id: number.boiler_selected_flow_temperaturedata:value: "{{ states('input_number.em_flow_temp_min') | float(38) }}" - delay: "00:00:02" - service: automation.turn_ontarget:entity_id: - automation.em_vorlauftemperatur_regeln - automation.em_dhw_ladung_steuern - automation.em_aux_heater_steuerung - service: system_log.writedata:message: "EM: Wintermodus aktiviert"level: info - conditions: - condition: templatevalue_template: "{{ mode == 'Sommer' }}"sequence: - service: input_boolean.turn_offtarget:entity_id: - input_boolean.energy_manager_enabled - input_boolean.em_dhw_charging - input_boolean.em_allow_aux - service: automation.turn_offtarget:entity_id: - automation.em_vorlauftemperatur_regeln - automation.em_dhw_ladung_steuern - automation.em_aux_heater_steuerung - service: script.em_reset_to_defaults - service: system_log.writedata:message: "EM: Sommermodus aktiviert"level: warningem_toggle_mode:alias: "EM: Modus umschalten"mode: singlesequence: - variables:current: "{{ states('input_select.energy_manager_mode') }}" - service: input_select.select_optiontarget:entity_id: input_select.energy_manager_modedata:option: > {% if current == 'Winter' %} Sommer {% else %} Winter {% endif %}em_reset_to_defaults:alias: "EM: Reset auf Standardwerte"mode: singlesequence:# 🛡 Safe Mode aktivieren (verhindert Zwischenzustände) - service: input_boolean.turn_ontarget:entity_id: input_boolean.em_safe_mode - delay: "00:00:01"# 🧹 Grundsystem deaktivieren - service: input_boolean.turn_offtarget:entity_id: - input_boolean.energy_manager_enabled - input_boolean.energy_manager_force_zero - input_boolean.em_allow_aux - input_boolean.em_dhw_charging# ✔ Temp Status initialisieren - service: input_boolean.turn_ontarget:entity_id: input_boolean.energy_manager_temp_ok# 🔧 Input Numbers Reset - service: input_number.set_valuedata:entity_id: input_number.pw_max_chargevalue: 4.6 - service: input_number.set_valuedata:entity_id: input_number.energy_manager_temp_startvalue: 14 - service: input_number.set_valuedata:entity_id: input_number.energy_manager_temp_stopvalue: 17 - service: input_number.set_valuedata:entity_id: input_number.em_flow_temp_minvalue: 38 - service: input_number.set_valuedata:entity_id: input_number.em_flow_temp_maxvalue: 50 - service: input_number.set_valuedata:entity_id: input_number.em_flow_temp_step_kvalue: 0.5 - service: input_number.set_valuedata:entity_id: input_number.em_min_runtimevalue: 15 - service: input_number.set_valuedata:entity_id: input_number.em_pv_threshold_startvalue: 3 - service: input_number.set_valuedata:entity_id: input_number.em_pv_threshold_stopvalue: 1 - service: input_number.set_valuedata:entity_id: input_number.em_dhw_max_tempvalue: 55 - service: input_number.set_valuedata:entity_id: input_number.em_dhw_min_tempvalue: 45 - service: input_number.set_valuedata:entity_id: input_number.em_dhw_charge_min_durationvalue: 20 - service: input_number.set_valuedata:entity_id: input_number.em_avail_startvalue: 0.6 - service: input_number.set_valuedata:entity_id: input_number.em_avail_stopvalue: 0.4# 🌡 Boiler Startwert setzen - service: number.set_valuedata:entity_id: number.boiler_selected_flow_temperaturevalue: "{{ states('input_number.em_flow_temp_min') | float(38) }}"# 🔌 Aktoren sicher aus - service: switch.turn_offtarget:entity_id: - switch.boiler_dhw_one_time_charging - switch.boiler_aux_heater_only# --- Filter Sensoren (Glättung) ---sensor: - platform: filtername: "em_pv_power_smoothed"entity_id: sensor.my_home_solar_energiefilters: - filter: lowpasstime_constant: 2precision: 2 - platform: filtername: "em_home_load_smoothed"entity_id: sensor.my_home_last_leistungfilters: - filter: time_simple_moving_averagewindow_size: 3precision: 2 - platform: filtername: "em_powerwall_battery_power_smoothed"entity_id: sensor.my_home_batterie_leistungfilters: - filter: lowpasstime_constant: 6precision: 2# --- Template Sensors & Binary Sensors ---template: - sensor: - name: "em_pv_power_corrected"unit_of_measurement: "kW"state: >- {% set force = is_state('input_boolean.energy_manager_force_zero', 'on') %} {% set raw = states('sensor.my_home_solar_energie') | float(0) %} {% set sm = states('sensor.em_pv_power_smoothed') | float(0) %} {% if force or raw < 0.02 %} 0 {% else %} {{ sm | round(2) }} {% endif %} - name: "em_home_load_corrected"unit_of_measurement: "kW"state: >- {% set force = is_state('input_boolean.energy_manager_force_zero', 'on') %} {% set raw = states('sensor.my_home_last_leistung') | float(0) %} {% set sm = states('sensor.em_home_load_smoothed') | float(0) %} {% if force or raw < 0.02 %} 0 {% else %} {{ sm | round(2) }} {% endif %} - name: "em_powerwall_charge_corrected"unit_of_measurement: "kW"state: >- {% set force = is_state('input_boolean.energy_manager_force_zero', 'on') %} {% set raw = states('sensor.my_home_batterie_leistung') | float(0) %} {% set sm = states('sensor.em_powerwall_battery_power_smoothed') | float(0) %} {% if force or (raw | abs) < 0.02 %} 0 {% else %} {{ sm | round(2) }} {% endif %} - name: "em_energy_surplus"unit_of_measurement: "kW"state: >- {{ (states('sensor.em_pv_power_corrected') | float(0) - states('sensor.em_home_load_corrected') | float(0)) | round(2) }} - name: "em_pw_max_charge"unit_of_measurement: "kW"state: >- {{ states('input_number.pw_max_charge') | float(4.6) }} - name: "em_pw_current_charge"unit_of_measurement: "kW"state: >- {{ (states('sensor.em_powerwall_charge_corrected') | float(0)) | abs | round(2) }} - name: "em_available_for_heatpump"unit_of_measurement: "kW"state: >- {% set surplus = states('sensor.em_energy_surplus') | float(0) %} {% set pw_curr = states('sensor.em_pw_current_charge') | float(0) %} {% set threshold = 0.02 %} {% if surplus > 0 %} {% set used = pw_curr if pw_curr > threshold else 0 %} {{ [surplus - used, 0] | max | round(2) }} {% else %} 0 {% endif %}attributes:note: "Surplus minus current battery charge (threshold 0.02 kW)" - name: "em_target_flow_temp"unit_of_measurement: "°C"state: >- {% set min_t = states('input_number.em_flow_temp_min') | float(0) %} {% set max_t = states('input_number.em_flow_temp_max') | float(0) %} {% set avail = states('sensor.em_available_for_heatpump') | float(0) %} {% set max_power = 6 %} {% set ratio = (avail / max_power) | float(0) %} {% set ratio = [ratio, 0] | max %} {% set ratio = [ratio, 1] | min %} {% set target = min_t + (max_t - min_t) * ratio %} {{ target | round(1) }} - name: "em_current_flow_temp"unit_of_measurement: "°C"state: "{{ states('sensor.boiler_current_flow_temperature') | float(0) }}" - name: "em_dhw_boiler_temp"unit_of_measurement: "°C"state: "{{ states('sensor.boiler_heat_carrier_forward_tc1') | float(0) }}" - name: "em_dhw_charge_state"state: >- {{ 'on' if is_state('input_boolean.em_dhw_charging', 'on') else 'off' }} - binary_sensor: - name: "wp_laufzeit_startbedingung"unique_id: wp_laufzeit_startbedingungdevice_class: runningstate: >- {% set enabled = is_state('input_boolean.energy_manager_enabled','on') %} {% set temp_ok = is_state('input_boolean.energy_manager_temp_ok','on') %} {% set safe_off = is_state('input_boolean.em_safe_mode','off') %} {% set avail = states('sensor.em_available_for_heatpump') | float(0) %} {% set start_th = states('input_number.em_avail_start') | float(0) %} {% set target_t = states('sensor.em_target_flow_temp') | float(0) %} {% set current_t = states('sensor.em_current_flow_temp') | float(0) %} {% set hysteresis = 0.2 %} {{ enabled and temp_ok and safe_off and (avail > start_th) and (target_t > current_t + hysteresis) }} {# Optional zusätzlich: or is_state('input_boolean.em_dhw_charging','on') #}attributes:em_enabled: "{{ states('input_boolean.energy_manager_enabled') }}"em_temp_ok: "{{ states('input_boolean.energy_manager_temp_ok') }}"em_safe_mode: "{{ states('input_boolean.em_safe_mode') }}"avail_for_hp_kW: "{{ states('sensor.em_available_for_heatpump') }}"avail_start_kW: "{{ states('input_number.em_avail_start') }}"target_flow_temp_C: "{{ states('sensor.em_target_flow_temp') }}"current_flow_temp_C: "{{ states('sensor.em_current_flow_temp') }}"would_set_to_C: >- {% set step = states('input_number.em_flow_temp_step_k') | float(0.5) %} {% set max_t = states('input_number.em_flow_temp_max') | float(50) %} {% set cand = (states('sensor.em_current_flow_temp') | float(0)) + step %} {% set tgt = states('sensor.em_target_flow_temp') | float(0) %} {% set capped = [cand, tgt, max_t] | min %} {{ capped | round(1) }}reason: >- {# Variablen in Attribut-Template erneut setzen, da separat gerendert #} {% set enabled = is_state('input_boolean.energy_manager_enabled','on') %} {% set temp_ok = is_state('input_boolean.energy_manager_temp_ok','on') %} {% set safe_off = is_state('input_boolean.em_safe_mode','off') %} {% set avail = states('sensor.em_available_for_heatpump') | float(0) %} {% set start_th = states('input_number.em_avail_start') | float(0) %} {% set target_t = states('sensor.em_target_flow_temp') | float(0) %} {% set current_t = states('sensor.em_current_flow_temp') | float(0) %} {% set hysteresis = 0.2 %} {% set reasons = [] %} {% if not enabled %}{% set reasons = reasons + ['disabled'] %}{% endif %} {% if not temp_ok %}{% set reasons = reasons + ['temp_not_ok'] %}{% endif %} {% if not safe_off %}{% set reasons = reasons + ['safe_mode_on'] %}{% endif %} {% if not (avail > start_th) %}{% set reasons = reasons + ['avail_below_start'] %}{% endif %} {% if not (target_t > current_t + hysteresis) %}{% set reasons = reasons + ['target_not_above_current'] %}{% endif %} {{ 'all_conditions_met' if reasons|length == 0 else reasons|join(',') }}availability: >- {{ (states('sensor.em_available_for_heatpump') not in ['unknown','unavailable','']) and (states('sensor.em_target_flow_temp') not in ['unknown','unavailable','']) and (states('sensor.em_current_flow_temp') not in ['unknown','unavailable','']) }}# --- Automationen (alle in einem Block) ---automation: - alias: "EM Temp OK Status aktualisieren"description: "Setzt input_boolean.energy_manager_temp_ok basierend auf Temperatur mit Debounce 60s"mode: restarttrigger: - platform: stateentity_id: sensor.boiler_air_inlet_temperature_tl2condition: []action: - choose: - conditions: - condition: templatevalue_template: >- {% set temp = states('sensor.boiler_air_inlet_temperature_tl2') %} {% set start = states('input_number.energy_manager_temp_start') %} {{ temp not in ['unavailable', 'unknown', ''] and start not in ['unavailable', 'unknown', ''] and (temp | float(0)) < (start | float(0)) }}sequence: - delay: "00:01:00" - condition: templatevalue_template: >- {% set temp = states('sensor.boiler_air_inlet_temperature_tl2') %} {% set start = states('input_number.energy_manager_temp_start') %} {{ temp not in ['unavailable', 'unknown', ''] and start not in ['unavailable', 'unknown', ''] and (temp | float(0)) < (start | float(0)) }} - service: input_boolean.turn_ontarget:entity_id: input_boolean.energy_manager_temp_ok - conditions: - condition: templatevalue_template: >- {% set temp = states('sensor.boiler_air_inlet_temperature_tl2') %} {% set stop = states('input_number.energy_manager_temp_stop') %} {{ temp not in ['unavailable', 'unknown', ''] and stop not in ['unavailable', 'unknown', ''] and (temp | float(0)) > (stop | float(0)) }}sequence: - delay: "00:01:00" - condition: templatevalue_template: >- {% set temp = states('sensor.boiler_air_inlet_temperature_tl2') %} {% set stop = states('input_number.energy_manager_temp_stop') %} {{ temp not in ['unavailable', 'unknown', ''] and stop not in ['unavailable', 'unknown', ''] and (temp | float(0)) > (stop | float(0)) }} - service: input_boolean.turn_offtarget:entity_id: input_boolean.energy_manager_temp_ok# Automation: EM: Vorlauftemperatur regeln (nur Erhöhungen, kein Absenken) - alias: "EM: Vorlauftemperatur regeln"description: "Erhöht Selected flow temperature stufenlos mit Ramping (nur Anheben, kein aktives Absenken)"trigger: - platform: stateentity_id: - sensor.em_available_for_heatpump - sensor.em_current_flow_temp - input_boolean.energy_manager_enabled - input_boolean.energy_manager_temp_okcondition: - condition: stateentity_id: input_boolean.energy_manager_enabledstate: 'on' - condition: stateentity_id: input_boolean.energy_manager_temp_okstate: 'on' - condition: stateentity_id: input_boolean.em_safe_modestate: 'off'# Hysterese-Bedingung: nur wenn verfügbar > Start-Schwelle - condition: templatevalue_template: >- {{ (states('sensor.em_available_for_heatpump') | float(0)) > (states('input_number.em_avail_start') | float(0)) }}action: - variables:target_temp_raw: "{{ states('sensor.em_target_flow_temp') | float(0) }}"current_temp: "{{ states('sensor.em_current_flow_temp') | float(0) }}"step: "{{ states('input_number.em_flow_temp_step_k') | float(0) }}"max_t: "{{ states('input_number.em_flow_temp_max') | float(0) }}"last_change: "{{ state_attr('number.boiler_selected_flow_temperature', 'last_changed') }}"time_since_change_min: >- {% if last_change is none %} 9999 {% else %} ((as_timestamp(now()) - as_timestamp(last_change)) / 60) {% endif %} - choose: - conditions:# Nur erhöhen, wenn Ziel > Ist - condition: templatevalue_template: >- {{ target_temp_raw > current_temp }}sequence: - variables:# Kandidat ist Ist + Schritt, aber nicht über Zielcandidate_temp: >- {% set cand = current_temp + step %} {% if cand > target_temp_raw %} {{ target_temp_raw }} {% else %} {{ cand }} {% endif %}# Kappe auf maximalen erlaubten Wertnew_temp_capped: >- {{ [ (candidate_temp | float), (max_t | float) ] | min | round(1) }} - service: number.set_valuedata:entity_id: number.boiler_selected_flow_temperaturevalue: "{{ new_temp_capped }}" - service: system_log.writedata:message: "EM: Vorlaufregelung erhöht von {{ current_temp }}°C auf {{ new_temp_capped }}°C (Ziel {{ target_temp_raw }}°C, Max {{ max_t }}°C)"level: infodefault: [] - alias: "EM: DHW Ladung steuern"description: "Steuert DHW Charging basierend auf PV Überschuss und Boiler Temperatur, mit Hysterese"trigger: - platform: stateentity_id: - sensor.em_available_for_heatpump - sensor.em_dhw_boiler_temp - input_boolean.energy_manager_enabled - input_boolean.energy_manager_temp_ok - input_boolean.em_dhw_chargingcondition: - condition: stateentity_id: input_boolean.energy_manager_enabledstate: 'on' - condition: stateentity_id: input_boolean.energy_manager_temp_okstate: 'on' - condition: stateentity_id: input_boolean.em_safe_modestate: 'off'action: - variables:pv_avail: "{{ states('sensor.em_available_for_heatpump') | float(0) }}"boiler_temp: "{{ states('sensor.boiler_heat_carrier_forward_tc1') | float(0) }}"dhw_charging: "{{ is_state('input_boolean.em_dhw_charging', 'on') }}"max_temp: "{{ states('input_number.em_dhw_max_temp') | float(0) }}"min_temp: "{{ states('input_number.em_dhw_min_temp') | float(0) }}"min_duration: "{{ states('input_number.em_dhw_charge_min_duration') | int }}"last_on: "{{ state_attr('input_boolean.em_dhw_charging', 'last_changed') }}"time_on_min: >- {% if last_on is none %} 9999 {% else %} ((as_timestamp(now()) - as_timestamp(last_on)) / 60) {% endif %}start_thresh: "{{ states('input_number.em_avail_start') | float(0) }}"stop_thresh: "{{ states('input_number.em_avail_stop') | float(0) }}" - choose:# Start DHW Charging wenn PV Überschuss über Startschwelle und Boiler Temp < max_temp - conditions: - condition: templatevalue_template: >- {{ pv_avail > start_thresh }} - condition: templatevalue_template: >- {{ boiler_temp < max_temp }} - condition: templatevalue_template: >- {{ not dhw_charging or (dhw_charging and time_on_min > min_duration) }}sequence: - service: input_boolean.turn_ontarget:entity_id: input_boolean.em_dhw_charging - service: switch.turn_ontarget:entity_id: switch.boiler_dhw_one_time_charging - service: system_log.writedata:message: "EM: DHW Charging gestartet (Boiler {{ boiler_temp }}°C, PV Überschuss {{ pv_avail }} kW)"level: info# Stop DHW Charging wenn PV unter Stop-Schwelle oder Boiler Temp >= max_temp und Mindestladezeit erfüllt - conditions: - condition: templatevalue_template: >- {{ (boiler_temp >= max_temp) or (pv_avail < stop_thresh) }} - condition: templatevalue_template: >- {{ dhw_charging and time_on_min > min_duration }}sequence: - service: input_boolean.turn_offtarget:entity_id: input_boolean.em_dhw_charging - service: switch.turn_offtarget:entity_id: switch.boiler_dhw_one_time_charging - service: system_log.writedata:message: "EM: DHW Charging gestoppt (Boiler {{ boiler_temp }}°C, PV Überschuss {{ pv_avail }} kW)"level: info - alias: "EM: Aux-Heater Steuerung"description: "Blockiert Aux-Heater wenn nicht erlaubt oder Boiler Temp ausreichend, mit Hysterese"trigger: - platform: stateentity_id: - input_boolean.em_allow_aux - sensor.boiler_heat_carrier_forward_tc1 - input_boolean.em_dhw_charging - sensor.em_available_for_heatpumpcondition: - condition: stateentity_id: input_boolean.em_safe_modestate: 'off'action: - variables:allow_aux: "{{ is_state('input_boolean.em_allow_aux', 'on') }}"boiler_temp: "{{ states('sensor.boiler_heat_carrier_forward_tc1') | float(0) }}"min_temp: "{{ states('input_number.em_dhw_min_temp') | float(0) }}"dhw_charging: "{{ is_state('input_boolean.em_dhw_charging', 'on') }}"pv_avail: "{{ states('sensor.em_available_for_heatpump') | float(0) }}"start_thresh: "{{ states('input_number.em_avail_start') | float(0) }}"stop_thresh: "{{ states('input_number.em_avail_stop') | float(0) }}" - choose: - conditions: - condition: templatevalue_template: >- {{ not allow_aux and boiler_temp > min_temp and dhw_charging and pv_avail > stop_thresh }}sequence: - service: switch.turn_offtarget:entity_id: switch.boiler_aux_heater_only - service: system_log.writedata:message: "EM: Aux-Heater deaktiviert wegen Energiemanager"level: info - conditions: - condition: templatevalue_template: "{{ allow_aux }}"sequence: - service: switch.turn_ontarget:entity_id: switch.boiler_aux_heater_only - service: system_log.writedata:message: "EM: Aux-Heater erlaubt durch Energiemanager"level: info - alias: "EM: Force smoothed zero on startup"description: "Setzt beim HA-Start kurzzeitig das Force-Zero-Flag, bis Rohsensoren initialisiert sind"mode: singletrigger: - platform: homeassistantevent: startaction: - service: input_boolean.turn_ontarget:entity_id: input_boolean.energy_manager_force_zero - wait_for_trigger: - platform: templatevalue_template: >- {% set s1 = states('sensor.my_home_solar_energie') %} {% set s2 = states('sensor.my_home_last_leistung') %} {% set s3 = states('sensor.my_home_batterie_leistung') %} {{ [s1, s2, s3] | select('match', '^(?!unknown$|^unavailable$|^$).+') | list | count > 0 }}timeout: "00:00:20" - delay: "00:00:02" - service: homeassistant.update_entitytarget:entity_id: - sensor.em_pv_power_smoothed - sensor.em_home_load_smoothed - sensor.em_powerwall_battery_power_smoothed - sensor.em_pv_power_corrected - sensor.em_home_load_corrected - sensor.em_powerwall_charge_corrected - sensor.em_energy_surplus - sensor.em_available_for_heatpump - sensor.em_pw_current_charge - sensor.em_pw_max_charge - sensor.em_target_flow_temp - sensor.em_current_flow_temp - sensor.boiler_heat_carrier_forward_tc1 - delay: "00:00:01" - service: input_boolean.turn_offtarget:entity_id: input_boolean.energy_manager_force_zero - alias: "EM: Aux-Heater max Laufzeit"description: "Schaltet den Aux-Heater automatisch nach definierter Zeit wieder aus (Failsafe)"mode: restarttrigger: - platform: stateentity_id: switch.boiler_aux_heater_onlyto: "on"for: "00:20:00"condition: []action: - service: switch.turn_offtarget:entity_id: switch.boiler_aux_heater_only - service: system_log.writedata:message: "EM: Aux-Heater wurde nach 20 Minuten automatisch abgeschaltet (Failsafe)"level: warning - alias: "EM: Aux-Heater aus bei keinem PV-Überschuss"description: "Schaltet den Aux-Heater aus, wenn kein ausreichender PV-Überschuss vorhanden ist"mode: restarttrigger: - platform: numeric_stateentity_id: sensor.em_available_for_heatpumpbelow: 0.3for: "00:02:00"condition: []action: - condition: stateentity_id: switch.boiler_aux_heater_onlystate: "on" - service: switch.turn_offtarget:entity_id: switch.boiler_aux_heater_only - service: system_log.writedata:message: "EM: Aux-Heater deaktiviert wegen fehlendem PV-Überschuss"level: warning - alias: "EM: Moduswechsel ausführen"mode: singletrigger: - platform: stateentity_id: input_select.energy_manager_modeaction: - service: script.em_set_mode - alias: "EM: HARD Restore Modus nach Neustart"mode: singletrigger: - platform: homeassistantevent: startaction:# ⏱ wirklich warten bis alles stabil ist - delay: "00:00:20"# 🧠 Modus hart lesen (mit Fallback!) - variables:mode: "{{ states('input_select.energy_manager_mode') | default('Winter') }}"# 🧯 Debug zur Kontrolle - service: system_log.writedata:message: "EM START: gespeicherter Modus = {{ mode }}"level: warning# 🔥 HARTE WIEDERHERSTELLUNG - choose: - conditions: - condition: templatevalue_template: "{{ mode == 'Sommer' }}"sequence: - service: script.em_set_mode - conditions: - condition: templatevalue_template: "{{ mode == 'Winter' }}"sequence: - service: script.em_set_mode
🕶️Überblick zur Funktionsweise des Energy Managers
Das Energiemanagement arbeitet in mehreren, klar strukturierten Schritten:
Datenaufnahme & Glättung
Rohdaten aus den Quellen PV-Anlage, Hausverbrauch und Powerwall-Batterie werden zunächst mit Filtern geglättet. Dadurch werden kurzfristige Schwankungen und Messrauschen reduziert, um stabile Steuerentscheidungen zu ermöglichen.
Überschussberechnung & Verteilung
Aus den geglätteten Werten wird ein Energieüberschuss berechnet (PV-Erzeugung minus Hausverbrauch). Von diesem Überschuss wird sukzessive der aktuelle Ladebedarf der Powerwall subtrahiert, da die Batterie Vorrang beim Laden hat.
Der verbleibende Überschuss steht dann der Wärmepumpe zur Verfügung – dieser Wert steuert, wie intensiv die Wärmepumpe beheizt wird.
Temperaturabhängige Aktivierung
Der Manager berücksichtigt über einen eigenen Input Boolean, ob die Temperaturbedingungen (bspw. Außentemperatur am Luftansaugpunkt) geeignet sind. Nur wenn die Temperatur im definierten Bereich ist, aktiviert er Heizprozesse – das erhöht Effizienz und schützt die Anlage.
Automatische Steuerung
Die Wärmepumpe wird nur dann hochgefahren, wenn ein Restüberschuss nach Batterieladung verbleibt. Die Vorlauftemperatur der WP wird sanft in kleinen Schritten erhöht, um eine zu schnelle Hysterese zu vermeiden.
Die Powerwall wird vorrangig mit Überschussstrom geladen, wobei eine maximale Ladeleistung definiert ist.
Außerdem steuert der Manager die Warmwasserbereitung (DHW), indem er das Boilerladen abhängig von PV-Überschuss, Boiler-Temperatur und definierten Zeiträumen ein- und ausschaltet.
Der Aux-Heater wird je nach Einstellung blockiert oder zugelassen, um unnötigen Stromverbrauch zu verhindern.
Manuelle Steuerung
Über definierte Input Booleans kann der Nutzer den Manager manuell ein- oder ausschalten. Ein Force-Flag sorgt beim Start für stabile Initialisierung, Safe Mode verhindert temporär alle automatischen Aktionen, und verschiedene Schalter erlauben die manuelle Aktivierung von DHW-Ladung oder Aux-Heater.
Mit dieser Kombination aus Helpern, Glättung, Logiksensoren und Automationen entsteht ein flexibles, robuster Energiemanager, der PV-Überschuss gezielt für Heizungs- und Warmwasserbetrieb nutzt und gleichzeitig Hausverbrauch und Batterie berücksichtigt. So wird der Eigenverbrauch erhöht, Stromkosten gesenkt und der Komfort gewahrt.
🔭Visualisierung im Dashboard
Im Hauptdashboard zeige ich aktuell nur den Betriebsmodus der Wärmepumpe mit einer custom:button-card an:
Zusätzlich habe ich ein rudimentäres Dashboard mit allen relevanten Entitäten zur Steuerung der Wärmepumpe erstellt (rein zur Übersicht und prüfen der Daten die alles steuern):
Screenshot Sommermodus
Screenshot Wintermodus
Hier der zugehörige Auszug aus der RAW-Konfigurationsdatei:
Heute möchte ich euch ein kleines, aber für mich sehr hilfreiches Smart‑Home‑Projekt vorstellen.
Unser Garagentorantrieb verrichtet seit vielen Jahren zuverlässig seinen Dienst – bisher allerdings völlig losgelöst von Home Assistant. Gerade im Alltag fehlte mir immer wieder die einfache Antwort auf eine scheinbar banale Frage: Ist das Garagentor eigentlich noch offen?
Statt zu rätseln oder extra nachzusehen, sollte mir Home Assistant künftig nicht nur die Steuerung übernehmen, sondern mir auch klar und eindeutig den aktuellen Zustand anzeigen. Ideal wäre das Ganze natürlich lokal, ohne Cloud und mit möglichst wenig zusätzlicher Hardware.
Besonders wichtig war mir, jederzeit sehen zu können, in welchem Zustand sich das Garagentor gerade befindet: geschlossen / in Bewegung / geöffnet.
Dieser Beitrag richtet sich an alle, die ein bestehendes Garagentor ohne großen Umbau in Home Assistant integrieren möchten – inklusive zuverlässiger Statusrückmeldung, lokaler Steuerung und sauberer Visualisierung im Dashboard.
Herausgekommen ist eine kompakte Lösung mit einem Shelly 1 Gen3, einem Shelly Add‑On und zwei Magnetschaltern, die ich euch im Folgenden Schritt für Schritt vorstelle.
🔧 Verwendete Komponenten
Shelly 1 Gen3 – schaltet den Torantrieb wie ein klassischer Taster
Shelly Plus Add‑On – ermöglicht den Anschluss der beiden Magnetschalter
Magnetschalter – liefern die Positionsinformation „offen / geschlossen“
Abzweigdose
🔌 Kurz zur Verkabelung
Ein Magnetschalter ist so an der Antriebsschiene angebracht, dass er schließt, wenn das Garagentor komplett geöffnet ist. Der zweite Magnetschalter schließt, wenn das Garagentor komplett geschlossen ist.
Zu beachten ist, dass das Shelly Add‑On nur einen digitalen Eingang für einen Magnetschalter besitzt. Deshalb wurde der zweite Magnetschalter am analogen Eingang angeschlossen.
Der analoge Eingang des Shelly Add‑On wird in diesem Fall nicht zur Messung eines Spannungswerts genutzt, sondern dient lediglich als zweiter „Status‑Eingang“. Der Magnetschalter wird dabei wie ein einfacher Schließer verwendet.
Normalerweise kann ein Shelly 1 über die 12 Volt Versorgung des Torantriebs betrieben werden. In meinem Fall hat das jedoch nicht funktioniert, deshalb habe ich den Shelly ganz klassisch an 230 Volt angeschlossen.
Für die Steuerung habe ich am Garagentorantrieb die Anschlüsse für einen externen Taster verwendet (GND & PB).
⚡ Verkabelung Shelly 1 Gen3
L & N an die 230‑Volt‑Stromversorgung
0 an den PB-Anschluss des Garagentorantriebs
I an den GND-Anschluss des Garagentorantriebs
📸Verkabelung Shelly 1
🔗 Verkabelung Shelly Add‑On
Magnetschalter 1 an Digital In und GND
Magnetschalter 2 an Analog In und GND (zweiten freien verwenden)
⚙️ Einstellungen in der Shelly App
Zunächst sollte das Shelly 1 Gen3 zusammen mit dem Shelly Add‑On über die Shelly App eingebunden werden. Einige wichtige Einstellungen müssen dort vorgenommen werden.
Die Einbindung der Add‑On‑Peripherie (Magnetschalter) überspringe ich an dieser Stelle.
Hier nur kurz das Ergebnis in der App:
📸Shelly App – Peripherieeinstellungen
⚙️Wichtige Einstellungen
Unter Einstellungen → Eingangs-/Ausgangseinstellungen sollten die Optionen wie folgt gesetzt sein:
📸Shelly App – Eingangs-/Ausgangseinstellungen
Zusätzlich muss bei der Zeitschaltuhr ein Wert für „Automatisches Ausschalten“ gesetzt werden, damit der Taster auch wirklich als Taster funktioniert.
Ohne diese Einstellung würde der Shelly‑Ausgang dauerhaft eingeschaltet bleiben, was bei einem Garagentorantrieb zu Fehlfunktionen oder ungewolltem Dauerlauf führen kann.
✅ Empfohlener Wert: 1 Sekunde
📸Shelly App – Zeitschaltuhr
🏠 Home Assistant Integration
In Home Assistant wird das Shelly Relais zusammen mit dem Add‑On ganz normal über die Shelly Integration eingebunden. Danach stehen alle notwendigen Entitäten zur Verfügung.
📸 Home Assistant – Shelly Entitäten
Zusätzlich habe ich eine Kamera in der Garage installiert, um bei Bedarf einen Blick auf das Garagentor zu werfen.
Wichtig war mir aber vor allem eine visuelle Statusanzeige:
🟢 Grün → Tor geschlossen
🟡 Gelb → Tor bewegt sich
🔴 Rot → Tor geöffnet
Die Statusänderungen über die Magnetschalter werden in Home Assistant nahezu verzögerungsfrei aktualisiert und haben sich im Alltag als sehr zuverlässig erwiesen.
🎨 Statusanzeige mit Button‑Card
Für die Anzeige habe ich eine custom:button-card verwendet.
Die Entity sensor.garagentor_status liefert den aktuellen Status. Diese wird über einen Template‑Sensor ermittelt.
Da der digitale und der analoge Eingang jeweils unterschiedliche Endpositionen abbilden, werden beide Sensorwerte kombiniert, um den exakten Zustand des Garagentors zuverlässig zu bestimmen.
- sensor: - name: "Garagentor Status"unique_id: "garagentor_status"state: >- {% set bin = states('binary_sensor.shelly1g3_28372f2ad118_input_100') %} {% set analog = states('sensor.garagentor_input_101_analog') %} {% if analog == 'unknown' and bin == 'on' %}error_on{% elif analog == 'unknown' and bin == 'off' %}error_off{% elif analog == '0.0' and bin == 'off' %}normal{% else %}other{% endif %}
Dieser externe Template Sensor wurde in einer separaten YAML-Datei (/homeassistant/sensors/garagentor.yaml) definiert und in der configuration.yaml wie folgt eingebunden:
In diesem Beitrag stelle ich mein vollständig automatisiertes Setup vor, mit dem ich meinen ECOVACS Deebot T10 Omni komplett lokal über Home Assistant steuere – ohne App-Zwang und ohne Cloud-Abhängigkeit.
📦 Funktionsumfang des Setups
✅ Plan 1 – zeitgesteuert
✅ Plan 2 – zeitgesteuert
✅ Plan 3 – Sofortstart per Button
✅ Raum-Mapping (Helper → Ecovacs Areas)
✅ Modus-Mapping (z. B. „Nur Saugen“ → „Vacuum-Only“)
✅ Wochentagssteuerung
✅ Mushroom-Dashboard zur kompletten Bedienung
Hinweis: Da verschiedene Ecovacs-Modelle unterschiedliche Area-IDs und Modusbezeichnungen nutzen, muss das Setup ggf. angepasst werden.