Super danke für deine Hilfe mit der Inegration hat es geklappt vielen Dank ![]()
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.
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:
- Code mit der Nuller-MAC flashen.
- Den geflashten ESP32 in Reichweite der Boje bringen.
- In ESPHome auf “Logs” klicken. Dort taucht der ESP32 auf und listet (dank des
esp32_ble_trackerim Code) alle Bluetooth-Geräte in der Umgebung auf. - Die richtige MAC-Adresse der Boje aus dem Log kopieren.
- Die Nullen im Code durch die echte MAC ersetzen.
- Noch einmal flashen (geht jetzt ganz bequem und schnell kabellos über WLAN/OTA).
Die secrets.yaml (rechts oben) nicht vergessen. ![]()
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.
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.
Wie würde Tom-HA schreiben???
… komplett lokal und ohne Abo.
Bei mir nur ohne KI oder AI.
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.
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.
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.
Vielleicht reicht ja für den Anfang die Pool Monitor Card.
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. ![]()
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.
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.
![]()
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
Karteneinbau – Kurzanleitung
Blueriiot Dosierempfehlungen-Karte
Voraussetzungen
- HACS-Integrationen: card-mod, multiple-entity-row
- Blueriiot-Integration in HA eingerichtet
- Ein
input_booleanfü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 → Bearbeiten → Karte hinzufügen → Manuell
Gesamten Inhalt einfügen → Entitäten anpassen → Speichern
# ==============================================================================
# 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.
Das sieht alles sehr cool aus, ich bekomme leider absolut nicht hin, da ich mich Null mit Coden auskenne.
Mein BlueConnect verbindet sich auch nicht mit dem Proxy über die Integration.,
Die Messboje erlaubt nur eine BLE Verbindung.
Ist die Boje vielleicht über dein Handy mit der App verbunden, wenn du in der Nähe der Boje bist und kann deswegen keine Verbindung über die Blue Connect Go Integration zu HA aufbauen?
Ich wollte neulich die Boje mit der Blue Connect App vom Hersteller verbinden, um sie inkl. Redox zu kalibrieren. Da musste ich sie erstmal vom ESP32 wegholen, um mich mit der Hersteller App über BLE verbinden zu können.
Servus, da hat sich ja einiges getan, nachdem ich den Beitrag vor Jahren erstellt habe.
ich bin leider nicht so tief in der Materie wie Ihr Jungs, deswegen verzeiht meine wahrscheinlich dummen Fragen …
.
Ich habe über HACS BlueConnect installiert. Wenn ich jetzt in HA die Blue Connect Go Integration installieren will findet er diese, findet aber meinen Blue Connect nicht.
Wie integriere ich diesen nun? Momentan habe ich ihn per Extender verbunden.
Benötige ich zwingend einen ESP32 Bluetooth Stick und bekommt man diesen als Laie konfiguriert?
Welche Kaufempfehlung gibt es für einen Stick?
Danke für Eure Hilfe.
MFG
Wie und ob das mit einem Extender funktioniert, kann ich dir nicht sagen. Guck mal weiter oben, da gab es auch einen User mit Extender, wo die Verbindung nicht geklappt hat.
Ich benutze seit einigen Monaten nur einen ESP32 pro Boje und sonst nix. Der ESP benötigt natürlich eine Stromversorgung und sollte sich so nah wie möglich an der Messboje befinden. WLAN sollte dort auch sein, sonst kann HA die Daten nicht vom ESP abholen.
Für mich hat sich in den letzten Wochen/Monaten herausgestellt, dass diese Kombination ESP32/Messboje sehr zuverlässig läuft, wenn der Empfang passt, aber bei mir war ein Akku zu schnell leer.
Claude hat den Code für den ESP dahingehend optimiert. Zusätzlich kann ich nun auch den Ladezustand vom Akku auslesen. Mal gucken, wie sich dieser Code in der nächsten Zeit so schlägt. ![]()
Zum Code hat Claude noch eine kurze Zusammenfassung erstellt.
Zusammenfassung
ESPHome BLE-Proxy für die Blue Connect Go – akkuschonend, mit Batterieanzeige
Dieser ESPHome-Code liest eine Blueriiot Blue Connect Go über einen ESP32 aus und stellt Temperatur, pH, ORP/Redox und die Batteriespannung in Home Assistant bereit.
Der Schwerpunkt liegt auf Akkuschonung: Der ESP32 hält keine Dauerverbindung zur Boje, sondern verbindet sich nur kurz für eine einzelne Messung und trennt sofort danach wieder (auto_connect: false, Trennung direkt nach Empfang der Daten). Eine Messung wird über einen Button ausgelöst – entweder manuell oder per HA-Automation, sodass man die Messhäufigkeit frei steuern kann.
Ein paar Punkte, die in der Praxis wichtig waren und im Code kommentiert sind:
- Timing beim Verbindungsaufbau: Nach dem Connect braucht es einen kurzen Vorlauf, bis Service Discovery und Notify-Abo fertig sind, bevor der Messbefehl rausgeht. Schickt man ihn zu früh, misst die Boje zwar, aber die Antwort geht verloren.
- Scan-Neustart nach jeder Trennung, auch bei fehlgeschlagenem Connect – sonst bleibt der RSSI-Sensor irgendwann stehen.
- Batterie ohne Mehraufwand: Die Batteriespannung steckt bereits in jeder Messantwort (Bytes 9–10). Sie wird also ohne zusätzlichen BLE-Connect mitgelesen und kostet keinen extra Akku. Die Dekodierung stammt aus der HACS-Integration adamantivm/BlueConnect. Die Prozentanzeige ist eine lineare Näherung (3,40 V = 0 %, 3,64 V = 100 %) – der Spannungswert selbst ist der verlässliche.
Vor dem Flashen müssen nur drei Dinge angepasst werden: die MAC-Adresse der eigenen Boje, die WLAN-Einstellungen und die secrets.yaml. Alles ist im Code kommentiert.
# ==============================================================================
# ESPHome BLE-Proxy für Blueriiot "Blue Connect Go" Pool-Boje
# ==============================================================================
# Liest Temperatur, pH, ORP/Redox UND Batteriespannung der Boje aus.
#
# Funktionsprinzip:
# - Der ESP32 hält KEINE Dauerverbindung (auto_connect: false), sondern
# verbindet sich nur kurz zur Messung und trennt danach sofort wieder.
# Das schont den Akku der Boje erheblich.
# - Eine Messung wird über den Button "Messung auslösen" gestartet
# (oder via HA-Automation, die diesen Button drückt).
# - Die Boje liefert alle Werte in EINEM Notify-Frame. Daraus werden
# Temperatur, pH, ORP und Batteriespannung dekodiert.
#
# Batterie-Dekodierung (Bytes 9-10, mV, little-endian) basiert auf der
# HACS-Integration adamantivm/BlueConnect (parser.py). Das Auslesen kostet
# KEINEN zusätzlichen BLE-Connect – die Daten kommen ohnehin mit.
#
# ──────────────────────────────────────────────────────────────────────────
# VOR DEM FLASHEN ANPASSEN:
# 1. MAC-Adresse der eigenen Boje eintragen (unten bei ble_client + ble_rssi)
# → MAC findet man z.B. mit der nRF Connect App oder im ESPHome-Log,
# wenn der Scanner läuft.
# 2. WLAN: Entweder feste IP setzen (manual_ip-Block) oder den ganzen
# manual_ip-Block löschen für DHCP.
# 3. secrets.yaml mit den referenzierten !secret-Werten füllen.
# ──────────────────────────────────────────────────────────────────────────
substitutions:
device_name: pool-proxy
friendly_name: "Pool Proxy"
esphome:
name: ${device_name}
friendly_name: ${friendly_name}
esp32:
board: esp32dev
framework:
type: esp-idf
logger:
level: INFO
api:
encryption:
key: !secret pool_proxy_api_key
ota:
- platform: esphome
password: !secret ota_password
# Webserver für einfache Diagnose im Browser
web_server:
port: 80
version: 2
auth:
username: admin
password: !secret ota_password
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
power_save_mode: none
# Optional: feste IP. Für DHCP diesen manual_ip-Block einfach löschen.
manual_ip:
static_ip: 192.168.0.100
gateway: 192.168.0.1
subnet: 255.255.255.0
dns1: 192.168.0.1
captive_portal:
# Scanner bleibt durchgehend aktiv (continuous = Default true), damit der
# BLE-RSSI-Sensor weiter funktioniert. Während des Verbindungsaufbaus wird
# der Scan kurz gestoppt (siehe Button) und über on_disconnect IMMER wieder
# gestartet – auch wenn der Connect fehlschlägt.
esp32_ble_tracker:
scan_parameters:
interval: 320ms
window: 30ms
active: true
# ──────────────────────────────────────────────────────────────────────────
# auto_connect: false – Boje wird nur für die Messung kurz verbunden und
# danach sofort getrennt → Akku-Schonung. on_disconnect startet den Scan
# zuverlässig wieder (auch bei fehlgeschlagenem Connect).
# ──────────────────────────────────────────────────────────────────────────
ble_client:
- mac_address: "XX:XX:XX:XX:XX:XX" # <-- MAC der eigenen Boje eintragen
id: ble_client_boje
auto_connect: false
on_connect:
then:
# 6 s Vorlauf: Service Discovery (~2 s) + Notify-Abo (CCCD) müssen
# FERTIG sein, bevor der Messbefehl rausgeht. Das delay gibt zudem die
# Kontrolle an den Scheduler zurück (Pflicht-Workaround für ble_write
# aus on_connect, ESPHome-Issue #5870). Schreibt man zu früh, misst die
# Boje zwar, aber der ESP ist noch nicht für Notifications angemeldet
# → die Antwort geht verloren.
- logger.log:
level: INFO
format: "Boje verbunden – warte auf Notify-Abo, dann Messbefehl"
- delay: 6s
- ble_client.ble_write:
id: ble_client_boje
service_uuid: 'F3300001-F0A2-9B06-0C59-1BC4763B5C00'
characteristic_uuid: 'F3300002-F0A2-9B06-0C59-1BC4763B5C00'
value: [0x01]
on_disconnect:
then:
- logger.log:
level: INFO
format: "Boje getrennt – Scan wird neu gestartet"
- esp32_ble_tracker.start_scan:
button:
# ────────────────────────────────────────────────────────────────────────
# "Messung auslösen": Scan stoppen → verbinden. Der Messbefehl wird
# automatisch aus ble_client.on_connect geschrieben (richtiges Timing).
# Das 30-s-delay ist nur ein SICHERHEITSFENSTER: kommt eine Notify, trennt
# bereits on_notify sofort; bleibt sie aus, trennt spätestens dieses delay.
# ────────────────────────────────────────────────────────────────────────
- platform: template
name: "${friendly_name} Messung auslösen"
id: btn_messung
on_press:
then:
- logger.log:
level: INFO
format: "Boje: Messung angefordert – verbinde"
- esp32_ble_tracker.stop_scan:
- ble_client.connect: ble_client_boje
- delay: 30s
- ble_client.disconnect: ble_client_boje
- 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_temperature
unit_of_measurement: "°C"
state_class: measurement
device_class: temperature
- platform: template
name: "${friendly_name} pH"
id: sensor_ph
unit_of_measurement: "pH"
state_class: measurement
- platform: template
name: "${friendly_name} ORP"
id: sensor_orp
unit_of_measurement: "mV"
state_class: measurement
# ── Batterie der Boje (aus derselben Messantwort, Bytes 9-10) ─────────────
- platform: template
name: "${friendly_name} Boje Batterie Spannung"
id: sensor_batt_v
unit_of_measurement: "V"
accuracy_decimals: 3
state_class: measurement
device_class: voltage
entity_category: diagnostic
# Prozent-Schätzung: 3,400 V = 0 %, 3,640 V = 100 % (lineare Näherung,
# Community-Erfahrungswerte – kein offizieller Herstellerwert. Die
# Spannung oben ist der verlässliche Wert.)
- platform: template
name: "${friendly_name} Boje Batterie"
id: sensor_batt_pct
unit_of_measurement: "%"
accuracy_decimals: 0
state_class: measurement
device_class: battery
entity_category: diagnostic
- 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: "XX:XX:XX:XX:XX:XX" # <-- selbe MAC wie oben
name: "${friendly_name} Boje BLE Signal"
text_sensor:
- platform: wifi_info
ip_address:
name: "${friendly_name} IP"
entity_category: diagnostic
- platform: ble_client
id: reading_data
name: "${friendly_name} raw data"
internal: true
ble_client_id: ble_client_boje
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_LOGI("raw_hex", "Rohdaten der Boje: %s", rawhex.c_str());
// Temperatur (Bytes 1-2, /100)
float temperature = (float)((int16_t)(x[2]<< 8) + x[1])/100;
id(sensor_temperature).publish_state(temperature);
// pH (Bytes 3-4): (2048 - raw) / 232 + 7
float raw_ph = (float)( (int16_t) (x[4]<< 8) + x[3]);
float ph = (float)( (int16_t) (2048 - raw_ph)) / 232 + 7;
id(sensor_ph).publish_state(ph);
// ORP / Redox (Bytes 5-6, /4)
float orp = (float)( (int16_t) (x[6]<< 8) + x[5]);
id(sensor_orp).publish_state(orp/4);
// Batteriespannung (Bytes 9-10, mV, little-endian).
// Längen-Schutz: Frame muss mindestens 11 Bytes haben.
if (x.size() >= 11) {
float batt_mv = (float)((uint16_t)(x[10] << 8) + x[9]);
id(sensor_batt_v).publish_state(batt_mv / 1000.0);
// Linear: 3400 mV = 0 %, 3640 mV = 100 % (geclammt)
float pct = (batt_mv - 3400.0) / (3640.0 - 3400.0) * 100.0;
if (pct < 0) pct = 0;
if (pct > 100) pct = 100;
id(sensor_batt_pct).publish_state(pct);
}
# Daten erhalten → sofort trennen (kürzeste Verbindung, schont Akku).
- ble_client.disconnect: ble_client_boje
So sehen die Werte in HA aus.
Für mich habe ich dann noch eine Dosierempfehlung bauen lassen, die aus diesen Werten und den händisch mit dem Poollab gemessenen Werten eine Dosierempfehlung erstellt, den Sondendrift kontrolliert und die Anzeige in HA dahingehend korrigiert.
Das wird dann aber sehr speziell und es sind dazu diverse Helfer und Automationen notwendig, um diese Dosierempfehlung nutzen zu können.
Möglich ist das aber… ![]()
Karten der Dosierempfehlung
Den errechneten Sondendrift lasse ich mir bei den Messwerten vom Poollab anzeigen. Die zwei Karten vorher zeigen den Live-Messwert der Boje an, nur dass dort der Sondendrift schon mit eingerechnet wurde.
So “kalibriere” ich ständig den angezeigten Wert der pH-Sonde durch gelegentliche händische Messung.
Da würde ich eher der Elektrode vertrauen!
Der PoolLab hat durchaus Messtoleranzen von +/-0,2 bei pH.
Zusätzlich Messfehler oder älter Messtabletten
@Kai2 was sagt die Anzeige Sonden-Drift bei dir aus?
Das ist der Unterschied zwischen pH Sonde und ph mit Poollab gemessen. Der angezeigte Messwert der Sonde wird dann um diesen korrigiert und angezeigt. So spare ich mir das Kalibrieren der Sonde und bekomme immer den genauen Wert angezeigt, auch wenn die Sonde mit der Zeit driftet.
Der Drift sagt auf deinen Bildern 0,1. PoolLab sagt 7,6 Sonde 6,94?
Wie bekommst du deine PoolLab Werte in HA?
Der Drift wird nur im Moment der Poollabmessung berechnet. Die Poollabmessung war damals um 12:14 Uhr. Der Drift wurde berechnet und dann habe ich pH Minus dosiert, denn gezeigte Sondenmesseung stammt laut Datei von 15:40 Uhr.


















