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.
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.
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-Switch switch.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
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
- switch.v8_jacuzzi_spa_filter – Filterpumpe
- climate.v8_jacuzzi_spa_thermostat – Heizungssteuerung
- binary_sensor.v8_jacuzzi_spa_connected – Verbindungsstatus
- binary_sensor.v8_jacuzzi_spa_errors – Fehlererkennung
- 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:
Verfügbar = PV-Leistung (geglättet) - Haushaltslast (geglättet) - Powerwall-Ladung
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.
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_target_temp – Zieltemperatur (Standard: 37 °C)
- 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 5 Automationen im Überblick
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.
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.
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
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.
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.
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 erreichbar. Alle Buttons basieren auf custom:button-card.
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_toggleauf, das Heizung und Filter gemeinsam schaltet. - Filterpumpe – blau wenn aktiv, dunkel wenn aus. Arbeitet über den Template-Switch
switch.v8_jacuzzi_spa_filter_uials 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.
Ü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.
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.
Screenshot der drei Verbrauchs-Anzeigen auf der Jacuzzi-Detailseite
Der YAML-Code
Helper & Automationen (config/packages/jacuzzi.yaml)
# config/packages/jacuzzi.yaml
# --- Helpers ---
input_boolean:
jacuzzi_pv_auto:
name: "Jacuzzi: PV-Auto aktiv"
initial: on
icon: mdi:hot-tub
jacuzzi_filter_auto:
name: "Jacuzzi: Filter-Automatik aktiv"
initial: on
icon: mdi:water-sync
v8_jacuzzi_filter_state:
name: "Jacuzzi Filter Status"
initial: off
icon: mdi:water-pump
jacuzzi_spa_started_by_pv:
name: "Jacuzzi Spa durch PV gestartet"
initial: off
icon: mdi:solar-power
input_number:
jacuzzi_pv_start:
name: "Jacuzzi PV Start-Schwelle (kW)"
initial: 2.0
min: 0.5
max: 6.0
step: 0.1
unit_of_measurement: kW
jacuzzi_pv_stop:
name: "Jacuzzi PV Stop-Schwelle (kW)"
initial: 1.0
min: 0.0
max: 5.0
step: 0.1
unit_of_measurement: kW
jacuzzi_target_temp:
name: "Jacuzzi Zieltemperatur (°C)"
initial: 37
min: 20
max: 40
step: 0.5
unit_of_measurement: "°C"
jacuzzi_min_runtime:
name: "Jacuzzi Mindestlaufzeit (Min)"
initial: 30
min: 5
max: 120
step: 5
jacuzzi_filter_duration:
name: "Jacuzzi Filter Laufzeit (Min)"
initial: 60
min: 15
max: 240
step: 15
unit_of_measurement: min
# --- Filter-Zeitsteuerung ---
input_datetime:
jacuzzi_filter_time_1:
name: "Jacuzzi Filter Zeit 1"
has_time: true
has_date: false
jacuzzi_filter_time_2:
name: "Jacuzzi Filter Zeit 2"
has_time: true
has_date: false
# --- Automationen ---
automation:
- alias: "Jacuzzi: PV-Überschuss EIN"
description: "Schaltet Jacuzzi-Heizung bei ausreichendem PV-Überschuss ein"
mode: single
trigger:
- platform: numeric_state
entity_id: sensor.em_available_for_heatpump
above: input_number.jacuzzi_pv_start
for: "00:02:00"
condition:
- condition: state
entity_id: input_boolean.jacuzzi_pv_auto
state: "on"
- condition: state
entity_id: binary_sensor.v8_jacuzzi_spa_connected
state: "on"
- condition: state
entity_id: binary_sensor.v8_jacuzzi_spa_errors
state: "off"
action:
- variables:
spa_was_off: "{{ is_state('switch.v8_jacuzzi_spa_power', 'off') }}"
- if:
- condition: template
value_template: "{{ spa_was_off }}"
then:
- service: switch.turn_on
target:
entity_id: switch.v8_jacuzzi_spa_power
- service: input_boolean.turn_on
target:
entity_id: input_boolean.jacuzzi_spa_started_by_pv
- delay: "00:00:05"
- service: climate.set_temperature
target:
entity_id: climate.v8_jacuzzi_spa_thermostat
data:
temperature: "{{ states('input_number.jacuzzi_target_temp') | float(37) }}"
- service: climate.set_hvac_mode
target:
entity_id: climate.v8_jacuzzi_spa_thermostat
data:
hvac_mode: heat
- service: switch.turn_on
target:
entity_id: switch.v8_jacuzzi_spa_filter
- service: input_boolean.turn_on
target:
entity_id: input_boolean.v8_jacuzzi_filter_state
- service: system_log.write
data:
message: >
Jacuzzi: EIN via PV-Überschuss
{{ states('sensor.em_available_for_heatpump') }} kW,
Zieltemp {{ states('input_number.jacuzzi_target_temp') }}°C
(Spa war {{ 'aus – wurde eingeschaltet' if spa_was_off else 'bereits an' }})
level: info
- data:
title: "Jacuzzi gestartet"
message: >
Jacuzzi automatisch gestartet
(PV-Überschuss: {{ states('sensor.em_available_for_heatpump') }} kW,
Zieltemp: {{ states('input_number.jacuzzi_target_temp') }}°C)
action: notify.kottlowski_gmail_com
- alias: "Jacuzzi: PV-Überschuss AUS"
description: "Schaltet Jacuzzi-Heizung ab wenn PV-Überschuss zu gering"
mode: single
trigger:
- platform: numeric_state
entity_id: sensor.em_available_for_heatpump
below: input_number.jacuzzi_pv_stop
for: "00:03:00"
condition:
- condition: state
entity_id: input_boolean.jacuzzi_pv_auto
state: "on"
- condition: state
entity_id: switch.v8_jacuzzi_spa_power
state: "on"
- condition: template
value_template: >
{% set last_on = states.switch.v8_jacuzzi_spa_power.last_changed %}
{% set min_run = states('input_number.jacuzzi_min_runtime') | int(30) %}
{{ (as_timestamp(now()) - as_timestamp(last_on)) > (min_run * 60) }}
action:
- service: climate.set_hvac_mode
target:
entity_id: climate.v8_jacuzzi_spa_thermostat
data:
hvac_mode: "off"
- delay: "00:00:02"
- service: switch.turn_off
target:
entity_id: switch.v8_jacuzzi_spa_filter
- delay: "00:00:02"
- service: switch.turn_off
target:
entity_id: switch.v8_jacuzzi_spa_filter
- delay: "00:00:02"
- service: switch.turn_off
target:
entity_id: switch.v8_jacuzzi_spa_filter
- delay: "00:00:01"
- service: input_boolean.turn_off
target:
entity_id: input_boolean.v8_jacuzzi_filter_state
- delay: "00:00:03"
- if:
- condition: state
entity_id: input_boolean.jacuzzi_spa_started_by_pv
state: "on"
then:
- service: switch.turn_off
target:
entity_id: switch.v8_jacuzzi_spa_power
- service: input_boolean.turn_off
target:
entity_id: input_boolean.jacuzzi_spa_started_by_pv
- service: system_log.write
data:
message: >
Jacuzzi: AUS – PV-Überschuss unter Schwelle
({{ states('sensor.em_available_for_heatpump') }} kW)
level: info
- data:
title: "Jacuzzi gestoppt"
message: >
Jacuzzi automatisch gestoppt
(PV-Überschuss zu gering: {{ states('sensor.em_available_for_heatpump') }} kW)
action: notify.kottlowski_gmail_com
- alias: "Jacuzzi: Filter Zyklus"
description: "Startet Filterung zu konfigurierbaren Zeiten – Spa wird bei Bedarf kurz eingeschaltet"
mode: single
trigger:
- platform: time
at: input_datetime.jacuzzi_filter_time_1
- platform: time
at: input_datetime.jacuzzi_filter_time_2
condition:
- condition: state
entity_id: input_boolean.jacuzzi_filter_auto
state: "on"
- condition: state
entity_id: binary_sensor.v8_jacuzzi_spa_connected
state: "on"
- condition: state
entity_id: binary_sensor.v8_jacuzzi_spa_errors
state: "off"
- condition: state
entity_id: input_boolean.v8_jacuzzi_filter_state
state: "off"
action:
- variables:
spa_was_off: "{{ is_state('switch.v8_jacuzzi_spa_power', 'off') }}"
- if:
- condition: template
value_template: "{{ spa_was_off }}"
then:
- service: switch.turn_on
target:
entity_id: switch.v8_jacuzzi_spa_power
- delay: "00:00:05"
- service: switch.turn_on
target:
entity_id: switch.v8_jacuzzi_spa_filter
- service: input_boolean.turn_on
target:
entity_id: input_boolean.v8_jacuzzi_filter_state
- service: system_log.write
data:
message: >
Jacuzzi: Filter gestartet für
{{ states('input_number.jacuzzi_filter_duration') | int }} Min
(Spa war {{ 'aus – wurde eingeschaltet' if spa_was_off else 'bereits an' }})
level: info
- delay:
minutes: "{{ states('input_number.jacuzzi_filter_duration') | int(60) }}"
- service: switch.turn_off
target:
entity_id: switch.v8_jacuzzi_spa_filter
- service: input_boolean.turn_off
target:
entity_id: input_boolean.v8_jacuzzi_filter_state
- if:
- condition: template
value_template: >
{{ spa_was_off and
not is_state('climate.v8_jacuzzi_spa_thermostat', 'heat') }}
then:
- delay: "00:00:03"
- service: switch.turn_off
target:
entity_id: switch.v8_jacuzzi_spa_power
- service: system_log.write
data:
message: "Jacuzzi: Filter Zyklus beendet"
level: info
- alias: "Jacuzzi: Auto-Modus deaktiviert"
description: "Stoppt den Jacuzzi sofort wenn PV-Auto manuell deaktiviert wird"
mode: single
trigger:
- platform: state
entity_id: input_boolean.jacuzzi_pv_auto
to: "off"
condition:
- condition: state
entity_id: switch.v8_jacuzzi_spa_power
state: "on"
action:
- service: climate.set_hvac_mode
target:
entity_id: climate.v8_jacuzzi_spa_thermostat
data:
hvac_mode: "off"
- delay: "00:00:02"
- service: switch.turn_off
target:
entity_id: switch.v8_jacuzzi_spa_filter
- delay: "00:00:02"
- service: switch.turn_off
target:
entity_id: switch.v8_jacuzzi_spa_filter
- delay: "00:00:02"
- service: switch.turn_off
target:
entity_id: switch.v8_jacuzzi_spa_filter
- delay: "00:00:01"
- service: input_boolean.turn_off
target:
entity_id: input_boolean.v8_jacuzzi_filter_state
- delay: "00:00:03"
- if:
- condition: state
entity_id: input_boolean.jacuzzi_spa_started_by_pv
state: "on"
then:
- service: switch.turn_off
target:
entity_id: switch.v8_jacuzzi_spa_power
- service: input_boolean.turn_off
target:
entity_id: input_boolean.jacuzzi_spa_started_by_pv
- service: system_log.write
data:
message: "Jacuzzi: PV-Auto deaktiviert – Heizung und Filter gestoppt"
level: info
- data:
title: "Jacuzzi deaktiviert"
message: "Jacuzzi PV-Auto wurde deaktiviert. Heizung und Filter wurden gestoppt."
action: notify.kottlowski_gmail_com
- alias: "Jacuzzi: Fehler-Benachrichtigung"
description: "Benachrichtigt bei Jacuzzi-Fehler und stoppt falls aktiv"
mode: single
trigger:
- platform: state
entity_id: binary_sensor.v8_jacuzzi_spa_errors
to: "on"
action:
- data:
title: "Jacuzzi Fehler!"
message: "Der Jacuzzi meldet einen Fehler. Bitte überprüfen!"
action: notify.kottlowski_gmail_com
- service: system_log.write
data:
message: "Jacuzzi: Fehler erkannt – manuelle Prüfung erforderlich"
level: error
# --- Laufzeit-Protokollierung ---
sensor:
- platform: history_stats
name: "Jacuzzi Heizung Laufzeit heute"
entity_id: climate.v8_jacuzzi_spa_thermostat
state: "heat"
type: time
start: "{{ now().replace(hour=0, minute=0, second=0) }}"
end: "{{ now() }}"
template:
- sensor:
- name: "Jacuzzi Heizung Laufzeit heute formatiert"
icon: mdi:timer-play
state: >
{% set h = states('sensor.jacuzzi_heizung_laufzeit_heute') | float(0) %}
{% set hours = h | int %}
{% set minutes = ((h - hours) * 60) | int %}
{{ '%d:%02d h' | format(hours, minutes) }}
Status-Button (Lovelace YAML)
type: custom:button-card
name: Jacuzzi Status
icon: mdi:information-outline
show_label: true
show_state: false
tap_action:
action: none
hold_action:
action: more-info
entity: climate.v8_jacuzzi_spa_thermostat
triggers_update:
- climate.v8_jacuzzi_spa_thermostat
- switch.v8_jacuzzi_spa_filter_ui
- switch.v8_jacuzzi_spa_jets
- select.v8_jacuzzi_spa_bubbles
label: |
[[[
var heat = states['climate.v8_jacuzzi_spa_thermostat'].state;
var filter = states['switch.v8_jacuzzi_spa_filter_ui'].state;
var jets = states['switch.v8_jacuzzi_spa_jets'].state;
var bubbles = states['select.v8_jacuzzi_spa_bubbles'].state;
var parts = [];
if (heat === 'heat') parts.push('Heizung AN');
if (heat === 'idle') parts.push('Heizung Idle');
if (filter === 'on') parts.push('Filter AN');
if (jets === 'on') parts.push('Massage AN');
if (bubbles !== 'OFF') parts.push('Bubbles AN');
return parts.length > 0 ? parts.join(' · ') : 'Alles aus';
]]]
styles:
card:
- height: 60px
- padding: 6px 10px
- border-radius: 10px
- cursor: default
- background: |
[[[
var heat = states['climate.v8_jacuzzi_spa_thermostat'].state;
var filter = states['switch.v8_jacuzzi_spa_filter_ui'].state;
var jets = states['switch.v8_jacuzzi_spa_jets'].state;
var bubbles = states['select.v8_jacuzzi_spa_bubbles'].state;
var anyOn = heat === 'heat' || heat === 'idle' || filter === 'on' || jets === 'on' || bubbles !== 'OFF';
return anyOn ? '#1565c0' : '#3a3a3a';
]]]
- border: |
[[[
var heat = states['climate.v8_jacuzzi_spa_thermostat'].state;
var filter = states['switch.v8_jacuzzi_spa_filter_ui'].state;
var jets = states['switch.v8_jacuzzi_spa_jets'].state;
var bubbles = states['select.v8_jacuzzi_spa_bubbles'].state;
var anyOn = heat === 'heat' || heat === 'idle' || filter === 'on' || jets === 'on' || bubbles !== 'OFF';
return anyOn ? '2px solid #1565c0' : '1px solid #666';
]]]
grid:
- grid-template-areas: "\"i n\" \"i l\""
- grid-template-columns: 32px auto
- grid-template-rows: auto auto
- align-items: center
- justify-items: start
- column-gap: 8px
icon:
- width: 22px
- height: 22px
- color: |
[[[
var heat = states['climate.v8_jacuzzi_spa_thermostat'].state;
var filter = states['switch.v8_jacuzzi_spa_filter_ui'].state;
var jets = states['switch.v8_jacuzzi_spa_jets'].state;
var bubbles = states['select.v8_jacuzzi_spa_bubbles'].state;
var anyOn = heat === 'heat' || heat === 'idle' || filter === 'on' || jets === 'on' || bubbles !== 'OFF';
return anyOn ? '#ffffff' : '#9e9e9e';
]]]
name:
- font-size: 14px
- font-weight: 600
- justify-self: start
- text-align: left
- color: |
[[[
var heat = states['climate.v8_jacuzzi_spa_thermostat'].state;
var filter = states['switch.v8_jacuzzi_spa_filter_ui'].state;
var jets = states['switch.v8_jacuzzi_spa_jets'].state;
var bubbles = states['select.v8_jacuzzi_spa_bubbles'].state;
var anyOn = heat === 'heat' || heat === 'idle' || filter === 'on' || jets === 'on' || bubbles !== 'OFF';
return anyOn ? '#ffffff' : '#cccccc';
]]]
label:
- font-size: 12px
- justify-self: start
- text-align: left
- color: |
[[[
var heat = states['climate.v8_jacuzzi_spa_thermostat'].state;
var filter = states['switch.v8_jacuzzi_spa_filter_ui'].state;
var jets = states['switch.v8_jacuzzi_spa_jets'].state;
var bubbles = states['select.v8_jacuzzi_spa_bubbles'].state;
var anyOn = heat === 'heat' || heat === 'idle' || filter === 'on' || jets === 'on' || bubbles !== 'OFF';
return anyOn ? '#e3f2fd' : '#aaaaaa';
]]]
Steuer-Buttons (Lovelace YAML)
type: vertical-stack
cards:
- type: custom:button-card
entity: switch.v8_jacuzzi_spa_power
name: SPA Power
icon: mdi:hot-tub
show_state: true
size: 22px
tap_action:
action: toggle
hold_action:
action: more-info
state:
- value: "on"
styles:
card:
- background: "#2e7d32"
- border: 2px solid "#2e7d32"
icon:
- color: "#ffffff"
name:
- color: "#ffffff"
state:
- color: "#e8f5e9"
- value: "off"
styles:
card:
- background: "#3a3a3a"
- border: "1px solid #666"
icon:
- color: "#9e9e9e"
name:
- color: "#cccccc"
state:
- color: "#aaaaaa"
styles:
card:
- height: 60px
- padding: 6px 10px
- border-radius: 10px
- background: var(--ha-card-background)
- border: 1px solid var(--divider-color)
- cursor: pointer
grid:
- grid-template-areas: "\"i n\" \"i s\""
- grid-template-columns: 32px auto
- grid-template-rows: auto auto
- align-items: center
- justify-items: start
icon:
- width: 22px
- height: 22px
- color: var(--secondary-text-color)
name:
- font-size: 14px
- font-weight: 600
- justify-self: start
- text-align: left
state:
- font-size: 12px
- opacity: 0.8
- justify-self: start
- text-align: left
- type: custom:button-card
entity: climate.v8_jacuzzi_spa_thermostat
name: Heizung
icon: mdi:fire
show_state: true
tap_action:
action: call-service
service: script.jacuzzi_heat_toggle
hold_action:
action: more-info
state:
- value: heat
styles:
card:
- background: "#d32f2f"
- border: 2px solid "#d32f2f"
icon:
- color: "#ffffff"
name:
- color: "#ffffff"
state:
- color: "#ffebee"
- value: idle
styles:
card:
- background: "#f57c00"
- border: 2px solid "#f57c00"
icon:
- color: "#ffffff"
name:
- color: "#ffffff"
state:
- color: "#fff3e0"
- value: "off"
styles:
card:
- background: "#3a3a3a"
- border: "1px solid #666"
icon:
- color: "#9e9e9e"
name:
- color: "#cccccc"
state:
- color: "#aaaaaa"
styles:
card:
- height: 60px
- padding: 6px 10px
- border-radius: 10px
- cursor: pointer
grid:
- grid-template-areas: "\"i n\" \"i s\""
- grid-template-columns: 32px auto
- grid-template-rows: auto auto
- align-items: center
- justify-items: start
- column-gap: 8px
icon:
- width: 22px
- height: 22px
name:
- font-size: 14px
- font-weight: 600
- justify-self: start
- text-align: left
state:
- font-size: 12px
- opacity: 0.8
- justify-self: start
- text-align: left
- type: custom:button-card
entity: switch.v8_jacuzzi_spa_filter_ui
name: Filterpumpe
icon: mdi:water-pump
show_state: true
size: 22px
tap_action:
action: call-service
service: >
[[[ return entity.state === 'on' ? 'switch.turn_off' : 'switch.turn_on'
]]]
target:
entity_id: switch.v8_jacuzzi_spa_filter_ui
hold_action:
action: more-info
state:
- value: "on"
styles:
card:
- background: "#0288d1"
- border: 2px solid "#0288d1"
icon:
- color: "#ffffff"
name:
- color: "#ffffff"
state:
- color: "#e1f5fe"
- value: "off"
styles:
card:
- background: "#3a3a3a"
- border: "1px solid #666"
icon:
- color: "#9e9e9e"
name:
- color: "#cccccc"
state:
- color: "#aaaaaa"
styles:
card:
- height: 60px
- padding: 6px 10px
- border-radius: 10px
- border: 1px solid var(--divider-color)
- cursor: pointer
grid:
- grid-template-areas: "\"i n\" \"i s\""
- grid-template-columns: 32px auto
- grid-template-rows: auto auto
- align-items: center
- justify-items: start
- column-gap: 8px
icon:
- width: 22px
- height: 22px
name:
- font-size: 14px
- font-weight: 600
- justify-self: start
- text-align: left
state:
- font-size: 12px
- opacity: 0.8
- justify-self: start
- text-align: left
- type: custom:button-card
entity: select.v8_jacuzzi_spa_bubbles
name: Bubble-Modus
icon: mdi:chart-bubble
show_state: true
tap_action:
action: call-service
service: select.select_option
service_data:
entity_id: select.v8_jacuzzi_spa_bubbles
option: "[[[ return entity.state === 'MAX' ? 'OFF' : 'MAX'; ]]]"
hold_action:
action: more-info
state:
- value: "OFF"
styles:
card:
- background: "#3a3a3a"
- border: "1px solid #666"
icon:
- color: "#9e9e9e"
name:
- color: "#cccccc"
state:
- color: "#aaaaaa"
- value: MAX
styles:
card:
- background: "#ff9800"
- border: 2px solid "#ff9800"
icon:
- color: "#ffffff"
name:
- color: "#ffffff"
state:
- color: "#fff8e1"
styles:
card:
- height: 60px
- padding: 6px 10px
- border-radius: 10px
grid:
- grid-template-areas: "\"i n\" \"i s\""
- grid-template-columns: 32px auto
- grid-template-rows: auto auto
- align-items: center
- justify-items: start
- column-gap: 8px
icon:
- width: 22px
- height: 22px
name:
- font-size: 14px
- font-weight: 600
- justify-self: start
- text-align: left
state:
- font-size: 12px
- justify-self: start
- text-align: left
- opacity: 0.8
- type: custom:button-card
entity: switch.v8_jacuzzi_spa_jets
name: Massagefunktion
icon: mdi:waves
show_state: true
tap_action:
action: toggle
hold_action:
action: more-info
state:
- value: "on"
styles:
card:
- background: "#8e24aa"
- border: 2px solid "#8e24aa"
icon:
- color: "#ffffff"
name:
- color: "#ffffff"
state:
- color: "#f3e5f5"
- value: "off"
styles:
card:
- background: "#3a3a3a"
- border: "1px solid #666"
icon:
- color: "#9e9e9e"
name:
- color: "#cccccc"
state:
- color: "#aaaaaa"
styles:
card:
- height: 60px
- padding: 6px 10px
- border-radius: 10px
grid:
- grid-template-areas: "\"i n\" \"i s\""
- grid-template-columns: 32px auto
- grid-template-rows: auto auto
- align-items: center
- justify-items: start
- column-gap: 8px
icon:
- width: 22px
- height: 22px
name:
- font-size: 14px
- font-weight: 600
- justify-self: start
- text-align: left
state:
- font-size: 12px
- justify-self: start
- text-align: left
grid_options:
rows: auto
columns: 6
card_mod:
style: |
ha-card {
gap: 6px; /* 🔥 Abstand zwischen Buttons */
}
Script & Template-Switch (script.yaml / sensors/jacuzzi.yaml)
In: “/homeassistant/scripts.yaml”
jacuzzi_heat_toggle:
alias: "Jacuzzi: Heizung + Filter-UI Toggle (force off with retries)"
mode: single
sequence:
- choose:
- conditions:
- condition: state
entity_id: climate.v8_jacuzzi_spa_thermostat
state: 'off'
sequence:
- service: climate.set_hvac_mode
target:
entity_id: climate.v8_jacuzzi_spa_thermostat
data:
hvac_mode: 'heat'
- delay: { seconds: 2 }
- service: switch.turn_on
target:
entity_id: switch.v8_jacuzzi_spa_filter
- service: input_boolean.turn_on
target:
entity_id: input_boolean.v8_jacuzzi_filter_state
default:
- service: climate.turn_off
target:
entity_id: climate.v8_jacuzzi_spa_thermostat
- delay: { seconds: 2 }
# drei Versuche, den Filter auszuschalten (mit kurzen Pausen)
- service: switch.turn_off
target:
entity_id: switch.v8_jacuzzi_spa_filter
- delay: { seconds: 2 }
- service: switch.turn_off
target:
entity_id: switch.v8_jacuzzi_spa_filter
- delay: { seconds: 2 }
- service: switch.turn_off
target:
entity_id: switch.v8_jacuzzi_spa_filter
- delay: { seconds: 1 }
- service: input_boolean.turn_off
target:
entity_id: input_boolean.v8_jacuzzi_filter_state
Unter: “/homeassistant/sensors/jacuzzi.yaml”
- switch:
- name: "v8-Jacuzzi Spa Filter (UI)"
unique_id: v8_jacuzzi_filter_ui
optimistic: false # optional
state: "{{ is_state('input_boolean.v8_jacuzzi_filter_state','on') }}"
turn_on:
- service: switch.turn_on
target:
entity_id: switch.v8_jacuzzi_spa_filter
- service: input_boolean.turn_on
target:
entity_id: input_boolean.v8_jacuzzi_filter_state
turn_off:
- service: switch.turn_off
target:
entity_id: switch.v8_jacuzzi_spa_filter
- service: input_boolean.turn_off
target:
entity_id: input_boolean.v8_jacuzzi_filter_state
Vollständiges Dashboard (Lovelace Raw-Konfiguration)
Der vollständige YAML-Code meines Dashboards – inklusive des Jacuzzi-Bereichs eingebettet in das Tablet-Dashboard mit Nordic Theme:
- type: sections
max_columns: 3
title: Jacuzzi
path: jacuzzi
icon: mdi:spa
sections:
- type: grid
cards:
- type: custom:button-card
entity: switch.jacuzzi
name: SPA Master Switch
icon: mdi:electric-switch
show_state: true
size: 22px
tap_action:
action: toggle
hold_action:
action: more-info
state:
- value: "on"
styles:
card:
- background: "#2e7d32"
- border: 2px solid "#2e7d32"
icon:
- color: "#ffffff"
name:
- color: "#ffffff"
state:
- color: "#e8f5e9"
- value: "off"
styles:
card:
- background: "#c62828"
- border: 2px solid "#c62828"
icon:
- color: "#ffffff"
name:
- color: "#ffffff"
state:
- color: "#ffebee"
styles:
card:
- height: 60px
- padding: 6px 10px
- border-radius: 10px
- cursor: pointer
grid:
- grid-template-areas: "\"i n s\""
- grid-template-columns: 32px auto auto
- grid-template-rows: 1fr
- align-items: center
icon:
- width: 22px
- height: 22px
name:
- font-size: 16px
- font-weight: 600
- justify-self: start
- margin-left: 8px
state:
- font-size: 18px
- justify-self: start
- font-weight: 700
- margin-left: 10px
- text-transform: uppercase
- type: heading
icon: ""
heading_style: title
- type: thermostat
entity: climate.v8_jacuzzi_spa_thermostat
show_current_as_primary: true
grid_options:
columns: 6
rows: auto
name: Jacuzzi
- type: vertical-stack
cards:
- type: custom:button-card
entity: switch.v8_jacuzzi_spa_power
name: SPA Power
icon: mdi:hot-tub
show_state: true
size: 22px
tap_action:
action: toggle
hold_action:
action: more-info
state:
- value: "on"
styles:
card:
- background: "#2e7d32"
- border: 2px solid "#2e7d32"
icon:
- color: "#ffffff"
name:
- color: "#ffffff"
state:
- color: "#e8f5e9"
- value: "off"
styles:
card:
- background: "#3a3a3a"
- border: "1px solid #666"
icon:
- color: "#9e9e9e"
name:
- color: "#cccccc"
state:
- color: "#aaaaaa"
styles:
card:
- height: 60px
- padding: 6px 10px
- border-radius: 10px
- background: var(--ha-card-background)
- border: 1px solid var(--divider-color)
- cursor: pointer
grid:
- grid-template-areas: "\"i n\" \"i s\""
- grid-template-columns: 32px auto
- grid-template-rows: auto auto
- align-items: center
- justify-items: start
icon:
- width: 22px
- height: 22px
- color: var(--secondary-text-color)
name:
- font-size: 14px
- font-weight: 600
- justify-self: start
- text-align: left
state:
- font-size: 12px
- opacity: 0.8
- justify-self: start
- text-align: left
- type: custom:button-card
entity: climate.v8_jacuzzi_spa_thermostat
name: Heizung
icon: mdi:fire
show_state: true
tap_action:
action: call-service
service: script.jacuzzi_heat_toggle
hold_action:
action: more-info
state:
- value: heat
styles:
card:
- background: "#d32f2f"
- border: 2px solid "#d32f2f"
icon:
- color: "#ffffff"
name:
- color: "#ffffff"
state:
- color: "#ffebee"
- value: idle
styles:
card:
- background: "#f57c00"
- border: 2px solid "#f57c00"
icon:
- color: "#ffffff"
name:
- color: "#ffffff"
state:
- color: "#fff3e0"
- value: "off"
styles:
card:
- background: "#3a3a3a"
- border: "1px solid #666"
icon:
- color: "#9e9e9e"
name:
- color: "#cccccc"
state:
- color: "#aaaaaa"
styles:
card:
- height: 60px
- padding: 6px 10px
- border-radius: 10px
- cursor: pointer
grid:
- grid-template-areas: "\"i n\" \"i s\""
- grid-template-columns: 32px auto
- grid-template-rows: auto auto
- align-items: center
- justify-items: start
- column-gap: 8px
icon:
- width: 22px
- height: 22px
name:
- font-size: 14px
- font-weight: 600
- justify-self: start
- text-align: left
state:
- font-size: 12px
- opacity: 0.8
- justify-self: start
- text-align: left
- type: custom:button-card
entity: switch.v8_jacuzzi_spa_filter_ui
name: Filterpumpe
icon: mdi:water-pump
show_state: true
size: 22px
tap_action:
action: call-service
service: >
[[[ return entity.state === 'on' ? 'switch.turn_off' :
'switch.turn_on' ]]]
target:
entity_id: switch.v8_jacuzzi_spa_filter_ui
hold_action:
action: more-info
state:
- value: "on"
styles:
card:
- background: "#0288d1"
- border: 2px solid "#0288d1"
icon:
- color: "#ffffff"
name:
- color: "#ffffff"
state:
- color: "#e1f5fe"
- value: "off"
styles:
card:
- background: "#3a3a3a"
- border: "1px solid #666"
icon:
- color: "#9e9e9e"
name:
- color: "#cccccc"
state:
- color: "#aaaaaa"
styles:
card:
- height: 60px
- padding: 6px 10px
- border-radius: 10px
- border: 1px solid var(--divider-color)
- cursor: pointer
grid:
- grid-template-areas: "\"i n\" \"i s\""
- grid-template-columns: 32px auto
- grid-template-rows: auto auto
- align-items: center
- justify-items: start
- column-gap: 8px
icon:
- width: 22px
- height: 22px
name:
- font-size: 14px
- font-weight: 600
- justify-self: start
- text-align: left
state:
- font-size: 12px
- opacity: 0.8
- justify-self: start
- text-align: left
- type: custom:button-card
entity: select.v8_jacuzzi_spa_bubbles
name: Bubble-Modus
icon: mdi:chart-bubble
show_state: true
tap_action:
action: call-service
service: select.select_option
service_data:
entity_id: select.v8_jacuzzi_spa_bubbles
option: "[[[ return entity.state === 'MAX' ? 'OFF' : 'MAX'; ]]]"
hold_action:
action: more-info
state:
- value: "OFF"
styles:
card:
- background: "#3a3a3a"
- border: "1px solid #666"
icon:
- color: "#9e9e9e"
name:
- color: "#cccccc"
state:
- color: "#aaaaaa"
- value: MAX
styles:
card:
- background: "#ff9800"
- border: 2px solid "#ff9800"
icon:
- color: "#ffffff"
name:
- color: "#ffffff"
state:
- color: "#fff8e1"
styles:
card:
- height: 60px
- padding: 6px 10px
- border-radius: 10px
grid:
- grid-template-areas: "\"i n\" \"i s\""
- grid-template-columns: 32px auto
- grid-template-rows: auto auto
- align-items: center
- justify-items: start
- column-gap: 8px
icon:
- width: 22px
- height: 22px
name:
- font-size: 14px
- font-weight: 600
- justify-self: start
- text-align: left
state:
- font-size: 12px
- justify-self: start
- text-align: left
- opacity: 0.8
- type: custom:button-card
entity: switch.v8_jacuzzi_spa_jets
name: Massagefunktion
icon: mdi:waves
show_state: true
tap_action:
action: toggle
hold_action:
action: more-info
state:
- value: "on"
styles:
card:
- background: "#8e24aa"
- border: 2px solid "#8e24aa"
icon:
- color: "#ffffff"
name:
- color: "#ffffff"
state:
- color: "#f3e5f5"
- value: "off"
styles:
card:
- background: "#3a3a3a"
- border: "1px solid #666"
icon:
- color: "#9e9e9e"
name:
- color: "#cccccc"
state:
- color: "#aaaaaa"
styles:
card:
- height: 60px
- padding: 6px 10px
- border-radius: 10px
grid:
- grid-template-areas: "\"i n\" \"i s\""
- grid-template-columns: 32px auto
- grid-template-rows: auto auto
- align-items: center
- justify-items: start
- column-gap: 8px
icon:
- width: 22px
- height: 22px
name:
- font-size: 14px
- font-weight: 600
- justify-self: start
- text-align: left
state:
- font-size: 12px
- justify-self: start
- text-align: left
grid_options:
rows: auto
columns: 6
card_mod:
style: |
ha-card {
gap: 6px; /* 🔥 Abstand zwischen Buttons */
}
- type: grid
cards:
- type: gauge
name: Jacuzzi
severity:
green: 0
yellow: 1000
red: 1500
needle: true
max: 2000
entity: sensor.jacuzzi_leistung
- type: custom:apexcharts-card
header:
title: Jacuzzi Energie (14 Tage)
show: true
graph_span: 14d
span:
start: day
offset: "-13d"
series:
- entity: sensor.jacuzzi_energy_daily
name: Jacuzzi
type: column
group_by:
func: max
duration: 1d
transform: |
return x < 0 ? 0 : x;
apex_config:
chart:
height: 300
plotOptions:
bar:
borderRadius: 4
columnWidth: 60%
colors:
ranges:
- from: 0
to: 0.5
color: "#90EE90"
- from: 0.5
to: 0.7
color: "#00C853"
- from: 0.7
to: 15
color: "#FFD600"
xaxis:
labels:
datetimeFormatter:
day: dd.MM
yaxis:
min: 0
- type: custom:apexcharts-card
header:
title: Jacuzzi Energie (stündlich)
show: true
graph_span: 24h
series:
- entity: sensor.jacuzzi_energie
name: Solar
type: column
group_by:
func: avg
duration: 1h
transform: |
return x < 0 ? 0 : x;
apex_config:
chart:
height: 300
plotOptions:
bar:
borderRadius: 4
columnWidth: 60%
colors:
ranges:
- from: 0
to: 4
color: "#90EE90"
- from: 4
to: 8
color: "#00C853"
- from: 8
to: 15
color: "#FFD600"
xaxis:
labels:
datetimeFormatter:
hour: HH:mm
yaxis:
min: 0
tooltip:
"y":
formatter: |
function(value) {
return Math.round(value) + " W";
}
- type: grid
cards:
- type: heading
heading_style: title
icon: mdi:shower-head
heading: Dusche
- square: false
type: grid
cards:
- type: custom:button-card
entity: valve.morpheus_reich_valve_4
name: Dusche
icon: mdi:shower-head
show_state: true
size: 22px
state_display: |
[[[
return entity.state === 'open' ? 'Ein' : 'Aus';
]]]
tap_action:
action: toggle
hold_action:
action: more-info
state:
- value: open
styles:
card:
- background: "#2e7d32"
- border: 2px solid "#2e7d32"
icon:
- color: "#ffffff"
name:
- color: "#ffffff"
state:
- color: "#e8f5e9"
- value: closed
styles:
card:
- background: "#3a3a3a"
- border: "1px solid #666"
icon:
- color: "#9e9e9e"
name:
- color: "#cccccc"
state:
- color: "#aaaaaa"
styles:
card:
- height: 60px
- padding: 6px 10px
- border-radius: 10px
- background: var(--ha-card-background)
- border: 1px solid var(--divider-color)
- cursor: pointer
grid:
- grid-template-areas: "\"i n\" \"i s\""
- grid-template-columns: 32px auto
- grid-template-rows: auto auto
- align-items: center
- justify-items: start
icon:
- width: 22px
- height: 22px
- color: var(--secondary-text-color)
name:
- font-size: 14px
- font-weight: 600
- justify-self: start
- text-align: left
state:
- font-size: 12px
- opacity: 0.8
- justify-self: start
- text-align: left
- type: custom:button-card
name: Dauer
icon: mdi:timer
show_name: false
entity: input_number.dusche_laufzeit
tap_action:
action: call-service
service: input_number.increment
target:
entity_id: input_number.dusche_laufzeit
hold_action:
action: call-service
service: input_number.decrement
target:
entity_id: input_number.dusche_laufzeit
double_tap_action:
action: more-info
show_state: true
state_display: |
[[[
const v = Number(entity.state);
return `${v} min`;
]]]
aspect_ratio: 3/1
size: 22px
styles:
card:
- height: 60px
- padding: 6px 10px
- border-radius: 10px
- border: 1px solid
- background: |
[[[
const v = Number(entity.state);
if (v === 0) return '#424242'; // aus / deaktiviert
if (v <= 90) return '#2e7d32'; // grün (kurz, sicher)
if (v <= 150) return '#f9a825'; // gelb (normal)
return '#c62828'; // rot (lang)
]]]
name:
- font-size: 14px
- font-weight: 600
- color: "#cccccc"
icon:
- width: 22px
- height: 22px
- color: "#bdbdbd"
state:
- font-size: 14px
- font-weight: bold
- color: "#ffffff"
columns: 2
- type: heading
heading: Filterung
heading_style: title
icon: mdi:filter-outline
- square: false
type: grid
cards:
- type: custom:button-card
entity: input_boolean.jacuzzi_filter_auto
name: Filter-Automatik
icon: mdi:filter
show_state: true
size: 22px
tap_action:
action: toggle
hold_action:
action: more-info
state:
- value: "on"
styles:
card:
- background: "#2e7d32"
- border: 2px solid "#2e7d32"
icon:
- color: "#ffffff"
name:
- color: "#ffffff"
state:
- color: "#e8f5e9"
- value: "off"
styles:
card:
- background: "#3a3a3a"
- border: "1px solid #666"
icon:
- color: "#9e9e9e"
name:
- color: "#cccccc"
state:
- color: "#aaaaaa"
styles:
card:
- height: 60px
- padding: 6px 10px
- border-radius: 10px
- background: var(--ha-card-background)
- border: 1px solid var(--divider-color)
- cursor: pointer
grid:
- grid-template-areas: "\"i n\" \"i s\""
- grid-template-columns: 32px auto
- grid-template-rows: auto auto
- align-items: center
- justify-items: start
icon:
- width: 22px
- height: 22px
- color: var(--secondary-text-color)
name:
- font-size: 14px
- font-weight: 600
- justify-self: start
- text-align: left
state:
- font-size: 12px
- opacity: 0.8
- justify-self: start
- text-align: left
- type: custom:button-card
name: Dauer
icon: mdi:timer
show_name: false
entity: input_number.jacuzzi_filter_duration
tap_action:
action: call-service
service: input_number.increment
target:
entity_id: input_number.jacuzzi_filter_duration
hold_action:
action: call-service
service: input_number.decrement
target:
entity_id: input_number.jacuzzi_filter_duration
double_tap_action:
action: more-info
show_state: true
state_display: |
[[[
const v = Number(entity.state);
return `${v} min`;
]]]
aspect_ratio: 3/1
size: 22px
styles:
card:
- height: 60px
- padding: 6px 10px
- border-radius: 10px
- border: 1px solid
- background: |
[[[
const v = Number(entity.state);
if (v === 0) return '#424242'; // aus / deaktiviert
if (v <= 90) return '#2e7d32'; // grün (kurz, sicher)
if (v <= 150) return '#f9a825'; // gelb (normal)
return '#c62828'; // rot (lang)
]]]
name:
- font-size: 14px
- font-weight: 600
- color: "#cccccc"
icon:
- width: 22px
- height: 22px
- color: "#bdbdbd"
state:
- font-size: 14px
- font-weight: bold
- color: "#ffffff"
columns: 2
grid_options:
rows: 1
- square: false
type: grid
columns: 2
grid_options:
rows: auto
cards:
- type: custom:button-card
entity: input_datetime.jacuzzi_filter_time_1
icon: mdi:clock-start
show_name: false
show_state: true
show_icon: true
tap_action:
action: more-info
layout: vertical
aspect_ratio: 3/1
size: 22px
styles:
card:
- height: 60px
- padding: 4px
- border-radius: 10px
- border: "1px solid #555"
- background: var(--card-background-color)
grid:
- grid-template-areas: "\"i\" \"s\""
- grid-template-rows: 1fr auto
- justify-items: center
icon:
- width: 24px
- height: 24px
state:
- font-size: 14px
- font-weight: bold
- type: custom:button-card
entity: input_datetime.jacuzzi_filter_time_2
icon: mdi:clock-start
show_name: false
show_state: true
show_icon: true
tap_action:
action: more-info
layout: vertical
aspect_ratio: 3/1
size: 22px
styles:
card:
- height: 60px
- padding: 4px
- border-radius: 10px
- border: "1px solid #555"
- background: var(--card-background-color)
grid:
- grid-template-areas: "\"i\" \"s\""
- grid-template-rows: 1fr auto
- justify-items: center
icon:
- width: 24px
- height: 24px
state:
- font-size: 14px
- font-weight: bold
- type: heading
heading_style: title
icon: mdi:heating-coil
heading: Automatisierung Filter
- square: false
type: grid
columns: 2
grid_options:
rows: 1
cards:
- type: custom:button-card
entity: automation.jacuzzi_filter_zyklus
icon: mdi:home-automation
show_name: false
show_state: true
layout: vertical
aspect_ratio: 3/1
size: 22px
styles:
card:
- height: 60px
- padding: 6px 10px
- border-radius: 10px
- border: "1px solid #555"
grid:
- grid-template-areas: "\"i\" \"s\""
- grid-template-rows: 1fr auto
- justify-items: center
icon:
- width: 24px
- height: 24px
state:
- font-size: 14px
- font-weight: bold
state:
- value: "on"
styles:
card:
- background: "#2e7d32"
- border: "1px solid #ffffff"
icon:
- color: "#a5d6a7"
state:
- color: "#e8f5e9"
- value: "off"
styles:
card:
- background: "#c62828"
- border: "1px solid #555"
icon:
- color: "#ffcdd2"
state:
- color: "#ffebee"
- type: heading
heading_style: title
- type: heading
heading_style: title
icon: mdi:heating-coil
heading: Heizung
- square: false
type: grid
cards:
- type: custom:button-card
entity: input_boolean.jacuzzi_pv_auto
name: Heiz-Automatik
icon: mdi:heating-coil
show_state: true
size: 22px
tap_action:
action: toggle
hold_action:
action: more-info
state:
- value: "on"
styles:
card:
- background: "#2e7d32"
- border: 2px solid "#2e7d32"
icon:
- color: "#ffffff"
name:
- color: "#ffffff"
state:
- color: "#e8f5e9"
- value: "off"
styles:
card:
- background: "#3a3a3a"
- border: "1px solid #666"
icon:
- color: "#9e9e9e"
name:
- color: "#cccccc"
state:
- color: "#aaaaaa"
styles:
card:
- height: 60px
- padding: 6px 10px
- border-radius: 10px
- background: var(--ha-card-background)
- border: 1px solid var(--divider-color)
- cursor: pointer
grid:
- grid-template-areas: "\"i n\" \"i s\""
- grid-template-columns: 32px auto
- grid-template-rows: auto auto
- align-items: center
- justify-items: start
icon:
- width: 22px
- height: 22px
- color: var(--secondary-text-color)
name:
- font-size: 14px
- font-weight: 600
- justify-self: start
- text-align: left
state:
- font-size: 12px
- opacity: 0.8
- justify-self: start
- text-align: left
- type: custom:button-card
name: Dauer
icon: mdi:timer
show_name: false
entity: input_number.jacuzzi_min_runtime
tap_action:
action: call-service
service: input_number.increment
target:
entity_id: input_number.jacuzzi_min_runtime
hold_action:
action: call-service
service: input_number.decrement
target:
entity_id: input_number.jacuzzi_min_runtime
double_tap_action:
action: more-info
show_state: true
state_display: |
[[[
const v = Number(entity.state);
return `${v} min`;
]]]
aspect_ratio: 3/1
size: 22px
styles:
card:
- height: 60px
- padding: 6px 10px
- border-radius: 10px
- border: 1px solid
- background: |
[[[
const v = Number(entity.state);
if (v === 0) return '#424242'; // aus / deaktiviert
if (v <= 90) return '#2e7d32'; // grün (kurz, sicher)
if (v <= 150) return '#f9a825'; // gelb (normal)
return '#c62828'; // rot (lang)
]]]
name:
- font-size: 14px
- font-weight: 600
- color: "#cccccc"
icon:
- width: 22px
- height: 22px
- color: "#bdbdbd"
state:
- font-size: 14px
- font-weight: bold
- color: "#ffffff"
columns: 2
grid_options:
rows: 1
- square: false
type: grid
cards:
- type: custom:button-card
name: Dauer
icon: mdi:hydraulic-oil-temperature
show_name: false
entity: input_number.jacuzzi_target_temp
tap_action:
action: call-service
service: input_number.increment
target:
entity_id: input_number.jacuzzi_target_temp
hold_action:
action: call-service
service: input_number.decrement
target:
entity_id: input_number.jacuzzi_target_temp
double_tap_action:
action: more-info
show_state: true
state_display: |
[[[
const v = Number(entity.state);
return `${v} Grad`;
]]]
aspect_ratio: 3/1
size: 22px
styles:
card:
- height: 60px
- padding: 6px 10px
- border-radius: 10px
- border: 1px solid
- background: |
[[[
const v = Number(entity.state);
if (v === 0) return '#424242'; // aus / deaktiviert
if (v <= 90) return '#2e7d32'; // grün (kurz, sicher)
if (v <= 150) return '#f9a825'; // gelb (normal)
return '#c62828'; // rot (lang)
]]]
name:
- font-size: 14px
- font-weight: 600
- color: "#cccccc"
icon:
- width: 22px
- height: 22px
- color: "#bdbdbd"
state:
- font-size: 14px
- font-weight: bold
- color: "#ffffff"
columns: 2
- type: heading
heading_style: title
icon: mdi:heating-coil
heading: Automatisierungen Heizung
- square: false
type: grid
columns: 2
grid_options:
rows: auto
cards:
- type: custom:button-card
entity: automation.jacuzzi_pv_uberschuss_ein
name: Automatisierung Start
show_name: true
show_icon: false
show_state: true
layout: vertical
styles:
card:
- height: 60px
- padding: 6px 10px
- border-radius: 10px
- border: 1px solid
grid:
- grid-template-areas: "\"n\" \"s\""
- grid-template-rows: auto auto
- justify-items: start
name:
- font-size: 14px
- font-weight: 600
state:
- font-size: 13px
- font-weight: bold
state:
- value: "on"
styles:
card:
- background: "#2e7d32"
name:
- color: "#e8f5e9"
state:
- color: "#e8f5e9"
- value: "off"
styles:
card:
- background: "#c62828"
name:
- color: "#ffebee"
state:
- color: "#ffebee"
- type: custom:button-card
entity: automation.jacuzzi_pv_uberschuss_aus
name: Automatisierung Stop
show_name: true
show_icon: false
show_state: true
layout: vertical
styles:
card:
- height: 60px
- padding: 6px 10px
- border-radius: 10px
- border: 1px solid
grid:
- grid-template-areas: "\"n\" \"s\""
- grid-template-rows: auto auto
- justify-items: start
name:
- font-size: 14px
- font-weight: 600
state:
- font-size: 13px
- font-weight: bold
state:
- value: "on"
styles:
card:
- background: "#2e7d32"
name:
- color: "#e8f5e9"
state:
- color: "#e8f5e9"
- value: "off"
styles:
card:
- background: "#c62828"
name:
- color: "#ffebee"
state:
- color: "#ffebee"
- square: false
type: grid
cards:
- type: custom:button-card
name: PV-Ã�berschuß Stop
icon: mdi:power
show_name: true
show_icon: false
entity: input_number.jacuzzi_pv_stop
tap_action:
action: call-service
service: input_number.increment
target:
entity_id: input_number.jacuzzi_min_runtime
hold_action:
action: call-service
service: input_number.decrement
target:
entity_id: input_number.jacuzzi_min_runtime
double_tap_action:
action: more-info
show_state: true
state_display: |
[[[
const v = Number(entity.state);
return `${v} kW`;
]]]
aspect_ratio: 3/1
size: 22px
styles:
card:
- height: 60px
- padding: 6px 10px
- border-radius: 10px
- border: 1px solid
- background: |
[[[
const v = Number(entity.state);
if (v === 0) return '#424242'; // aus / deaktiviert
if (v <= 1) return '#2e7d32'; // grün (kurz, sicher)
if (v <= 2) return '#f9a825'; // gelb (normal)
return '#c62828'; // rot (lang)
]]]
name:
- font-size: 14px
- font-weight: 600
- color: "#cccccc"
icon:
- width: 22px
- height: 22px
- color: "#bdbdbd"
state:
- font-size: 14px
- font-weight: bold
- color: "#ffffff"
- type: custom:button-card
name: PV-Ã�berschuß Start
icon: mdi:power
show_name: true
show_icon: false
entity: input_number.jacuzzi_pv_start
tap_action:
action: call-service
service: input_number.increment
target:
entity_id: input_number.jacuzzi_min_runtime
hold_action:
action: call-service
service: input_number.decrement
target:
entity_id: input_number.jacuzzi_min_runtime
double_tap_action:
action: more-info
show_state: true
state_display: |
[[[
const v = Number(entity.state);
return `${v} kW`;
]]]
aspect_ratio: 3/1
size: 22px
styles:
card:
- height: 60px
- padding: 6px 10px
- border-radius: 10px
- border: 1px solid
- background: |
[[[
const v = Number(entity.state);
if (v === 0) return '#424242'; // aus / deaktiviert
if (v <= 2) return '#2e7d32'; // grün (kurz, sicher)
if (v <= 3) return '#f9a825'; // gelb (normal)
return '#c62828'; // rot (lang)
]]]
name:
- font-size: 14px
- font-weight: 600
- color: "#cccccc"
icon:
- width: 22px
- height: 22px
- color: "#bdbdbd"
state:
- font-size: 14px
- font-weight: bold
- color: "#ffffff"
columns: 2
grid_options:
rows: 1
- square: false
type: grid
cards:
- type: custom:button-card
name: Aktueller PV-Ã�berschuß
icon: mdi:power
show_name: true
show_icon: false
entity: sensor.em_available_for_heatpump
tap_action:
action: call-service
service: input_number.increment
target:
entity_id: input_number.jacuzzi_min_runtime
hold_action:
action: call-service
service: input_number.decrement
target:
entity_id: input_number.jacuzzi_min_runtime
double_tap_action:
action: more-info
show_state: true
state_display: |
[[[
const v = Number(entity.state);
return `${v} kW`;
]]]
aspect_ratio: 3/1
size: 22px
styles:
card:
- height: 60px
- padding: 6px 10px
- border-radius: 10px
- border: 1px solid
- background: |
[[[
const v = Number(entity.state);
if (v === 0) return '#f9a825'; // aus / deaktiviert
if (v <= 5) return '#2e7d32'; // grün (kurz, sicher)
if (v <= 14) return '#424242'; // gelb (normal)
return '#c62828'; // rot (lang)
]]]
name:
- font-size: 14px
- font-weight: 600
- color: "#cccccc"
icon:
- width: 22px
- height: 22px
- color: "#bdbdbd"
state:
- font-size: 14px
- font-weight: bold
- color: "#ffffff"
columns: 2
visible: []
cards: []
header:
card:
type: markdown
text_only: true
content: "# 🌿 Jacuzzi 🫧"
subview: true
Teil 2: Die smarte Gartendusche
Ausgangsproblem & Hintergrund
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.
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 Wasserdruck 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
Der YAML-Code
Helper & Automation (config/packages/dusche.yaml)
# config/packages/dusche.yaml
# --- Helper ---
input_number:
dusche_laufzeit:
name: "Dusche Laufzeit (Min)"
initial: 60
min: 5
max: 180
step: 5
unit_of_measurement: min
mode: box
icon: mdi:timer-outline
# --- Automation ---
automation:
- alias: "Dusche: Auto-Aus nach Laufzeit"
description: "Schließt das Duschventil automatisch nach konfigurierbarer Zeit"
mode: restart
trigger:
- platform: state
entity_id: valve.morpheus_reich_valve_4
to: "open"
action:
- delay:
minutes: "{{ states('input_number.dusche_laufzeit') | int(60) }}"
- condition: state
entity_id: valve.morpheus_reich_valve_4
state: "open"
- service: valve.close_valve
target:
entity_id: valve.morpheus_reich_valve_4
- service: system_log.write
data:
message: >
Dusche: automatisch geschlossen nach
{{ states('input_number.dusche_laufzeit') | int }} Min
level: info
Dashboard-Button (Lovelace YAML)
square: false
type: grid
cards:
- type: custom:button-card
entity: valve.morpheus_reich_valve_4
name: Dusche
icon: mdi:shower-head
show_state: true
size: 22px
state_display: |
[[[
return entity.state === 'open' ? 'Ein' : 'Aus';
]]]
tap_action:
action: toggle
hold_action:
action: more-info
state:
- value: open
styles:
card:
- background: "#2e7d32"
- border: 2px solid "#2e7d32"
icon:
- color: "#ffffff"
name:
- color: "#ffffff"
state:
- color: "#e8f5e9"
- value: closed
styles:
card:
- background: "#3a3a3a"
- border: "1px solid #666"
icon:
- color: "#9e9e9e"
name:
- color: "#cccccc"
state:
- color: "#aaaaaa"
styles:
card:
- height: 60px
- padding: 6px 10px
- border-radius: 10px
- background: var(--ha-card-background)
- border: 1px solid var(--divider-color)
- cursor: pointer
grid:
- grid-template-areas: "\"i n\" \"i s\""
- grid-template-columns: 32px auto
- grid-template-rows: auto auto
- align-items: center
- justify-items: start
icon:
- width: 22px
- height: 22px
- color: var(--secondary-text-color)
name:
- font-size: 14px
- font-weight: 600
- justify-self: start
- text-align: left
state:
- font-size: 12px
- opacity: 0.8
- justify-self: start
- text-align: left
- type: custom:button-card
name: Dauer
icon: mdi:timer
show_name: false
entity: input_number.dusche_laufzeit
tap_action:
action: call-service
service: input_number.increment
target:
entity_id: input_number.dusche_laufzeit
hold_action:
action: call-service
service: input_number.decrement
target:
entity_id: input_number.dusche_laufzeit
double_tap_action:
action: more-info
show_state: true
state_display: |
[[[
const v = Number(entity.state);
return `${v} min`;
]]]
aspect_ratio: 3/1
size: 22px
styles:
card:
- height: 60px
- padding: 6px 10px
- border-radius: 10px
- border: 1px solid
- background: |
[[[
const v = Number(entity.state);
if (v === 0) return '#424242'; // aus / deaktiviert
if (v <= 90) return '#2e7d32'; // grün (kurz, sicher)
if (v <= 150) return '#f9a825'; // gelb (normal)
return '#c62828'; // rot (lang)
]]]
name:
- font-size: 14px
- font-weight: 600
- color: "#cccccc"
icon:
- width: 22px
- height: 22px
- color: "#bdbdbd"
state:
- font-size: 14px
- font-weight: bold
- color: "#ffffff"
columns: 2
Fazit
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! ![]()






