Wellness-Oase-Garten mit HA Integration (Bestway)

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.



:camera_with_flash: Foto der Wellness-Oase im Garten mit Jacuzzi und Dusche


:bathtub: Teil 1: Der smarte Jacuzzi


:wrench: 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.

:backhand_index_pointing_right: 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

:link: 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.


:camera_with_flash: Screenshot der Bestway-Integration

:warning: 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.


:bullseye: Ziele meiner Automatisierung

  • :sun: Jacuzzi heizt nur bei ausreichendem PV-Überschuss (≥ 2 kW für 2 Minuten)
  • :stop_button: Automatisches Abschalten wenn Überschuss zu gering (< 1 kW für 3 Minuten)
  • :stopwatch: Mindestlaufzeit von 30 Minuten schützt vor ständigem Ein-/Ausschalten
  • :droplet: Regelmäßige Filterzyklen – unabhängig davon ob die Heizung läuft
  • :brain: War der Spa manuell eingeschaltet: Automatisierung greift nur in Heizung und Filter ein – Spa bleibt an
  • :e_mail: E-Mail-Benachrichtigungen bei jedem automatischen Start, Stopp und bei Fehlern
  • :bar_chart: Übersichtliches Dashboard zur Steuerung und Statusanzeige

:electric_plug: 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)

:high_voltage: 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.


:puzzle_piece: Helper – automatisch durch das Package erstellt

Das Package erstellt beim Start alle benötigten Helfer automatisch. Kein manuelles Anlegen notwendig.

:radio_button: 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.

:gear: 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)

:one_o_clock: 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)

:light_bulb: Tipp: Die Heizphasen tagsüber liefern automatisch zusätzliche Filterzeit – die zwei konfigurierten Zyklen dienen als Mindestabsicherung für Tage ohne PV-Überschuss.


:robot: Die 5 Automationen im Überblick

:one: 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.

:backhand_index_pointing_right: 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.

:two: 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.

:backhand_index_pointing_right: War der Spa vor dem PV-Start bereits manuell eingeschaltet, wird er nicht ausgeschaltet – nur Heizung und Filter stoppen.

:three: 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

:four: 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.

:five: 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.

:light_bulb: Tipp zur Filterpflege: Die Filterkartusche sollte spätestens alle 2–3 Tage gereinigt werden, sonst kommt es schnell zu Fehlern durch schlechten Durchfluss.


:desktop_computer: 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.


:camera_with_flash: 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.


:camera_with_flash: Die fünf Steuer-Buttons – verschiedene Zustände sichtbar

:pushpin: Ü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.


:camera_with_flash: 4-Button-Übersichts-Widgets auf dem Hauptdashboard


:high_voltage: 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.

:camera_with_flash: Screenshot der drei Verbrauchs-Anzeigen auf der Jacuzzi-Detailseite


:clipboard: 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


:shower: Teil 2: Die smarte Gartendusche


:wrench: 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.


:camera_with_flash: 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 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.


:bullseye: Ziele

  • :stopwatch: Dusche schaltet sich nach konfigurierbarer Zeit automatisch ab (Standard: 60 Minuten)
  • :1234: Laufzeit per Eingabefeld einstellbar (5–180 Minuten) – kein Schieberegler
  • :counterclockwise_arrows_button: Öffnet man die Dusche erneut während der Timer läuft, startet der Countdown von vorne
  • :label: Dashboard-Button zeigt „Ein" / „Aus" statt der internen Zustände „open" / „closed"

:puzzle_piece: Helper

  • input_number.dusche_laufzeit – Einstellbare Laufzeit bis zum automatischen Abschalten. Standard: 30 Min, Bereich: 5–180 Min, Eingabe als Zahlenfeld (kein Schieberegler).

:robot: 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.


:desktop_computer: 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
:camera_with_flash: Screenshot des Dusche-Buttons im Dashboard – sowie der Laufzeit


:clipboard: 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


:white_check_mark: 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! :backhand_index_pointing_down: