Wasserwerte mittels BlueConnect im HA anzeigen?

Super danke für deine Hilfe mit der Inegration hat es geklappt vielen Dank :grinning_face:

1 „Gefällt mir“

Ich habe ebenfalls Blue Connect im Einsatz, aber über den Blue Connect Extender. Leider gibt es dafür keine Integration und ich bekomme die Fehlermeldung kein Gerät gefunden (wie Andreas). Jetzt habe ich die Bluetooth-Verbindung zum Extender entfernt, aber ich bekomme immer noch die Meldung kein Gerät gefunden. Ich habe den ESP32-WROOM-32, der ist etwa 5m entfernt. Mit dem Extender hat das so immer gepasst. Hat jemand schon einmal den Blue Connect Extender eingebunden? Falls ja, welche Entitäten gibt mir der aus?

Ich habe auch den ESP32-WROOM-32 im einsatz.. und wie gesagt diese Integration: GitHub - adamantivm/BlueConnect: Home Assistant custom component for Blueriiot Blue Connect Go Damit klappt es einwandfrei. Vor allem : ohne Abo.

Vieleicht den Blue Connect mal zurücksetzen ? Vieleicht ist der irgendwie mit dem Extender “verheiratet” ?

Es hat jetzt erst einmal funktioniert, dazu musste ich den Abstand zum Blue Connect bis auf 2m reduzieren. Danach habe ich den ESP wieder auf 5m entfernt. 9 Entitäten werden jetzt mit Werten befüllt. Mehr Werte als über die App angezeigt werden. Jetzt habe ich das Intervall mal auf 5 Minuten reduziert und warte ab ob die Werte sich ändern.

1 „Gefällt mir“

Da die Poolsaison vor der Tür steht und der Whirlpool immer Saison hat, wollte ich nur kurz mitteilen, dass bei mir auch alles unter HA 2026.3.1 läuft.


Der ESP steht direkt am Whirlpool.

Benutzt du den Extender WiFi oder über BLE, weil ich kriege meinen nicht in HA eingebunden.

Ich finde ihn als Bluetooth gerät in HA kann aber nicht draufzugreifen oder verbinden.

Ich benutze einen ESP32, den ich inzwischen mit folgendem Code über ESPHome Builder in HA geflasht habe. Jetzt bekomme ich zwar die Batteriespannung nicht mehr angezeigt, aber die Boje meldet sich akustisch, wenn der Akku leer ist.

substitutions:
  device_name: pool-proxy-whirlpool
  friendly_name: "Pool Proxy – Whirlpool"

esphome:
  name: ${device_name}
  friendly_name: ${friendly_name}

esp32:
  board: esp32dev
  framework:
    type: esp-idf

logger:
  level: INFO

api:
  encryption:
    key: !secret pool_proxy_whirlpool_api_key

ota:
  - platform: esphome
    password: !secret ota_password


wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  power_save_mode: none

captive_portal:

esp32_ble_tracker:
  scan_parameters:
    interval: 320ms
    window: 30ms
    active: true

ble_client:
  - mac_address: "00:00:00:00:00:00"
    id: ble_client_whirlpool

button:
  - platform: template
    name: "${friendly_name} Messung auslösen"
    on_press:
      then:
        - ble_client.ble_write:
            id: ble_client_whirlpool
            service_uuid: 'F3300001-F0A2-9B06-0C59-1BC4763B5C00'
            characteristic_uuid: 'F3300002-F0A2-9B06-0C59-1BC4763B5C00'
            value: [0x01]

  - platform: restart
    name: "${friendly_name} Neustart"
    entity_category: diagnostic

  - platform: safe_mode
    name: "${friendly_name} Safe Mode"
    entity_category: diagnostic

sensor:
  - platform: template
    name: "${friendly_name} Temperatur"
    id: sensor_whirlpool_temperature
    unit_of_measurement: "°C"
    state_class: measurement
    device_class: temperature

  - platform: template
    name: "${friendly_name} pH"
    id: sensor_whirlpool_ph
    state_class: measurement

  - platform: template
    name: "${friendly_name} ORP"
    id: sensor_whirlpool_orp
    unit_of_measurement: "mV"
    state_class: measurement

  - platform: wifi_signal
    name: "${friendly_name} WLAN Signal"
    update_interval: 60s
    entity_category: diagnostic

  - platform: uptime
    name: "${friendly_name} Laufzeit"
    entity_category: diagnostic

  - platform: ble_rssi
    mac_address: "00:00:00:00:00:00"
    name: "${friendly_name} Boje BLE Signal"

text_sensor:
  - platform: wifi_info
    ip_address:
      name: "${friendly_name} IP"
      entity_category: diagnostic

  - platform: ble_client
    id: whirlpool_reading_data
    name: "${friendly_name} raw data"
    internal: true
    ble_client_id: ble_client_whirlpool
    service_uuid: 'F3300001-F0A2-9B06-0C59-1BC4763B5C00'
    characteristic_uuid: 'F3300003-F0A2-9B06-0C59-1BC4763B5C00'
    notify: true
    update_interval: never
    on_notify:
      then:
        - lambda: |-
            std::string rawhex = format_hex_pretty((uint8_t *) x.c_str(), x.size()).c_str();
            ESP_LOGD("raw_hex", "Rohdaten der Boje: %s", rawhex.c_str());

            float temperature = (float)((int16_t)(x[2]<< 8) + x[1])/100;
            id(sensor_whirlpool_temperature).publish_state(temperature);

            float raw_ph = (float)( (int16_t) (x[4]<< 8) + x[3]);
            float ph = (float)( (int16_t) (2048 - raw_ph)) / 232  + 7;
            id(sensor_whirlpool_ph).publish_state(ph);

            float orp = (float)( (int16_t) (x[6]<< 8) + x[5]);
            id(sensor_whirlpool_orp).publish_state(orp/4);

Du musst im Code noch an zwei Stellen die MAC deiner Boje eintragen. Ersetze 00:00:00:00:00:00 mit deiner MAC. Gemini beschreibt das Vorgehen so:

  1. Code mit der Nuller-MAC flashen.
  2. Den geflashten ESP32 in Reichweite der Boje bringen.
  3. In ESPHome auf “Logs” klicken. Dort taucht der ESP32 auf und listet (dank des esp32_ble_tracker im Code) alle Bluetooth-Geräte in der Umgebung auf.
  4. Die richtige MAC-Adresse der Boje aus dem Log kopieren.
  5. Die Nullen im Code durch die echte MAC ersetzen.
  6. Noch einmal flashen (geht jetzt ganz bequem und schnell kabellos über WLAN/OTA).

Die secrets.yaml (rechts oben) nicht vergessen. :slight_smile:


Der ESP32 ist per USB an einem entsprechenden Netzteil angeschlossen und empfängt per BLE die Signale der Boje, die per WLAN vom ESP32 weitergeschickt werden.
Wenn alles geklappt hat, siehst du die Werte in HA.


:sweat_droplets: Da ich parallel noch einen Poollab für die Messung der anderen Wasserwerte benutze, kann ich inzwischen sogar schon den Sondendrift der Boje automatisch korrigieren lassen und auch sonst habe ich in Zusammenarbeit mit Gemini und Claude meine Poolkarten “etwas” erweitert.
Ich bekomme jetzt die Dosiermengen mundgerecht mitgeteilt und muss nicht mehr rechnen.
Die Boje misst ständig → Poollab unregelmäßig (händisch) und korrigiert ggf. die Boje, die ab dann den korrigierten Wert (Sondendrift) anzeigt.
Für die Chemie habe ich mir auch eine Karte erstellen lassen, wo ich die Zielwerte eingebe.

:rocket: Wie würde Tom-HA schreiben???
… komplett lokal und ohne Abo. :slight_smile: Bei mir nur ohne KI oder AI.




:robot: Der Sondendrift wird nur in dem Moment berechnet, wo die genauen Werte vom Poollab kommen.

Die Karte für Zielwerte und Produktparameter, auf die sich die Formeln der Berechnungen beziehen.

:information_source: Für mich privat funktioniert es.
Das letzte Wort hat im Moment aber noch der Messwert vom Poollab und der gesunde Menschenverstand.
Gemini und Claude haben zu oft und mit zu viel Selbstsicherheit absoluten Quatsch behauptet.

1 „Gefällt mir“

Das sieht richtig nice aus. Ich werde das mal probieren.

Die Karten und die Berechnungen sind anderer Code. Da müsste ich erstmal hinsetzen und noch mal in mich gehen… Ich hatte die Berechnungen zuerst direkt in den Karten, aber das war nicht pflegeleicht. Bei jeder Korrektur musste ich jede Karte anfassen. Inzwischen ist dieser Code in yaml Dateien ausgelagert und diverse Helfer mussten angelegt werden.
Da ich vom Programmieren keine Ahnung habe, habe ich Claude und Gemini so lange genervt, bis ich die Karten hatte, die ich jetzt habe. So ist das über Wochen gewachsen und ich habe natürlich nichts dokumentiert, weil ich am Anfang eigentlich nur die Boje über ESP32 einbinden wollte. Die Karten waren erstmal nur ein Versuch, der sich dann schnell ausgewachsen hat, als die ersten Schritte funktionierten.

Wenn die Boje über den ESP eingebunden ist, hast du “erstmal” nur das in HA.

:white_check_mark: Vielleicht reicht ja für den Anfang die Pool Monitor Card.

:right_arrow: Damit konnte ich die Boje bei meinen ersten Versuchen auch in HA einbinden.
ESP32 als Bluetooth Proxy in HA eingebunden → ESP32 in der Nähe der Boje platziert …

@Kai2 gibt es einen Grund warum du von der HACS Integration direkt auf den ESP32 gewechselt bist? Hattest du Probleme mit der Integration?
Ich hab seit gestern meinen BlueConnect und bin jetzt auch dabei diesen mit HomeAssistant einzurichten. Mit dem BLE Proxy und der Integration läuft es bisher.

Noch eine Frage: Mit dem freien Chlor-Wert kann man wahrscheinlich nicht arbeiten, oder?
Der zeigt bei mir einen sehr hohen Wert an

Hallo @Herby189, ich bin umgestiegen, weil es bei mir immer wieder mal zu Verbindungsabbrüchen kam. Mal war es das WLAN, mal wurde die Boje nicht gefunden. Ich hatte langsam keine Idee mehr und habe die KI befragt. :slight_smile:
Inzwischen glaube ich, dass es auch am ESP lag, der einfach nicht nah genug an der Boje war. Ein ESP mit externer Antenne wäre vielleicht die bessere Wahl. Ich musste dann immer raus, den ESP stromlos machen, wieder mit Strom versorgen rein und hoffen.

Da ich inzwischen einiges geändert habe, kann ich die genaue Ursache der wiederkehrenden Verbindungsabbrüche nicht nennen. Toi toi toi! Im Moment läuft es seit einigen Wochen stabil. Der ESP ist nun maximal einen Meter von der Boje entfernt, je nach dem, wo die gerade im Wasser schwimmt. Wenn das so bleibt, finalisiere ich das bei mir, indem ich einen ESP32 mit externer Antenne bestelle, den in einem wasserdichten Gehäuse verbaue und dann wieder im “Keller” versenke, also unter der umlaufenden Holzterrasse vom Pool/Whirlpool.

Claude hat jetzt eine einfache Karten erstellt, die nur die Entitäten der Blueriiot Boje benötigt. Das angezeigt freie Chlor hat Claude direkt vernachlässigt. :slight_smile: Du kannst die Karte auf eigene “Gefahr” als Hinweis zu Wasserqualität verwenden, verbessern usw.
Einfach deine drei Entitäten der Boje hinterlegen, dann sollte die Karte etwas anzeigen.

:information_source:
Wichtig für die Berechnung ist, dass du auch dein Beckenvolumen (aktuell 1.1 m³, deinen pH-Zielwert (aktuell 7.2) und die Dosiermenge deiner Chemie anpasst. Das steht aber unten in der Anleitung, die Claude erstellt hat.

Das ist die Vorschau der Karte, bevor dieEntitäten hinterlegt wurden.

Hier habe ich meine drei Entitäten der Boje hinterlegt. Einfach STRG+F drücken und dann die vorhandene Entität mit deiner ersetzen (REPLACE ALL)

Code + Einbauanleitung von Claude

:clipboard: Karteneinbau – Kurzanleitung

Blueriiot Dosierempfehlungen-Karte

Voraussetzungen

  • HACS-Integrationen: card-mod, multiple-entity-row
  • Blueriiot-Integration in HA eingerichtet
  • Ein input_boolean für den Dosieranweisung-Toggle (z.B. input_boolean.erklarung_whirlpool)

Sensor-IDs anpassen

Die Karte hat drei Stellen die angepasst werden müssen – alle mit # ANPASSEN markiert:

entity: sensor.ph_whirlpool       # → deinen Blueriiot pH-Sensor eintragen
entity: sensor.redox_whirlpool    # → deinen Blueriiot Redox-Sensor
entity: sensor.temp_whirlpool     # → deinen Blueriiot Temperatur-Sensor

Sensor-IDs findest du unter Einstellungen → Geräte & Dienste → Blueriiot → Sensoren.

Zusätzlich in den {% set ... %} Blöcken anpassen:

{% set vol    = 1.1 %}   # Beckenvolumen in m³
{% set ph_z   = 7.2 %}   # pH-Zielwert
{% set faktor = 10.0 %}  # g pH-Minus/Plus pro m³ pro 0.1 pH (Bayrol-Standard)

Toggle-Helfer

Der Dosieranweisung-Toggle benötigt einen input_boolean.:

Unter Einstellungen → Geräte & Dienste → Helfer → Helfer erstellen → Schalter einen neuen Helfer anlegen, z.B. blueriiot_tipps. Dann in der Karte alle drei Vorkommen von input_boolean.erklarung_whirlpool durch input_boolean.blueriiot_tipps ersetzen.

Einbau

Dashboard → BearbeitenKarte hinzufügenManuell
Gesamten Inhalt einfügen → Entitäten anpassenSpeichern


# ==============================================================================
# Blueriiot Boje – Dosierempfehlungen (Standalone-Karte)
# Keine Package-Installation nötig – alles wird direkt in der Karte berechnet.
#
# ANPASSEN:
#   Sensor-IDs: alle Zeilen mit "# ANPASSEN" suchen
#   Zielwerte & Volumen: am Anfang jedes Sensor-Blocks als {% set ... %}
#
# Benötigt: card-mod, multiple-entity-row, mini-graph-card, button-card
# Optional: input_boolean.erklarung_whirlpool (für Dosieranweisung-Toggle)
#   → Fehlt der Helfer, einfach den Toggle-Block und Conditional-Block entfernen
# ==============================================================================

type: vertical-stack
cards:

  # ── Kopfzeile ──────────────────────────────────────────────────────────────
  - type: markdown
    content: >
      {%- set ph_sensor   = 'sensor.ph_whirlpool' -%}
      {%- set orp_sensor  = 'sensor.redox_whirlpool' -%}
      {%- set temp_sensor = 'sensor.temp_whirlpool' -%}
      {%- set ph_z = 7.2 -%}
      {%- set ph   = states(ph_sensor)  | float(-1) -%}
      {%- set orp  = states(orp_sensor) | float(-1) -%}
      {%- set temp = states(temp_sensor)| float(-1) -%}
      {%- set ph_ok  = ph  > 6.9 and ph  < 7.5 -%}
      {%- set orp_ok = orp > 700 and orp < 780  -%}
      {%- if ph < 0 -%}
        {%- set status = '⚠️ Boje nicht verfügbar' -%}
      {%- elif ph_ok and orp_ok -%}
        {%- set status = '✅ Wasserqualität gut' -%}
      {%- elif not ph_ok -%}
        {%- set status = '⚠️ pH außerhalb Zielbereich' -%}
      {%- else -%}
        {%- set status = '⚠️ Redox außerhalb Zielbereich' -%}
      {%- endif -%}
      <b>{{ status }}</b>{% if temp > 0 %} · {{ temp | round(1) }}°C{% endif %}
    card_mod:
      style: |
        ha-card {
          background: var(--card-background-color) !important;
          border-radius: 16px 16px 0 0 !important;
          border-bottom: none !important;
          box-shadow: none !important;
          padding: 10px 16px 8px 16px !important;
          font-size: 15px !important;
        }

  # ── Messwerte + Inline-Dosierempfehlungen ─────────────────────────────────
  - type: entities
    show_header_toggle: false
    card_mod:
      style: |
        ha-card {
          background: var(--card-background-color) !important;
          border-radius: 0 !important;
          border-top: none !important;
          border-bottom: none !important;
          box-shadow: none !important;
        }
    entities:

      # ── pH ────────────────────────────────────────────────────────────────
      - entity: sensor.ph_whirlpool    # ANPASSEN
        type: custom:multiple-entity-row
        name: "pH-Wert (live)"
        icon: mdi:ph
        format: precision2
        unit: pH
        card_mod:
          style: >
            {% set ph     = states('sensor.ph_whirlpool') | float(-1) %}
            {% set vol    = 1.1 %}
            {% set ph_z   = 7.2 %}
            {% set faktor = 10.0 %}
            {% set mg = (((ph - ph_z) | abs / 0.1) * faktor * vol) | round(0) | int %}
            {% if ph < 0 %}
              {% set s = '❓ Keine Daten' %}
              {% set fg = '#9e9e9e' %}{% set bg = 'rgba(158,158,158,0.08)' %}
              {% set dos = '' %}
            {% elif ph > 7.7 or ph < 6.7 %}
              {% set s = '🚨 Viel zu ' ~ ('hoch!' if ph > ph_z else 'niedrig!') %}
              {% set fg = '#ef5350' %}{% set bg = 'rgba(239,83,80,0.12)' %}
              {% set dos = '➕ ' ~ mg ~ 'g pH-' ~ ('Minus' if ph > ph_z else 'Plus') ~ ' zugeben.' %}
            {% elif ph > 7.5 or ph < 6.9 %}
              {% set s = '⚠️ Zu ' ~ ('hoch' if ph > ph_z else 'niedrig') %}
              {% set fg = '#ffa726' %}{% set bg = 'rgba(255,167,38,0.12)' %}
              {% set dos = '➕ ' ~ mg ~ 'g pH-' ~ ('Minus' if ph > ph_z else 'Plus') ~ ' zugeben.' %}
            {% else %}
              {% set s = '✅ pH-Wert ideal!' %}
              {% set fg = '#66bb6a' %}{% set bg = 'rgba(102,187,106,0.12)' %}
              {% set dos = '' %}
            {% endif %}
            {% set msg = s ~ ('\A ' ~ dos if dos else '') %}
            :host { display: block; margin-bottom: 10px !important; position: relative; }
            :host::before {
              content: "Ziel: {{ ph_z }} pH  |  Vol: {{ vol }} m³  |  Faktor: {{ faktor }}g/m³/0.1 pH";
              display: block; position: absolute; bottom: 8px; left: 12px;
              font-size: 11px; color: rgba(255,255,255,0.4); z-index: 2;
            }
            :host::after {
              content: "{{ msg }}";
              background: {{ bg }} !important; color: {{ fg }} !important;
              display: block; margin-top: 8px; padding: 7px 12px 26px 12px;
              border-radius: 6px; font-size: 14px; font-weight: 600;
              line-height: 1.4; white-space: pre-wrap; box-sizing: border-box;
            }

      # ── Redox ─────────────────────────────────────────────────────────────
      - entity: sensor.redox_whirlpool    # ANPASSEN
        type: custom:multiple-entity-row
        name: "Redox (Desinfektionskraft)"
        icon: mdi:lightning-bolt-circle
        format: precision0
        unit: mV
        card_mod:
          style: >
            {% set v = states('sensor.redox_whirlpool') | float(-1) %}
            {% set vi = v | round(0) | int %}
            {% if v < 0 %}
              {% set s = '❓ Keine Daten' %}
              {% set fg = '#9e9e9e' %}{% set bg = 'rgba(158,158,158,0.08)' %}{% set dos = '' %}
            {% elif v < 650 %}
              {% set s = '🚨 Viel zu niedrig! (' ~ vi ~ ' mV)' %}
              {% set fg = '#ef5350' %}{% set bg = 'rgba(239,83,80,0.12)' %}
              {% set dos = 'Chlor prüfen & pH senken – Keime kaum abgetötet.' %}
            {% elif v > 850 %}
              {% set s = '🚨 Zu hoch! (' ~ vi ~ ' mV)' %}
              {% set fg = '#ef5350' %}{% set bg = 'rgba(239,83,80,0.12)' %}
              {% set dos = 'Chlor abbauen lassen – Abdeckung öffnen.' %}
            {% elif v < 700 %}
              {% set s = '⚠️ Zu niedrig (' ~ vi ~ ' mV)' %}
              {% set fg = '#ffa726' %}{% set bg = 'rgba(255,167,38,0.12)' %}
              {% set dos = 'pH und Chlor prüfen – Desinfektion nicht optimal.' %}
            {% elif v > 780 %}
              {% set s = '⚠️ Leicht erhöht (' ~ vi ~ ' mV)' %}
              {% set fg = '#ffa726' %}{% set bg = 'rgba(255,167,38,0.12)' %}
              {% set dos = 'Noch unbedenklich, beobachten.' %}
            {% else %}
              {% set s = '✅ Desinfektion optimal! (' ~ vi ~ ' mV)' %}
              {% set fg = '#66bb6a' %}{% set bg = 'rgba(102,187,106,0.12)' %}{% set dos = '' %}
            {% endif %}
            {% set msg = s ~ ('\A ' ~ dos if dos else '') %}
            :host { display: block; margin-bottom: 10px !important; position: relative; }
            :host::before {
              content: "Ziel: 700–780 mV  |  Kein Chlor-Sensor – Redox als Proxy";
              display: block; position: absolute; bottom: 8px; left: 12px;
              font-size: 11px; color: rgba(255,255,255,0.4); z-index: 2;
            }
            :host::after {
              content: "{{ msg }}";
              background: {{ bg }} !important; color: {{ fg }} !important;
              display: block; margin-top: 8px; padding: 7px 12px 26px 12px;
              border-radius: 6px; font-size: 14px; font-weight: 600;
              line-height: 1.4; white-space: pre-wrap; box-sizing: border-box;
            }

      # ── Temperatur ────────────────────────────────────────────────────────
      - entity: sensor.temp_whirlpool    # ANPASSEN
        type: custom:multiple-entity-row
        name: "Temperatur"
        icon: mdi:thermometer
        format: precision1
        unit: °C
        card_mod:
          style: >
            {% set t = states('sensor.temp_whirlpool') | float(-1) %}
            {% if t < 0 %}
              {% set s = '❓ Keine Daten' %}{% set fg = '#9e9e9e' %}{% set bg = 'rgba(158,158,158,0.08)' %}
            {% elif t < 30 %}
              {% set s = '🌡️ Aufheizen – ' ~ t|round(1) ~ '°C' %}{% set fg = '#29b6f6' %}{% set bg = 'rgba(41,182,246,0.10)' %}
            {% elif t > 40 %}
              {% set s = '🔴 Zu heiß! ' ~ t|round(1) ~ '°C' %}{% set fg = '#ef5350' %}{% set bg = 'rgba(239,83,80,0.12)' %}
            {% elif t > 38 %}
              {% set s = '⚠️ Grenzwertig warm – ' ~ t|round(1) ~ '°C' %}{% set fg = '#ffa726' %}{% set bg = 'rgba(255,167,38,0.12)' %}
            {% else %}
              {% set s = '✅ Betriebstemperatur – ' ~ t|round(1) ~ '°C' %}{% set fg = '#66bb6a' %}{% set bg = 'rgba(102,187,106,0.12)' %}
            {% endif %}
            :host { display: block; margin-bottom: 4px !important; }
            :host::after {
              content: "{{ s }}";
              background: {{ bg }} !important; color: {{ fg }} !important;
              display: block; margin-top: 8px; padding: 7px 12px;
              border-radius: 6px; font-size: 14px; font-weight: 600;
            }

  # ── Dosieranweisung & Hilfe Toggle ────────────────────────────────────────
  # Benötigt: input_boolean.erklarung_whirlpool (oder eigenen Helfer anlegen)
  - type: entities
    show_header_toggle: false
    state_color: true
    card_mod:
      style: |
        ha-card {
          background-color: var(--card-background-color) !important;
          border-top: none !important;
          border-top-left-radius: 0px !important;
          border-top-right-radius: 0px !important;
          box-shadow: none !important;
          {% if is_state('input_boolean.erklarung_whirlpool', 'on') %}
          border-bottom: none !important;
          border-bottom-left-radius: 0px !important;
          border-bottom-right-radius: 0px !important;
          {% else %}
          border-bottom-left-radius: 16px !important;
          border-bottom-right-radius: 16px !important;
          {% endif %}
        }
    entities:
      - entity: input_boolean.erklarung_whirlpool  # ANPASSEN falls anderer Helfer
        name: Dosieranweisung & Hilfe
        icon: mdi:school-outline
        tap_action:
          action: none

  # ── Dosieranweisung (conditional) ─────────────────────────────────────────
  - type: conditional
    conditions:
      - entity: input_boolean.erklarung_whirlpool  # ANPASSEN
        state: "on"
    card:
      type: markdown
      card_mod:
        style: |
          ha-card {
            background-color: var(--card-background-color) !important;
            border-top: none !important;
            border-top-left-radius: 0px !important;
            border-top-right-radius: 0px !important;
            border-bottom-left-radius: 16px !important;
            border-bottom-right-radius: 16px !important;
            box-shadow: none !important;
          }
      content: >
        {% set ph_sensor   = 'sensor.ph_whirlpool' %}
        {% set orp_sensor  = 'sensor.redox_whirlpool' %}
        {% set temp_sensor = 'sensor.temp_whirlpool' %}
        {% set ph     = states(ph_sensor)   | float(-1) %}
        {% set orp    = states(orp_sensor)  | float(-1) %}
        {% set temp   = states(temp_sensor) | float(-1) %}
        {% set vol    = 1.1 %}
        {% set ph_z   = 7.2 %}
        {% set faktor = 10.0 %}
        {% set mg = (((ph - ph_z) | abs / 0.1) * faktor * vol) | round(0) | int if ph > 0 else 0 %}
        {% set step = namespace(n=1) %}
        {% set actions = namespace(needed=false) %}

        **<font color="#ffa726">Schritt-für-Schritt bei {{ temp | round(1) if temp > 0 else '?' }}°C</font>**

        ***

        {% if ph < 0 %}
        ### ⚠️ Boje nicht verfügbar
        Bitte Verbindung zur Blueriiot-Boje prüfen.

        {% else %}

        {% if ph > 7.5 %}
        ### <font color="#29b6f6">Schritt {{ step.n }}: pH senken</font>
        pH aktuell **{{ ph | round(2) }}** (Ziel: {{ ph_z }}) → <font color="#ffa726"><b>{{ mg }}g pH-Minus</b></font> zugeben. Nach 2 Stunden erneut messen.
        {% set step.n = step.n + 1 %}{% set actions.needed = true %}

        ***

        {% elif ph < 6.9 %}
        ### <font color="#29b6f6">Schritt {{ step.n }}: pH anheben</font>
        pH aktuell **{{ ph | round(2) }}** (Ziel: {{ ph_z }}) → <font color="#ffa726"><b>{{ mg }}g pH-Plus</b></font> zugeben. Nach 2 Stunden erneut messen.
        {% set step.n = step.n + 1 %}{% set actions.needed = true %}

        ***

        {% endif %}

        {% if orp < 700 %}
        ### <font color="#29b6f6">Schritt {{ step.n }}: Desinfektion verbessern</font>
        Redox: **{{ orp | round(0) | int }} mV** (Ziel: 700–780 mV) → Chlor nachfüllen und pH prüfen. Blueriiot misst kein freies Chlor direkt – Redox ist der Proxy.
        {% set step.n = step.n + 1 %}{% set actions.needed = true %}

        ***

        {% elif orp > 780 %}
        ### <font color="#29b6f6">Schritt {{ step.n }}: Redox beobachten</font>
        Redox: **{{ orp | round(0) | int }} mV** – leicht erhöht, aber noch unbedenklich. Abdeckung gelegentlich öffnen.
        {% set step.n = step.n + 1 %}{% set actions.needed = true %}

        ***

        {% endif %}

        {% if not actions.needed %}
        ### <font color="#66bb6a">🎉 Alles in Ordnung!</font>
        pH und Redox im Zielbereich – genieß das Wasser!

        ***

        {% endif %}

        {% endif %}

        ### 📖 Hinweise zur Blueriiot-Boje

        <details style="background: rgba(0,0,0,0.25); padding: 12px; border-radius: 6px; margin-bottom: 8px;">
          <summary style="font-weight: bold; cursor: pointer; color: #ffb74d;">⚗️ pH-Dosierung mit pH-Minus / pH-Plus</summary>
          <p style="margin-top: 8px; line-height: 1.4; color: #e0e0e0;">Formel: Abweichung vom Ziel × 10g × Volumen (m³). Immer in kleinen Mengen dosieren, nie direkt auf Ansaugöffnungen gießen. 2h warten, dann neu messen.</p>
        </details>

        <details style="background: rgba(0,0,0,0.25); padding: 12px; border-radius: 6px; margin-bottom: 8px;">
          <summary style="font-weight: bold; cursor: pointer; color: #ffb74d;">⚡ Redox als Chlor-Proxy</summary>
          <p style="margin-top: 8px; line-height: 1.4; color: #e0e0e0;">Blueriiot misst kein freies Chlor direkt. Der Redox-Wert (ORP) zeigt die Desinfektionskraft: 700–780 mV ist ideal. Niedriger Redox → Chlor nachfüllen. Hoher pH senkt den Redox-Wert drastisch.</p>
        </details>

        <details style="background: rgba(0,0,0,0.25); padding: 12px; border-radius: 6px; margin-bottom: 8px;">
          <summary style="font-weight: bold; cursor: pointer; color: #ffb74d;">⚖️ pH & Chlor-Wirksamkeit</summary>
          <p style="margin-top: 8px; line-height: 1.4; color: #e0e0e0;">Bei pH 7.0 ist Chlor zu 75% aktiv. Bei pH 7.8 nur noch 20%! Deshalb immer zuerst pH korrigieren, dann Chlor beurteilen.</p>
        </details>

        <details style="background: rgba(0,0,0,0.25); padding: 12px; border-radius: 6px; margin-bottom: 8px;">
          <summary style="font-weight: bold; cursor: pointer; color: #ffb74d;">🧪 Alkalinität & Calcium</summary>
          <p style="margin-top: 8px; line-height: 1.4; color: #e0e0e0;">Blueriiot misst keine Alkalinität und Calciumhärte. Diese Werte sollten regelmäßig mit Teststreifen oder Poollab geprüft werden: TA 80–120 mg/l, Ca 200–400 mg/l (Whirlpool).</p>
        </details>

card_mod:
  style: |
    #root { gap: 0px !important; }

Was hier auf jeden Fall fehlt, ist Chlor. Aber das kann die Boje nicht messen. Vielleicht kannst du den Code der Karte dahingehende verbessern (lassen), dass der errechnete Wert für das freie Chlor mit berücksichtigt werden kann.
Ich messe Chlor mit dem Poollab und dieser Messwert wird bei meinen “ausführlichen” Karten bei der Dosierungsempfehlung und bei der Bewertung der Wasserqualität mit berücksichtig.