Für unseren Aufstellpool verwende ich eine Bali-400 Sandfilteranlage mit Aqua Plus 11 Pumpe. Die ist für einen 366 × 76 cm Pool natürlich deutlich überdimensioniert, aber der Plan ist, den Pool mit zunehmendem Alter der Kinder irgendwann gegen etwas mit mehr Beckenvolumen auszutauschen. Um die Pumpe bis dahin trotzdem sparsam, leise und zur Poolgröße passend zu betreiben, hängt sie an einem AquaForte iSaver-X. Dazu kommt eine Poolex MagFi 5 als Inverter-Wärmepumpe. Geschaltet und gemessen wird bei mir über Shelly Outdoor Steckdosen, damit Home Assistant den Verbrauch kennt und die Anlage nicht einfach nur stur nach Uhrzeit läuft.
Der iSaver-X kann von Haus aus schon einiges: Drehzahlprofile, Zeitschaltuhr und natürlich die eigentliche Frequenzregelung der Pumpe. Was ihm fehlt, ist die smarte Anbindung. Keine Home-Assistant-Integration, kein MQTT, keine lokale API. Genau das war für mich schade, weil der iSaver-X eigentlich das perfekte Bindeglied zwischen Poolpumpe, Wärmepumpe, PV-Überschuss und Nachtbetrieb ist.

Kurzfassung: Ich habe zuerst den lauten Lüfter gegen zwei Noctua NF-A4x20 getauscht und anschließend einen ESP32 D1 Mini mit einem TTL-zu-RS485-Modul intern verbaut. Der ESP32 liest und setzt die Drehzahl über die interne RS485-Schnittstelle des iSaver-X. Home Assistant bekommt dadurch Entitäten für Status, Drehzahl, Fehler und Buttons für die wichtigsten Betriebsmodi.
Ausgangslage und Ziel
Der eigentliche Auslöser war nicht „ich will die Pumpe vom Smartphone einschalten“. Das wäre nett, aber dafür hätte sich der Aufwand kaum gelohnt. Mir ging es darum, dass Home Assistant selbst entscheidet, welche Drehzahl gerade sinnvoll ist. Nachts leise, tagsüber normal, bei Heizbedarf mit ausreichendem Durchfluss und bei PV-Überschuss etwas höher.
| Komponente | Bei mir im Einsatz | Rolle im Setup |
|---|---|---|
| Pool | Intex Frame 366 × 76 cm | aktuelles Becken, bewusst mit Reserven geplant |
| Filteranlage | OKU/Bali 400 | Sandfilteranlage mit Reserven für später |
| Pumpe | Aqua Plus 11 | wird per iSaver-X heruntergeregelt |
| Frequenzumrichter | AquaForte iSaver-X | Drehzahlregelung der Filterpumpe |
| Wärmepumpe | Poolex MagFi 5 | Temperatur halten, möglichst leise und effizient |
| Messung/Schaltung | Shelly Outdoor | Verbrauch messen und Geräte schalten |
| Smart Home | Home Assistant | Automationen, Modi, Dashboards und Benachrichtigungen |
Die wichtigsten Modi liegen bei mir grob zwischen 1300 und 2900 rpm. 1300 rpm reicht für normale, leise Filterung. Für die Wärmepumpe nutze ich mehr Durchfluss. Wenn viel PV-Überschuss vorhanden ist oder nach einem langen Badetag mit Sonnencreme mehr Umwälzung sinnvoll ist, darf die Pumpe entsprechend höher laufen.
| Modus | Drehzahl | Gedanke dahinter |
|---|---|---|
| Eco | 1300 rpm | normale Filterung, leise und sparsam |
| Nacht | 1500 rpm | weiterhin Umwälzung und brauchbare Temperaturmessung |
| Wärmepumpe | 1950 rpm | zuverlässiger Durchfluss für die Poolex MagFi 5 |
| PV | 2400 rpm | mehr Durchfluss, wenn ohnehin Überschuss vorhanden ist |
| Max | 2900 rpm | Rückspülen, Wartung oder manuelles Boost-Szenario |
Sicherheit vorweg
Der Lüfterumbau ist vergleichsweise harmlos, weil der Lüfterhalter außen am Kühlkörper sitzt und die Elektronik dafür nicht geöffnet werden muss. Beim ESP32-Umbau sieht das anders aus. Der iSaver-X ist ein Frequenzumrichter und im Gerät liegen gefährliche Spannungen an. Gerät spannungsfrei machen, warten, messen und im Zweifel jemanden fragen, der solche Arbeiten fachlich bewerten kann. Garantie oder Gewährleistung kann durch den Umbau natürlich ebenfalls ein Thema sein.
Teil 1: Den iSaver-X leiser machen
Bevor ich den iSaver-X smart gemacht habe, musste erstmal der Lüfter dran glauben. Der originale 40-mm-Lüfter war bei mir das lauteste Bauteil der ganzen Pooltechnik. Nicht die Aqua Plus 11, nicht die Wärmepumpe, sondern dieser kleine hochfrequente Lüfter auf der Rückseite.
Die Halterung ist bei meinem Gerät für zwei 40-mm-Lüfter vorbereitet, ab Werk war aber nur einer bestückt. Ich habe deshalb direkt zwei Noctua NF-A4x20 genommen. Die Stecker habe ich abgeschnitten und die beiden Lüfter parallel mit der vorhandenen 12-V-Versorgung verlötet. Widerstände oder Low-Noise-Adapter habe ich nicht verwendet, weil der iSaver-X die Lüfter temperaturabhängig schaltet.

| Benötigt | Hinweis |
|---|---|
| 1–2× Noctua NF-A4x20 | bei mir zwei Stück, 12 V vorher am eigenen Gerät prüfen |
| Schrumpfschlauch | für die verlöteten Adern |
| Lötkolben und Lötzinn | ein Set mit Multimeter und Schraubendrehern ist praktisch |
| Seitenschneider / Cutter | zum Kürzen und Abisolieren |
| Multimeter | zum Prüfen der Lüfterspannung |
| Feuerzeug oder Heißluft | für den Schrumpfschlauch |
- Modell prüfen und nachsehen, ob die Aufnahme für zwei 40-mm-Lüfter vorbereitet ist.
- Lüfterkabel vorsichtig freilegen und prüfen, ob dort wirklich 12 V anliegen. Dafür muss das Gerät kurz bestromt werden.
- Gerät wieder stromlos machen und warten. Danach den Originallüfter möglichst nah am Lüfter trennen, damit vom originalen Kabel möglichst viel übrig bleibt.
- Noctua-Stecker abschneiden, Adern freilegen, verzinnen, Schrumpfschlauch vorher nicht vergessen und beide Lüfter parallel anlöten.
- Lötstellen isolieren, Lüfter in die Halterung setzen und die Halterung wieder am Kühlkörper verschrauben.
Einen direkten Test bekommt man nicht immer sofort, weil die Lüfter temperaturgesteuert laufen. Im normalen Gartenbetrieb sind die Noctuas bei mir praktisch nicht mehr zu hören. Die Wärme der Kühlrippen wird trotzdem ordentlich abtransportiert.

Teil 2: Den iSaver-X per ESP32 und RS485 smart machen
Der zweite Teil ist etwas aufwändiger. Grundlage ist ein öffentliches GitHub-Projekt, das die RS485-Kommunikation für iSaver/iSaver-X dokumentiert hat. Das ursprüngliche Beispiel nutzt einen ESP8266. Ich habe es für meinen ESP32 D1 Mini angepasst, andere UART-Pins verwendet, das Polling auf 3 Sekunden gesetzt und die Konfiguration für mein Home-Assistant-Setup erweitert.
Wichtig: Das ist kein normales Modbus RTU, auch wenn RS485 verwendet wird. Der iSaver-X spricht ein proprietäres Protokoll. Standard-Modbus-Integrationen helfen hier also nicht direkt weiter.
| Bauteil | Verwendung | Hinweis |
|---|---|---|
| ESP32 D1 Mini | ESPHome-Gerät | USB-C/Micro-USB je nach Boardvariante |
| TTL-zu-RS485-Modul | UART ↔ RS485 | bei mir ein Modul mit Auto-Direction |
| JST-PH 2,0 mm, 9 Pin | RS485-Anschluss am iSaver-X | Pinbelegung zählen, nicht blind Kabelfarben vertrauen |
| JST-XH 2,54 mm, 4 Pin | 5-V-Versorgung | freier Anschluss auf der Platine, vorher messen |
| Dupont-Kabel oder Litze | Verdrahtung | ich habe teilweise mit Dupont gearbeitet |
| Lochrasterplatine | Träger im Gehäuse | optional, macht den Einbau sauberer |
| Heißklebepistole | Befestigung | ESP und RS485-Modul bei mir unten an der Innenseite der Front |
ESP32 am Schreibtisch vorbereiten
Ich würde den ESP32 immer zuerst am Schreibtisch flashen und OTA testen. Wenn das Board einmal im iSaver-X sitzt, möchte man es nicht wegen eines simplen WLAN- oder OTA-Problems wieder ausbauen.
$ ls /dev/cu.*
/dev/cu.Bluetooth-Incoming-Port /dev/cu.usbserial-0001
Unter macOS wurde mein Board als CP2102 USB to UART Bridge erkannt. Danach habe ich den Chip mit esptool geprüft. Die MAC-Adresse ist hier anonymisiert:
$ esptool --chip esp32 --port /dev/cu.usbserial-0001 chip-id
esptool v5.3.0
Connected to ESP32 on /dev/cu.usbserial-0001:
Chip type: ESP32-D0WD-V3 (revision v3.1)
Features: Wi-Fi, BT, Dual Core + LP Core, 240MHz
MAC: d4:e9:f4:xx:xx:xx
Danach kann die ESPHome-YAML kompiliert und zunächst per USB übertragen werden. Sobald WLAN und API funktionieren, lässt sich ein OTA-Test per IP machen. Das ist besonders praktisch, wenn mDNS im Netzwerk oder bei UniFi mal nicht sauber auflöst.
pip install esphome
esphome run esp32.yaml
esphome run esp32.yaml --device 192.168.x.xxx
esphome logs esp32.yaml --device 192.168.x.xxx
INFO Connecting to 192.168.x.xxx port 3232...
INFO Connected to 192.168.x.xxx
INFO Uploading firmware.bin (815808 bytes)
Uploading: [============================================================] 100% Done...
INFO OTA successful
INFO Successfully uploaded program.
Pinbelegung am iSaver-X
Der 9-polige JST-PH-Stecker ist für die RS485-Kommunikation zuständig, liefert aber keine 5-V-Versorgung für den ESP32. Für die reine RS485-Anbindung werden nur A, B und GND benötigt.
| Pin | Funktion | Für diesen Umbau |
|---|---|---|
| 1 | GND | optional / Masse |
| 2 | V/I Analogeingang | nicht als ESP-Versorgung nutzen |
| 3 | RS485 B | ja |
| 4 | RS485 A | ja |
| 5 | GND | ja, gemeinsame Masse |
| 6 | IN1 | nicht genutzt |
| 7 | IN2 | nicht genutzt |
| 8 | IN3 | nicht genutzt |
| 9 | IN4 | nicht genutzt |
Bei meinem vorbereiteten Kabel habe ich die Belegung so umgesetzt: Pin 3 auf B-, Pin 4 auf A+ und Pin 5 auf GND. Die Farben können bei anderen Kabeln abweichen. Also lieber einmal mehr nach Pin-Nummer zählen als später lange nach einem „No Response“ suchen.
iSaver-X JST-PH-9 -> RS485-Modul
Pin 3 / B -> B-
Pin 4 / A -> A+
Pin 5 / GND -> GND
ESP32 D1 Mini -> RS485-Modul
GPIO17 -> TXD
GPIO16 -> RXD
GND -> GND
5V/VCC -> VCC

Stromversorgung aus dem Gerät
Der 9-polige Stecker ist nicht die Lösung für die Versorgung. Auf der Hauptplatine gibt es bei meinem Gerät aber einen freien weißen JST-XH-4-Pin-Anschluss, an dem 5 V anliegen. Darüber versorge ich den ESP32 und das RS485-Modul. Vorher natürlich messen, denn Platinenrevisionen können sich ändern.
Ich habe mir dafür ein kleines Y-Kabel gebaut: 5 V und GND vom JST-XH-Stecker gehen auf den ESP32 und auf das RS485-Modul. Wichtig ist am Ende nur, dass GND sauber gemeinsam geführt wird und nichts lose im Gehäuse herumliegt.

Einbau im iSaver-X
Nach dem Trennen vom Strom habe ich das Gerät geöffnet und mir zuerst Fotos vom Originalzustand gemacht. Das spart Nerven, wenn man zwischendurch Kabel löst, um die Front weiter öffnen zu können. Bei mir habe ich die Erdung zur Front, die beiden gesteckten Stromkabel und einen blauen JST-XH-Stecker zur Front gelöst, um besser arbeiten zu können.
Den ESP32 und das RS485-Modul habe ich auf einer kleinen Lochrasterplatine vorbereitet und unten an der Innenseite der Front mit Heißkleber fixiert. Das ist nicht die edelste Lösung, aber für diesen kleinen, leichten Aufbau ausreichend. Wichtig ist, dass keine Kontakte das Gehäuse berühren und sich durch Vibrationen nichts lösen kann.
Der ESP sitzt damit im Metallgehäuse. Bei mir reicht das WLAN trotzdem, weil der Access Point etwa 10 m entfernt ist und der ESP in Richtung Kunststofffront sitzt. Wenn der Empfang schlecht ist, würde ich entweder einen ESP mit externer Antenne verwenden oder das Modul außerhalb des iSaver-X in ein separates Gehäuse setzen.

Der erste RS485-Test
Beim ersten Test war die TX-LED am RS485-Modul sofort aktiv. Das bedeutet erstmal nur: Der ESP32 sendet. Wenn RX nicht blinkt, antwortet der iSaver-X nicht. Bei mir waren anfangs A und B vertauscht. Nach dem Tausch blinkten TX und RX abwechselnd und im ESPHome-Log kam endlich „Online“.
Gekürzte und anonymisierte Beispielausgabe aus den ESPHome-Logs:
[09:23:49.674][S][text_sensor]: 'iSaver Modbus Status' >> 'No Response'
[13:14:31.534][S][text_sensor]: 'iSaver Modbus Status' >> 'Wrong Data Header'
[13:14:32.695][S][text_sensor]: 'iSaver Modbus Status' >> 'No Response'
[13:12:32.609][S][text_sensor]: 'iSaver Modbus Status' >> 'Online'
[13:12:32.610][S][text_sensor]: 'iSaver Error' >> 'No Error'
[13:12:46.626][S][sensor]: 'iSaver Current RPM' >> 1650 RPM
[13:13:40.628][S][sensor]: 'iSaver Current RPM' >> 1300 RPM
[13:14:33.614][S][sensor]: 'iSaver Current RPM' >> 2900 RPM
Mein Merksatz danach: TX alleine heißt nur, dass der ESP sendet. TX und RX im Wechsel heißt, dass auf dem Bus wenigstens etwas zurückkommt. Wenn der Status trotzdem nicht passt, sind A/B, GND und Timing die ersten Kandidaten.

Finale ESPHome-YAML
Die produktive YAML ist unten vollständig enthalten und zusätzlich als Datei esp32.yaml im Downloadpaket. WLAN, API-Key, OTA-Passwort und Fallback-Hotspot sind über !secret anonymisiert. Vor dem Flashen müssen diese Werte in deiner secrets.yaml vorhanden sein.
Wichtige Änderungen gegenüber dem ursprünglichen ESP8266-Beispiel: ESP32 statt ESP8266, GPIO17/GPIO16 für UART, 3-Sekunden-Polling, weniger Log-Traffic, Diagnose-Sensoren und eine einfache Soft-Sperre, damit Polling und Schreibbefehl nicht gleichzeitig auf UART/RS485 zugreifen.
uart:
id: modbus_uart
tx_pin: GPIO17
rx_pin: GPIO16
baud_rate: 1200
data_bits: 8
stop_bits: 1
parity: NONE
rx_buffer_size: 256
interval:
- interval: 3s
then:
- lambda: |-
if (id(rs485_busy)) {
return;
}
id(rs485_busy) = true;
uint8_t packet[8] = {0xAA, 0xC3, 0x07, 0xD1, 0x00, 0x00, 0x0D, 0x4D};
Use-Cases in Home Assistant
Der Umbau ist technisch fertig, sobald Home Assistant die Drehzahl lesen und setzen kann. Sinnvoll wird es aber erst durch die Automationen. Nachts geht der Pool bei mir in den Eco-Modus und wechselt nur dann in einen Nacht-Wärmepumpenmodus, wenn die Wassertemperatur unter einen bestimmten Wert fällt und der Strompreis nicht zu hoch ist.
Die Wärmepumpe begrenze ich indirekt über die Solltemperatur. Wenn die Leistungsaufnahme zu hoch wird, passt Home Assistant den Offset an. So soll die Poolex MagFi 5 nachts nicht unnötig hochdrehen. In meinem Setup kann ich sie damit z.B. in einem Bereich halten, in dem sie deutlich leiser läuft.
Tagsüber läuft die Pumpe in einem normalen Tagesmodus. Zusätzlich habe ich Buttons beziehungsweise Modi für Poolpflege und Boost. Das nutze ich nach Stoßchlorung, Flockung oder wenn die Kinder den ganzen Tag mit Sonnencreme im Pool waren. Nach einer definierten Dauer fällt die Anlage wieder zurück. Genau das ist der eigentliche Mehrwert: Ich muss nicht mehr ständig daran denken.
Troubleshooting
| Symptom | Wahrscheinliche Ursache | Was ich prüfen würde |
|---|---|---|
| TX blinkt, RX nicht | ESP sendet, iSaver-X antwortet nicht | A/B tauschen, GND prüfen, iSaver-X eingeschaltet? |
| No Response | keine gültige Antwort auf dem Bus | RS485-Leitungen, gemeinsame Masse und Versorgung prüfen |
| Wrong Data Header | Antwort unvollständig oder Timingproblem | Polling reduzieren, Schreib- und Lesebefehle entkoppeln |
| OTA klappt nicht | mDNS/UniFi/VLAN-Auflösung | esphome run esp32.yaml --device 192.168.x.xxx nutzen |
| UniFi zeigt alten Namen | Cache oder alter Device-Tracker | Client-Alias setzen oder alte Einträge bereinigen |
| WLAN schwach | ESP sitzt im Gehäuse ungünstig | ESP näher an die Kunststofffront, sonst externe Antenne |
Gerade A/B ist ein Klassiker. Wenn man sich eigentlich sicher ist, lohnt sich das Tauschen trotzdem. Bei mir war das genau der Punkt, der aus „der ESP sendet, aber nichts passiert“ ein funktionierendes Setup gemacht hat.
Kosten und Aufwand
| Position | Grobe Kosten |
|---|---|
| ESP32 D1 Mini | ca. 5–8 € |
| TTL-zu-RS485-Modul | ca. 2–5 € |
| JST-Stecker, Litze, Schrumpfschlauch | einige Euro |
| Lochrasterplatine / Kleinteile | einige Euro |
| Noctua NF-A4x20 | je nach Anzahl ca. 15 € pro Stück |
Der Smart-Umbau selbst ist preislich überschaubar. Der Lüfterumbau kostet durch die Noctuas mehr, bringt aber sofort einen spürbaren Gewinn, weil der hochfrequente Originalton weg ist.
Fazit
Ich würde den Umbau wieder machen. Nicht, weil ich jetzt per Smartphone die Drehzahl der Poolpumpe ändern kann. Das mache ich praktisch nie. Der eigentliche Vorteil ist, dass Home Assistant die Entscheidung übernimmt und der iSaver-X endlich in die restliche Logik passt.
Der iSaver-X ist dadurch für mich das Gerät geworden, das ich mir ursprünglich gewünscht hätte: ein leiser Frequenzumrichter, der sich lokal steuern lässt und automatisch auf Nachtbetrieb, Wärmepumpe, PV-Überschuss und Poolpflege reagiert.
Die meiste Zeit ging nicht für das Löten drauf, sondern für Kleinigkeiten: A/B vertauscht, OTA-Test, „No Response“, „Wrong Data Header“ und die Frage, wo man sauber 5 V abgreift. Genau deshalb habe ich diese Punkte hier aufgeschrieben. Vielleicht spart es dem nächsten ein paar Stunden Fehlersuche.
FAQ
Kann ich den Lüfterumbau unabhängig vom ESP32-Umbau machen?
Ja. Der Lüfterhalter sitzt außen am Kühlkörper. Für den reinen Lüftertausch muss das eigentliche Elektronikgehäuse nicht geöffnet werden.
Brauche ich zwingend einen ESP32?
Nein. Das ursprüngliche Projekt nutzt einen ESP8266. Ich habe mich für einen ESP32 D1 Mini entschieden, weil ich diese Boards ohnehin gerne für solche IoT-Aufgaben verwende und genug Reserven vorhanden sind.
Kommt die Versorgung aus dem 9-poligen JST-PH-Stecker?
Nein. Der 9-polige Stecker liefert bei meinem Gerät RS485 und GND, aber keine 5 V für den ESP32. Die 5 V habe ich über einen separaten freien JST-XH-Anschluss auf der Platine abgegriffen.
Was mache ich, wenn nur TX blinkt?
Dann sendet der ESP32, bekommt aber keine sinnvolle Antwort. Als erstes würde ich A/B tauschen, GND prüfen und kontrollieren, ob der iSaver-X überhaupt eingeschaltet beziehungsweise betriebsbereit ist.
Ist das normales Modbus RTU?
Nein. Es läuft elektrisch über RS485, aber das Protokoll ist proprietär. Deshalb wird in der YAML direkt per UART mit Byte-Frames gearbeitet.
Quellen und Dank
Die Grundlage für die RS485-Kommunikation stammt aus dem GitHub-Projekt backuprestore/isaver-isaverx-RS485-modbus. Ich habe die Idee auf meinen ESP32-/ESPHome-Aufbau übertragen, angepasst und für mein Home-Assistant-Setup erweitert. Danke an den Contributor für die Vorarbeit.
Anhang: vollständige esp32.yaml
Für WordPress würde ich diese Datei zusätzlich als Download anbieten. Hier ist sie zur Dokumentation vollständig enthalten:
substitutions:
device_name: isaver
friendly_name: "iSaver X"
# Pinbelegung ESP32 D1 Mini / ESP32 DevKit:
# GPIO17 -> TXD am RS485-Modul
# GPIO16 -> RXD am RS485-Modul
#
# RS485-Modul -> iSaver X JST-PH-9:
# A -> Pin 4 / A
# B -> Pin 3 / B
# GND -> Pin 5 / GND
#
# Hinweis:
# Der 9-polige JST-Stecker liefert keine Versorgungsspannung.
# ESP32 und RS485-Modul müssen separat mit 5V/GND versorgt werden.
esphome:
name: ${device_name}
friendly_name: ${friendly_name}
on_boot:
priority: -100
then:
- lambda: |-
// Nach einem Neustart nur den zuletzt gelesenen Zustand anzeigen.
// Es wird bewusst kein RPM-Befehl an den iSaver gesendet.
if (!isnan(id(pump_rpm_state).state) && id(pump_rpm_state).state > 0) {
id(pump_rpm).publish_state(id(pump_rpm_state).state);
}
esp32:
board: esp32dev
framework:
type: esp-idf
advanced:
minimum_chip_revision: "3.1"
sram1_as_iram: true
logger:
level: WARN
api:
encryption:
key: !secret isaver_api_key
ota:
- platform: esphome
password: !secret isaver_ota_password
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
power_save_mode: none
fast_connect: true
# Optional: feste Zieladresse für OTA, falls mDNS/UniFi zickt.
# use_address: 192.168.1.175
ap:
ssid: "iSaver Fallback"
password: !secret isaver_fallback_password
captive_portal:
globals:
# Einfache Soft-Sperre, damit Polling und Schreibbefehl nicht gleichzeitig
# auf UART/RS485 zugreifen. Das reduziert sporadische "Wrong Data Header"
# oder "No Response" direkt nach einem RPM-Wechsel.
- id: rs485_busy
type: bool
restore_value: no
initial_value: "false"
- id: rs485_last_rx_ms
type: uint32_t
restore_value: no
initial_value: "0"
- id: rs485_error_counter
type: uint32_t
restore_value: no
initial_value: "0"
uart:
id: modbus_uart
tx_pin: GPIO17
rx_pin: GPIO16
baud_rate: 1200
data_bits: 8
stop_bits: 1
parity: NONE
rx_buffer_size: 256
text_sensor:
- platform: template
name: "iSaver Modbus Status"
id: modbus_status
icon: "mdi:lan-connect"
- platform: template
name: "iSaver Error"
id: pump_error_text
icon: "mdi:alert-circle-outline"
- platform: version
name: "iSaver ESPHome Version"
hide_timestamp: true
sensor:
- platform: template
name: "iSaver Current RPM"
id: pump_rpm_state
unit_of_measurement: "RPM"
icon: "mdi:fan"
accuracy_decimals: 0
update_interval: never
- platform: template
name: "iSaver RS485 Error Counter"
id: rs485_error_counter_sensor
icon: "mdi:counter"
accuracy_decimals: 0
update_interval: 60s
lambda: |-
return (float) id(rs485_error_counter);
- platform: template
name: "iSaver Last Response"
id: rs485_last_response_age
unit_of_measurement: "s"
icon: "mdi:timer-outline"
accuracy_decimals: 0
update_interval: 10s
lambda: |-
if (id(rs485_last_rx_ms) == 0) {
return NAN;
}
return (float) ((millis() - id(rs485_last_rx_ms)) / 1000);
- platform: wifi_signal
name: "iSaver WiFi Signal"
id: wifi_signal_db
update_interval: 60s
- platform: uptime
name: "iSaver Uptime"
id: isaver_uptime
update_interval: 60s
number:
- platform: template
name: "iSaver RPM"
id: pump_rpm
icon: "mdi:speedometer"
min_value: 0
max_value: 2900
step: 50
optimistic: false
set_action:
then:
- lambda: |-
if (id(rs485_busy)) {
ESP_LOGW("isaver", "RS485 busy, skipping RPM command");
return;
}
id(rs485_busy) = true;
uint16_t rpm = (uint16_t)x;
// Beim iSaver bedeutet ein Wert unterhalb der Minimaldrehzahl "OFF".
if (rpm < 1200) {
rpm = 1;
}
// Wenn die gemessene Drehzahl schon dem Ziel entspricht, nicht unnötig schreiben.
if (!isnan(id(pump_rpm_state).state) &&
(uint16_t) id(pump_rpm_state).state == rpm) {
id(rs485_busy) = false;
return;
}
uint8_t rpm0 = (rpm >> 8) & 0xFF;
uint8_t rpm1 = rpm & 0xFF;
uint8_t data[6] = {0xAA, 0xD0, 0x0B, 0xB9, rpm0, rpm1};
uint16_t crc = 0xFFFF;
for (int i = 0; i < 6; i++) {
crc ^= data[i];
for (int j = 0; j < 8; j++) {
if (crc & 0x0001) {
crc >>= 1;
crc ^= 0xA001;
} else {
crc >>= 1;
}
}
}
uint8_t packet[8] = {
0xAA,
0xD0,
0x0B,
0xB9,
rpm0,
rpm1,
(uint8_t)(crc & 0xFF),
(uint8_t)((crc >> 8) & 0xFF)
};
auto uart = id(modbus_uart);
while (uart->available() > 0) {
uint8_t t;
uart->read_array(&t, 1);
}
uart->write_array(packet, 8);
uart->flush();
// Kurzer Abstand, damit das nächste Polling nicht direkt in die Antwort läuft.
delay(80);
id(rs485_busy) = false;
switch:
- platform: template
name: "iSaver Power"
id: pump_power
icon: "mdi:power"
optimistic: false
turn_on_action:
- number.set:
id: pump_rpm
value: 1800
turn_off_action:
- number.set:
id: pump_rpm
value: 0
button:
- platform: restart
name: "iSaver ESP Restart"
- platform: template
name: "iSaver ECO 1300"
icon: "mdi:leaf"
on_press:
- number.set:
id: pump_rpm
value: 1300
- platform: template
name: "iSaver Night 1500"
icon: "mdi:weather-night"
on_press:
- number.set:
id: pump_rpm
value: 1500
- platform: template
name: "iSaver WP 1950"
icon: "mdi:heat-pump"
on_press:
- number.set:
id: pump_rpm
value: 1950
- platform: template
name: "iSaver PV 2400"
icon: "mdi:solar-power-variant"
on_press:
- number.set:
id: pump_rpm
value: 2400
- platform: template
name: "iSaver Max 2900"
icon: "mdi:fan-speed-3"
on_press:
- number.set:
id: pump_rpm
value: 2900
interval:
- interval: 3s
then:
- lambda: |-
if (id(rs485_busy)) {
return;
}
id(rs485_busy) = true;
// Request Frame aus dem ursprünglichen iSaver/iSaverX-RS485-Projekt.
uint8_t packet[8] = {0xAA, 0xC3, 0x07, 0xD1, 0x00, 0x00, 0x0D, 0x4D};
auto uart = id(modbus_uart);
while (uart->available() > 0) {
uint8_t t;
uart->read_array(&t, 1);
}
uart->write_array(packet, 8);
uart->flush();
uint32_t start_time = millis();
while (uart->available() < 7 && (millis() - start_time) < 250) {
delay(10);
}
if (uart->available() >= 7) {
uint8_t response[7];
uart->read_array(response, 7);
if ((response[0] == 0xAA) && (response[1] == 0xC3)) {
id(rs485_last_rx_ms) = millis();
if (id(modbus_status).state != "Online") {
id(modbus_status).publish_state("Online");
}
uint16_t error = ((uint16_t)response[2] << 8) | (uint16_t)response[3];
if (error == 0) {
if (id(pump_error_text).state != "No Error") {
id(pump_error_text).publish_state("No Error");
}
} else {
char error_buffer[24];
snprintf(error_buffer, sizeof(error_buffer), "Error 0x%04X", error);
if (id(pump_error_text).state != error_buffer) {
id(pump_error_text).publish_state(error_buffer);
}
}
bool power = response[4] == 1;
if (power != id(pump_power).state) {
id(pump_power).publish_state(power);
}
float rpm = (float)(((uint16_t)response[5] << 8) | (uint16_t)response[6]);
if (isnan(id(pump_rpm_state).state) || rpm != id(pump_rpm_state).state) {
id(pump_rpm_state).publish_state(rpm);
id(pump_rpm).publish_state(rpm);
}
} else {
id(rs485_error_counter)++;
if (id(modbus_status).state != "Wrong Data Header") {
id(modbus_status).publish_state("Wrong Data Header");
}
}
} else {
id(rs485_error_counter)++;
if (id(modbus_status).state != "No Response") {
id(modbus_status).publish_state("No Response");
}
}
id(rs485_busy) = false;