Automation PH/EC für Hydroponisches System über ESPHome

Hallo liebe Community,

ich bin leider mit meinem Latein am Ende und drehe mich zur Zeit im Kreis.

Für meine NFT-Anlage (Hydroponischer Pflanzenanbau), möchte ich eine automatische Steuerung für Ph-Wert und EC-Wert basteln.

Meine Komponenten:

Hersteller: Atlas Scientific

Boards:

  • Whitebox T1 MkII
  • ESP32 ESP-WROOM-32
  • EZO-RTD
  • EZO-PH
  • EZO-EC

Sensoren (passend zu EZO Boards):

  • Temperatur
  • Ph-Wert
  • EC-Wert
  • Wasserstandsensor

Pumpen:
2 x EZO-PMP ( Peristaltic Pump) 1x für Ph-Down und 1x für Düngekonzentrat

Aufbau:

EZO-RTD port 1
EZO-PH port 2
EZO-EC port 3
EZO-PMP (Ph-Down) port 6
EZO-PMP (Dünger) port 7

Wasserstandsensor habe ich noch nicht integriert, da ich leider noch nicht weiß wie (gerne Hilfe hierbei).

ESP32 Board ist über SCL, SDA, 3,3V und GND am T1 Board verbunden.

Strom kommt über 5 V Netzteil über Micro USB an das T1 Board
Strom kommt über USB C an ESP 32
Strom kommt über 12 V Netzteil direkt an beide Pumpen (Pumpen haben einen separaten Strom Anschluss, da 12 V.

Leider schaffe ich es nicht die Yaml-Datei so zu schreiben, dass er nicht meckert und ich finde den Fehler einfach nicht.

esphome:
  name: esphome-nft
  friendly_name: ESPHome_NFT

# ESP32 Konfiguration
esp32:
  board: esp32dev
  framework:
    type: arduino

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

  ap:
    ssid: "Esphome-Nft Fallback"
    password: "fallbackpass"

captive_portal:

# Logging aktivieren
logger:

# Home Assistant API aktivieren
api:
  encryption:
    key: 

# OTA Konfiguration
ota:
  platform: esphome
  password:   # Dein OTA-Passwort

# I2C Konfiguration für Kommunikation mit Whitebox T1
i2c:
  id: bus_a
  sda: GPIO21
  scl: GPIO22
  scan: true

# Sensoren (pH, EC und Temperatur)
sensor:
  - platform: ezo
    address: 99
    name: "pH Wert"
    id: ph_wert
    update_interval: 60s

  - platform: ezo
    address: 100
    name: "EC Wert"
    id: ec_wert
    update_interval: 60s

  - platform: ezo
    address: 102
    name: "Wassertemperatur"
    id: wassertemp
    update_interval: 60s

# Steuerung der Pumpen (PH-Down und Dünger)
switch:
  - platform: ezo_pmp
    id: ph_down_pump
    name: "PH Down Pumpe"
    address: 111
    i2c_id: bus_a

  - platform: ezo_pmp
    id: duenger_pumpe
    name: "Dünger Pumpe"
    address: 112
    i2c_id: bus_a

# Automatische pH-Korrektur mit 30 Minuten Wartezeit
interval:
  - interval: 5min
    then:
      - lambda: |-
          static bool wartezeit = false;
          static unsigned long letzter_eingriff = 0;

          float ph = id(ph_wert).state;
          unsigned long jetzt = millis();

          if (!wartezeit && ph > 6.5) {
            ESP_LOGI("pH-Regelung", "pH ist %.2f – PH-Down wird aktiviert.", ph);
            id(ph_down_pump).turn_on();
            delay(1000);  // 1 Sekunde Pumpenlaufzeit (anpassbar)
            id(ph_down_pump).turn_off();
            letzter_eingriff = jetzt;
            wartezeit = true;
          }

          if (wartezeit && (jetzt - letzter_eingriff > 1800000)) { // 30 Minuten
            ESP_LOGI("pH-Regelung", "30 Minuten vergangen – Wartezeit beendet.");
            wartezeit = false;
          } else if (wartezeit) {
            ESP_LOGI("pH-Regelung", "Wartezeit aktiv. Noch %.0f Minuten.", (1800000 - (jetzt - letzter_eingriff)) / 60000.0);
          }

Kann mir jemand helfen?
Entweder er frisst die ezo_pmp platform nicht oder er will die platform esphome beim ota nicht.

Hier die Fehlermeldung:

INFO ESPHome 2025.4.1
INFO Reading configuration /config/esphome/esphome-nft.yaml...
INFO Unable to import component ezo_pmp.switch: No module named 'esphome.components.ezo_pmp.switch'
INFO Unable to import component ezo_pmp.switch: No module named 'esphome.components.ezo_pmp.switch'
Failed config

switch.ezo_pmp: [source /config/esphome/esphome-nft.yaml:63]
  
  Platform not found: 'switch.ezo_pmp'.
  platform: ezo_pmp
  id: ph_down_pump
  name: PH Down Pumpe
  address: 111
  i2c_id: bus_a
switch.ezo_pmp: [source /config/esphome/esphome-nft.yaml:69]
  
  Platform not found: 'switch.ezo_pmp'.
  platform: ezo_pmp
  id: duenger_pumpe
  name: Dünger Pumpe
  address: 112
  i2c_id: bus_a

Ich verstehe das nicht, es wird eigentlich alles von ESPHome unterstützt, selbst die Pumpen: Atlas Scientific Peristaltic Pump — ESPHome

Benötige dringend Hilfe.
Dankeschön
Gruß
Oli

Hi,

ich habs nur kurz überflogen aber die Fehlermeldung zeigen ja auf Zeile 63 und 69 in deinem YAML und dort eben auf “Platform not found: ‘switch.ezo_pmp’.”

Die hast du nicht definiert.
Sowas wie in der Doku vermisse ich

ezo_pmp:
  id: ezo_pmp

Hallo Tuxtom,

vielen Dank für die Antwort. Wenn ich jedoch

ezo_pmp:
  id: ezo_pmp

davor setzte, ändert sich auch nichts. Ich bin leider überhaupt kein Programmierer und verstehe daher relativ wenig was den Aufbau der Datei betrifft.

Ich glaube die Lösung steht auf der von dir verlinkten Seite weiter oben, die muss vermuttlich die Pumpe mit ihrer I2C Andresse anders definieren, wie ganz auf der Seite im Beispiel.

Ist aber nur ne Vermutung

Kannst du mich hier unterstützen? Ich habe wirklich keine Plan von dem ganzen. Für mich ist die Verlinkte seite unverständlich :frowning:

Schwer, weil ich kenn die Pumpen nicht und ohne das zu testen fast unmöglich.
Der Demo-Code vom Hersteller ist nicht verfügabr, die ganzen Links laufen ins leere - das wäre ne gute Quelle gewesen.

Ansonsten fragte im engl HomeAssistant Forum, da gibt es eine ESPHome Untergruppe und schon ein paar Artikel mit dem Pumpen bei Aquarien

Schau mal hier, vielleicht hilft das, hat mir die KI gerade rausgeworfen:

esphome:
  name: ezo_i2c_steuerung
  platform: ESP32
  board: esp32dev

wifi:
  ssid: "DEIN_WIFI"
  password: "DEIN_PASSWORT"

captive_portal:
logger:
api:
ota:

i2c:
  sda: GPIO21
  scl: GPIO22
  scan: true
  id: bus_a

# === SENSOR-KOMPONENTEN ===
sensor:
  - platform: ezo
    name: "pH-Wert"
    address: 0x63
    update_interval: 15s

  - platform: ezo
    name: "Leitfähigkeit"
    address: 0x64
    update_interval: 15s

  - platform: ezo
    name: "Wassertemperatur"
    address: 0x66
    update_interval: 15s

# === EZO-PMP PUMPEN (I2C) ===
# Pumpe 1: I2C-Adresse 0x6A
# Pumpe 2: I2C-Adresse 0x6B
# Beispielbefehl: "D,5" -> dosiere 5 ml

switch:
  - platform: template
    name: "Pumpe 1 (5ml)"
    turn_on_action:
      - lambda: |-
          const uint8_t addr = 0x6A;
          const char *cmd = "D,5";
          id(bus_a).write(addr, (const uint8_t *)cmd, strlen(cmd));
    turn_off_action:
      - logger.log: "Pumpe 1 Befehl gesendet"

  - platform: template
    name: "Pumpe 2 (3ml)"
    turn_on_action:
      - lambda: |-
          const uint8_t addr = 0x6B;
          const char *cmd = "D,3";
          id(bus_a).write(addr, (const uint8_t *)cmd, strlen(cmd));
    turn_off_action:
      - logger.log: "Pumpe 2 Befehl gesendet"

Wobei ich aber ehr glaube, das dies aus der ESPHome Doku die Lösung ist, da masst dann eben zwei von mit unterschiedlichen Namen / I2C Adressen

ezo_pmp:
  id: ezo_pmp
  address: 103  # Default Address for the EZO-PMP.
  update_interval: 60s

Habe jetzt nach langem herumprobieren das System zum laufen gebracht, jetzt muss ich die Sensoren noch kalibrieren, das kann ich jedoch erst nächstes Wochenende durchführen. Ich werde anschließend den Code und die Config posten. Rein über den ESPHome habe ich es jedoch nicht zum laufen bekommen, somit liest jetzt ein Arduino die Sensoren und der ESPHome greift auf die Daten des Arduino zu und leitet diese an Homeassistant weiter. Ich werde nächstes Wochenende auch testen ob die Befehlsweitergabe klappt (z.B. pumpe 1 kurz anschalten über Homeassistant).
Trotzdem danke für die Hilfe. Eventuell benötige ich nächste Woche weitere Hilfe mal schauen :slight_smile:

Hier der funktionierende Code für ESPHome:

# ESPHome Konfiguration für Hydroponisches NFT-System
# Echtbetrieb mit Arduino-Kommunikation

esphome:
  name: esphome-nft
  friendly_name: ESPHome_NFT

esp32:
  board: esp32dev
  framework:
    type: arduino

# WiFi Konfiguration
wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  
  # Statische IP-Konfiguration
  manual_ip:
    static_ip: xxx
    gateway: xxx
    subnet: xxx

  ap:
    ssid: "Esphome-Nft Fallback"
    password: "xxx"

captive_portal:

logger:
  level: DEBUG
  tx_buffer_size: 1024  # Erhöht für bessere Debug-Ausgaben

api:
  reboot_timeout: 0s

web_server:
  port: 80

# Globale Variablen für Sensordaten
globals:
  - id: temperature_value
    type: float
    restore_value: no
    initial_value: '25.0'
  - id: ph_value
    type: float
    restore_value: no
    initial_value: '7.0'
  - id: ec_value
    type: float
    restore_value: no
    initial_value: '1000.0'
  - id: ph_pump_state
    type: bool
    restore_value: no
    initial_value: 'false'
  - id: nutrient_pump_state
    type: bool
    restore_value: no
    initial_value: 'false'
  - id: ph_down_volume_value
    type: float
    restore_value: yes
    initial_value: '0.0'
  - id: nutrient_volume_value
    type: float
    restore_value: yes
    initial_value: '0.0'
  - id: bridge_status_string
    type: std::string
    restore_value: no
    initial_value: '"Warte auf Bridge..."'
  - id: uart_line_buffer
    type: std::string
    restore_value: no
    initial_value: '""'

# UART für die Kommunikation mit dem Arduino
uart:
  id: arduino_serial
  tx_pin: GPIO17
  rx_pin: GPIO16
  baud_rate: 115200
  rx_buffer_size: 512

# Komponente zur Verarbeitung der UART-Daten
text_sensor:
  - platform: template
    name: "Bridge Status"
    id: bridge_status
    lambda: 'return id(bridge_status_string);'
    
  - platform: template
    name: "Last UART Data"
    id: last_uart_data
    lambda: 'return id(uart_line_buffer);'
    update_interval: 2s
    
  - platform: version
    name: "ESPHome Version"
    
  - platform: wifi_info
    ip_address:
      name: "NFT IP-Adresse"
    ssid:
      name: "NFT WLAN SSID"
    bssid:
      name: "NFT WLAN BSSID"
    mac_address:
      name: "NFT MAC-Adresse"

# Zeit von Home Assistant abrufen (für tägliche Reset-Funktionen)
time:
  - platform: homeassistant
    id: homeassistant_time
    on_time:
      - seconds: 0
        minutes: 0
        hours: 0
        then:
          - lambda: |-
              // Täglicher Reset um Mitternacht
              id(ph_down_volume_value) = 0.0;
              id(nutrient_volume_value) = 0.0;
              id(ph_down_daily_volume).publish_state(0.0);
              id(nutrient_daily_volume).publish_state(0.0);

# Sensoren für die Hydroponik-Anlage (jetzt mit echten Werten vom Arduino)
sensor:
  # Temperatur-Sensor
  - platform: template
    name: "NFT Wassertemperatur"
    id: nft_temperature
    unit_of_measurement: "°C"
    accuracy_decimals: 1
    device_class: temperature
    state_class: measurement
    update_interval: 30s
    lambda: 'return id(temperature_value);'
  
  # pH-Wert Sensor
  - platform: template
    name: "NFT PH-Wert"
    id: nft_ph
    unit_of_measurement: "pH"
    accuracy_decimals: 2
    state_class: measurement
    update_interval: 30s
    lambda: 'return id(ph_value);'
  
  # EC-Wert Sensor
  - platform: template
    name: "NFT EC-Wert"
    id: nft_ec
    unit_of_measurement: "µS/cm"
    accuracy_decimals: 0
    state_class: measurement
    update_interval: 30s
    lambda: 'return id(ec_value);'
  
  # Sensoren für Dosierpumpen-Volumen
  - platform: template
    name: "PH Down Zugabe heute" 
    id: ph_down_daily_volume  
    unit_of_measurement: "ml"
    accuracy_decimals: 1
    icon: mdi:beaker
    lambda: 'return id(ph_down_volume_value);'
  
  - platform: template
    name: "Dünger Zugabe heute"
    id: nutrient_daily_volume
    unit_of_measurement: "ml"
    accuracy_decimals: 1
    icon: mdi:nutrition
    lambda: 'return id(nutrient_volume_value);'

# Binäre Sensoren für Pumpenstatus
binary_sensor:
  - platform: template
    name: "PH Down Pumpe aktiv"
    id: ph_down_pump_active
    device_class: running
    lambda: 'return id(ph_pump_state);'
  
  - platform: template
    name: "Dünger Pumpe aktiv"
    id: nutrient_pump_active
    device_class: running
    lambda: 'return id(nutrient_pump_state);'

  # Float Switch für Wasserstand
  - platform: gpio
    pin:
      number: GPIO4
      mode: INPUT_PULLUP
      inverted: true
    name: "Wasserstand OK"
    id: water_level
    filters:
      - delayed_on: 100ms
      - delayed_off: 100ms

# Schalter für manuelle Pumpensteuerung
switch:
  # Manueller Schalter für pH-Down Dosierung
  - platform: template
    name: "PH Down Dosierung"
    id: ph_down_dose
    icon: mdi:test-tube
    optimistic: true
    turn_on_action:
      - lambda: |-
          id(bridge_status_string) = "PH-Down Pumpe aktiviert";
          id(bridge_status).publish_state(id(bridge_status_string));
          // Sende Befehl an Arduino
          std::string cmd = "DOSE:PHDOWN:3.0\n";
          id(arduino_serial).write_array((const uint8_t*)cmd.c_str(), cmd.length());
      - delay: 3s
      - switch.turn_off: ph_down_dose
  
  # Manueller Schalter für Dünger Dosierung
  - platform: template
    name: "Dünger Dosierung"
    id: nutrient_dose
    icon: mdi:nutrition
    optimistic: true
    turn_on_action:
      - lambda: |-
          id(bridge_status_string) = "Dünger Pumpe aktiviert";
          id(bridge_status).publish_state(id(bridge_status_string));
          // Sende Befehl an Arduino
          std::string cmd = "DOSE:NUTRIENT:5.0\n";
          id(arduino_serial).write_array((const uint8_t*)cmd.c_str(), cmd.length());
      - delay: 3s
      - switch.turn_off: nutrient_dose

# Intervalle für verschiedene Aktionen
interval:
  # UART-Daten verarbeiten
  - interval: 50ms
    then:
      - lambda: |-
          static std::string buffer;
          uint8_t c;
          while (id(arduino_serial).available() > 0) {
            if (id(arduino_serial).read_byte(&c)) {
              if (c == '\n') {
                if (!buffer.empty()) {
                  // Speichere empfangene Zeile
                  id(uart_line_buffer) = buffer;
                  ESP_LOGD("uart", "Received: %s", buffer.c_str());
                  
                  // Parse ATLAS:DATA:TEMP:xx.x:PH:x.xx:EC:xxxx:PHPUMP:x:NUTPUMP:x
                  std::string &data = buffer;
                  if (data.compare(0, 11, "ATLAS:DATA:") == 0) {
                    size_t temp_pos = data.find(":TEMP:");
                    size_t ph_pos = data.find(":PH:");
                    size_t ec_pos = data.find(":EC:");
                    size_t phpump_pos = data.find(":PHPUMP:");
                    size_t nutpump_pos = data.find(":NUTPUMP:");
                    
                    if (temp_pos != std::string::npos && ph_pos != std::string::npos) {
                      std::string temp_str = data.substr(temp_pos + 6, ph_pos - (temp_pos + 6));
                      float temp = atof(temp_str.c_str());
                      if (temp > -10.0 && temp < 100.0) {
                        id(temperature_value) = temp;
                        id(nft_temperature).publish_state(temp);
                      }
                    }
                    
                    if (ph_pos != std::string::npos && ec_pos != std::string::npos) {
                      std::string ph_str = data.substr(ph_pos + 4, ec_pos - (ph_pos + 4));
                      float ph = atof(ph_str.c_str());
                      if (ph > 0.0 && ph < 14.0) {
                        id(ph_value) = ph;
                        id(nft_ph).publish_state(ph);
                      }
                    }
                    
                    if (ec_pos != std::string::npos && phpump_pos != std::string::npos) {
                      std::string ec_str = data.substr(ec_pos + 4, phpump_pos - (ec_pos + 4));
                      float ec = atof(ec_str.c_str());
                      if (ec >= 0.0 && ec < 10000.0) {
                        id(ec_value) = ec;
                        id(nft_ec).publish_state(ec);
                      }
                    }
                    
                    if (phpump_pos != std::string::npos && nutpump_pos != std::string::npos) {
                      std::string phpump_str = data.substr(phpump_pos + 8, nutpump_pos - (phpump_pos + 8));
                      bool ph_active = (phpump_str == "1");
                      id(ph_pump_state) = ph_active;
                      id(ph_down_pump_active).publish_state(ph_active);
                      
                      std::string nutpump_str = data.substr(nutpump_pos + 9);
                      bool nut_active = (nutpump_str == "1");
                      id(nutrient_pump_state) = nut_active;
                      id(nutrient_pump_active).publish_state(nut_active);
                    }
                  }
                  // Parse ATLAS:DOSING:PHDOWN:3.0 oder ATLAS:DOSING:NUTRIENT:5.0
                  else if (data.compare(0, 13, "ATLAS:DOSING:") == 0) {
                    if (data.find(":PHDOWN:") != std::string::npos) {
                      size_t val_pos = data.find(":PHDOWN:") + 8;
                      std::string volume_str = data.substr(val_pos);
                      float volume = atof(volume_str.c_str());
                      if (volume > 0.0) {
                        id(ph_down_volume_value) += volume;
                        id(ph_down_daily_volume).publish_state(id(ph_down_volume_value));
                        id(bridge_status_string) = "PH-Down Dosierung: " + volume_str + " ml";
                        id(bridge_status).publish_state(id(bridge_status_string));
                      }
                    }
                    else if (data.find(":NUTRIENT:") != std::string::npos) {
                      size_t val_pos = data.find(":NUTRIENT:") + 10;
                      std::string volume_str = data.substr(val_pos);
                      float volume = atof(volume_str.c_str());
                      if (volume > 0.0) {
                        id(nutrient_volume_value) += volume;
                        id(nutrient_daily_volume).publish_state(id(nutrient_volume_value));
                        id(bridge_status_string) = "Dünger Dosierung: " + volume_str + " ml";
                        id(bridge_status).publish_state(id(bridge_status_string));
                      }
                    }
                  }
                  else if (data.compare(0, 18, "ATLAS:BRIDGE:READY") == 0) {
                    id(bridge_status_string) = "Bridge verbunden";
                    id(bridge_status).publish_state(id(bridge_status_string));
                  }
                  
                  // Buffer leeren
                  buffer.clear();
                }
              } else if (c != '\r') {
                // Zeilenumbrüche ignorieren
                buffer += static_cast<char>(c);
              }
            }
          }
          
  # Automatische Steuerung von pH-Wert und EC-Wert (basierend auf echten Werten)
  - interval: 15min
    then:
      - lambda: |-
          // Nur ausführen, wenn der Wasserstand OK ist
          if (!id(water_level).state) {
            return;
          }
          
          // pH-Wert Steuerung
          if (id(ph_value) > 6.5 && !id(ph_pump_state)) {
            // pH-Wert zu hoch, pH-Down dosieren
            id(bridge_status_string) = "Auto: pH-Wert zu hoch";
            id(bridge_status).publish_state(id(bridge_status_string));
            std::string cmd = "DOSE:PHDOWN:2.0\n";
            id(arduino_serial).write_array((const uint8_t*)cmd.c_str(), cmd.length());
          }
          
          // EC-Wert Steuerung
          if (id(ec_value) < 1200 && !id(nutrient_pump_state)) {
            // EC-Wert zu niedrig, Nährstoffe dosieren
            id(bridge_status_string) = "Auto: EC-Wert zu niedrig";
            id(bridge_status).publish_state(id(bridge_status_string));
            std::string cmd = "DOSE:NUTRIENT:3.0\n";
            id(arduino_serial).write_array((const uint8_t*)cmd.c_str(), cmd.length());
          }

Hier der Code für den Arduino:

/**
 * Arduino Bridge für Atlas Scientific Sensoren (speicheroptimiert)
 */

#include <Wire.h>

// I2C-Adressen der EZO-Module
#define RTD_ADDR 0x66     // Temperatur 
#define PH_ADDR 0x63      // pH-Wert
#define EC_ADDR 0x64      // EC-Wert
#define PH_PUMP_ADDR 0x67 // pH-Down Pumpe
#define NUT_PUMP_ADDR 0x68 // Nährstoff-Pumpe

void setup() {
  Serial.begin(115200);
  Wire.begin();
  delay(1000);
  Serial.println("ATLAS:BRIDGE:READY");
}

void loop() {
  // Lese die Sensoren
  float temp = readSensor(RTD_ADDR, 600);
  float ph = readSensor(PH_ADDR, 900);
  float ec = readSensor(EC_ADDR, 900);
  
  // Sende Daten
  Serial.print("ATLAS:DATA:TEMP:");
  Serial.print(temp);
  Serial.print(":PH:");
  Serial.print(ph);
  Serial.print(":EC:");
  Serial.print(ec);
  Serial.print(":PHPUMP:0:NUTPUMP:0");
  Serial.println();
  
  // Auf Befehle prüfen
  if (Serial.available()) {
    String cmd = Serial.readStringUntil('\n');
    if (cmd.startsWith("DOSE:PHDOWN:")) {
      float vol = cmd.substring(12).toFloat();
      dosePump(PH_PUMP_ADDR, vol, "PHDOWN");
    }
    else if (cmd.startsWith("DOSE:NUTRIENT:")) {
      float vol = cmd.substring(14).toFloat();
      dosePump(NUT_PUMP_ADDR, vol, "NUTRIENT");
    }
    else if (cmd == "STATUS:REQ") {
      // Sende Daten erneut
    }
  }
  
  // Warte 2 Sekunden bis zum nächsten Zyklus
  delay(2000);
}

// Funktion zum Lesen eines Sensors
float readSensor(byte addr, int delayTime) {
  // Sende Lesebefehl
  Wire.beginTransmission(addr);
  Wire.write("R");
  if (Wire.endTransmission() != 0) {
    return -999.0; // Fehler
  }
  
  // Warte auf Antwort
  delay(delayTime);
  
  // Lese Antwort
  Wire.requestFrom((int)addr, (int)20);
  
  if (!Wire.available()) {
    return -999.0; // Keine Antwort
  }
  
  // Erstes Byte ist der Status
  byte status = Wire.read();
  
  // Lese die restlichen Bytes
  char data[20];
  byte i = 0;
  
  while (Wire.available() && i < 19) {
    data[i] = Wire.read();
    i++;
  }
  data[i] = '\0'; // String-Terminierung
  
  // Konvertiere zu Fließkommazahl
  return atof(data);
}

// Funktion zum Dosieren mit einer Pumpe
void dosePump(byte addr, float vol, const char* pumpName) {
  if (vol <= 0.0 || vol > 20.0) {
    Serial.println("ATLAS:ERROR:INVALID_VOLUME");
    return;
  }
  
  // Sende Dosierbefehl
  Wire.beginTransmission(addr);
  Wire.print("D,");
  Wire.print(vol);
  
  byte error = Wire.endTransmission();
  
  if (error == 0) {
    Serial.print("ATLAS:DOSING:");
    Serial.print(pumpName);
    Serial.print(":");
    Serial.println(vol);
  } else {
    Serial.print("ATLAS:ERROR:");
    Serial.print(pumpName);
    Serial.print("_COMM:");
    Serial.println(error);
  }
}

Hier die manuelle Karte für das Dashboard:

type: vertical-stack
cards:
  # Sensor values
  - type: entities
    title: NFT Sensorwerte
    entities:
      - entity: sensor.esphome_nft_nft_wassertemperatur
        name: Temperatur
        icon: mdi:thermometer
      - entity: sensor.esphome_nft_nft_ph_wert
        name: pH-Wert
        icon: mdi:flask
      - entity: sensor.esphome_nft_nft_ec_wert
        name: EC-Wert
        icon: mdi:water-opacity
  
  # System status
  - type: glance
    title: Systemstatus
    entities:
      - entity: binary_sensor.esphome_nft_wasserstand_ok
        name: Wasserstand
        icon: mdi:water-check
      - entity: binary_sensor.esphome_nft_ph_down_pumpe_aktiv
        name: pH-Down Pumpe
        icon: mdi:water-pump
      - entity: binary_sensor.esphome_nft_d_nger_pumpe_aktiv
        name: Dünger Pumpe
        icon: mdi:water-pump
  
  # Manual Control
  - type: entities
    title: Manuelle Steuerung
    show_header_toggle: false
    entities:
      - entity: switch.esphome_nft_ph_down_dosierung
        name: pH-Down Pumpe aktivieren
        icon: mdi:test-tube
      - entity: switch.esphome_nft_d_nger_dosierung
        name: Dünger Pumpe aktivieren
        icon: mdi:nutrition
  
  # History graph
  - type: history-graph
    title: Sensorverlauf
    hours_to_show: 24
    entities:
      - entity: sensor.esphome_nft_nft_wassertemperatur
        name: Temperatur
      - entity: sensor.esphome_nft_nft_ph_wert
        name: pH-Wert
      - entity: sensor.esphome_nft_nft_ec_wert
        name: EC-Wert
  
  # Daily dosage
  - type: entities
    title: Tägliche Dosierung
    entities:
      - entity: sensor.esphome_nft_ph_down_zugabe_heute
        name: pH-Down
        icon: mdi:beaker
      - entity: sensor.esphome_nft_d_nger_zugabe_heute
        name: Dünger
        icon: mdi:nutrition

Das System läuft jetzt erstmal, jetzt muss ich mir nur Gedanken zum EC-Wert machen, da dieser über die Zeit ansteigt, da Wasser verbraucht wird, und somit der Salzgehalt langsam steigt. Wenn ich somit mit einem EC-Wert von 1300 starte, habe ich nach zwei Woche (Wasser muss nachgefüllt werden), einen EC-Wert von 1500. Somit weiß ich leider nicht wie viele der guten Nährstoffe noch im Wasser vorhanden sind und wieviel davon Salz ist. Hat jemand von euch eine Idee, außer Wasserwechsel? Ich habe extra einen Filter (Poolfilter fein zweilagig, darunter Vulkansteine, darunter Aktivkohle), gebaut um den Wasserwechsel zu reduzieren.

1 „Gefällt mir“