Controlling a Gardena magnet valve (9V) with HA

Hi,

I want to share this usecase and how I implemented it.

The usecase is that I have a watering system made by Gardena. It includes a magnetic valve operated at 9V, that should be either opened manually for a given period of time or in the earling morning hours automatically for a different period of time. When the automation is running, the manual interaction with the valve should emit a warning to be confirmed by the user before opening/closing the valve.

I have made a small circutit with a ESP8266 and a H-Bridge to drive the valve (whill share the schematics later). The thing to observe is that the valve needs 9V/some ms pulse to open, but only -2V/some ms to close. -9V does NOT close the valve.

The ESP is using MQTT messages to communicate with HA. It receives commands on/off and duration in seconds. It feeds back the current state of the valve on/off.

I have defined a boolean sensor to display the current state of the valve (boolean_sensor.water_valve1_state), which shows the state of the input_boolean.water_valve1 switch.
The switch value is only controlled by the feedback of the ESP via MQTT, it should not be modifed by user input on a card.

for the user input, I have created a card holding a input_number (0-30) to let the user choose the running in minutes. additionally, the sensor.water_valve_state is shown, and the tap action is defined to call a script to open the valve. In fact, there are 2 variants of the sensor display, one for normal operation (manual on/off) and another one that is presented during the running automation.

that is basically all.

here is the card definition:

type: entities
entities:
  - entity: input_number.water_duration
    name: Dauer
    secondary_info: none
    icon: mdi:av-timer
  - type: conditional
    conditions:
      - entity: input_boolean.water_automation_running
        state: 'on'
    row:
      entity: binary_sensor.water_gauge1_state
      tap_action:
        action: call-service
        service: script.water_gauge1_script
        target: {}
        confirmation:
          text: Programm läuft, willst du eingreifen?
      name: Manuell Start/Stop während des Programms
      secondary_info: none
      icon: ''
  - type: conditional
    conditions:
      - entity: input_boolean.water_automation_running
        state: 'off'
    row:
      entity: binary_sensor.water_gauge1_state
      tap_action:
        action: call-service
        service: script.water_gauge1_script
        target: {}
      name: Manuell Start/Stop
      secondary_info: none
      icon: ''
title: Wassersteuerung
state_color: true

grafik

here comes the code of the scripts:

water_gauge1_script:
  alias: 'water_gauge1_script'
  sequence:
    - service: mqtt.publish
      data_template:
        topic: 'Wassersteuerung/Ventil1'
        payload: >
          {%- set value = "false" if is_state('input_boolean.water_gauge1', "on") else "true" -%}
          {%- set duration =  (states('input_number.water_duration')|float(0) * 60)|int -%}
          {"command":{{ value }}, "time": {{duration}}}
  mode: single
## MKS 2024/07/14: Script für zeitgesteuerten trigger
water_gauge1_script_auto:
  alias: 'water_gauge1_script_auto'
  fields:
    duration:
      description: "the duration of the water flow"
  sequence:
    - service: mqtt.publish
      data_template:
        topic: 'Wassersteuerung/Ventil1'
        payload: >
          {"command": true, "time": {{duration|multiply(60) | int}}}
    - delay:
        minutes: "{{ duration + 1 }}"
  mode: single

and these are the automations. 2 automations just to receive the status of the valve via MQTT. The last automation executes the watering 4x 15minutes. Just before and after it signals that the automation is running (we use this information to alert the user within the card, see above).

- id: get_water_feedback_on
  alias: Water/Gauge1/Feedback/on
  description: gets "on" event from water gauge
  trigger:
  - platform: mqtt
    topic: Wassersteuerung/Feedback
    payload: '1'
  condition: []
  action:
  - service: homeassistant.turn_on
    entity_id: input_boolean.water_gauge1
- id: get_water_feedback_off
  alias: Water/Gauge1/Feedback/off
  description: gets "on" event from water gauge
  trigger:
  - platform: mqtt
    topic: Wassersteuerung/Feedback
    payload: '0'
  condition: []
  action:
  - service: homeassistant.turn_off
    entity_id: input_boolean.water_gauge1
- id: '1720964078758'
  alias: Wasser_Morgens
  description: Morgendliche Bewässerung
  trigger:
  - platform: time
    at: 07:00:00
  condition: []
  action:
  - service: input_boolean.turn_on
    data: {}
    target:
      entity_id: input_boolean.water_automation_running
  - service: script.water_gauge1_script_auto
    data:
      duration: 15
  - service: script.water_gauge1_script_auto
    data:
      duration: 15
  - service: script.water_gauge1_script_auto
    data:
      duration: 15
  - service: script.water_gauge1_script_auto
    data:
      duration: 15
  - service: input_boolean.turn_off
    data: {}
    target:
      entity_id: input_boolean.water_automation_running
  mode: single


here is the code to be uploaded to the ESP8266:

/**
 * 
 * MQTT 2.5.1 https://github.com/256dpi/arduino-mqtt
 *
*/ 
#include <ESP8266WiFi.h>
#include <MQTT.h>
#include <ArduinoJson.h>


// communication via WLAN
#include "WifiSecrets.h"
MQTTClient client;
WiFiClient net;
int status = WL_IDLE_STATUS;






// the internal LED for debugging (2 for esp32, 1 for esp8266)
//#define LED_BUILTIN 1
#define doBlink true

// pins are specific to the board, esp8266 is pin 5 & 4, esp32 22 & 21
// 5 must be 1 for 250ms to start water
// 4 must be 1 for 250ms to stop water
int START_PIN = 5;
int STOP_PIN = 4;


// debug = true: output on serial. Note: time consuming!
bool debug = false;

// some flags to control the flow
bool isConnected = false;
bool isWaterRunning = false;
bool messageToSend = false;


// timer
long timeelapsed;
long lasttime;
long now;

bool action ; // on = true, off = false
int duration; // in seconds!

// a Jsondoc to communicate via MQTT payloads
JsonDocument pl;
DeserializationError deserialError;


void ledon() {
  digitalWrite(LED_BUILTIN, LOW);
}

void ledoff() {
  digitalWrite(LED_BUILTIN, HIGH);
}

void blink() {
  if (doBlink) {
    ledoff();
    delay(50);
    ledon();
    delay(100);
    ledoff();
    delay(50);
    ledon();
    delay(100);
    ledoff();
  }
}


void flash() {
  if (doBlink) {
    ledon();
    delay(25);
    ledoff();
  }
}

void initTimer(){
  now = millis();
  lasttime = now;
}

bool checkTimeElapsed() {
  now = millis();
  timeelapsed = now - lasttime;
  if (timeelapsed > duration*1000) {
    return true;
  } else {
    return false;
  }
}


void mqttConnect() {
  client.begin(MQTTSERVER, net);
  int count = 0;
  while (!client.connect("Wassersteuerung") && count < 10) {
    delay(100);
    count++;
  }
  client.onMessage(mqttReceive);
  client.subscribe("Wassersteuerung/Ventil1");
  Serial.print("MQTT listening...");
  client.publish("Wassersteuerung/Feedback","Wassersteuerung bereit");
}

void mqttSend(bool state) {
  client.publish("Wassersteuerung/Feedback", String(state));
  delay(50);
  client.loop();
}

void mqttDisconnect() {
  client.disconnect();
}


void mqttReceive(String &topic, String &payload) {
  Serial.print("Message arrived on topic: ");
  Serial.print(topic);
  Serial.print(". Message: ");
  Serial.print(payload);
  deserialError = deserializeJson(pl, payload);

  // Test if parsing succeeds.
  if (deserialError) {
    Serial.print(F("deserializeJson() failed: "));
    Serial.println(deserialError.f_str());
    return;
  }

  action = pl["command"];
  duration = pl["time"];

  if(action ){
    water_start();
  }
  else {
    water_stop();
  }
}

void wait_250ms() {
  delay(250);  
}

void all_down() {
  digitalWrite(STOP_PIN, LOW);
  digitalWrite(START_PIN, LOW);
}


void water_start() {
  digitalWrite(START_PIN, HIGH);
  digitalWrite(STOP_PIN, LOW);
  wait_250ms();
  all_down();
  isWaterRunning = true;
  initTimer();
  queueFeedback();
  ledon();
}



void water_stop() {
  digitalWrite(STOP_PIN, HIGH);
  digitalWrite(START_PIN, LOW);
  wait_250ms();
  all_down();
  isWaterRunning = false;
  queueFeedback();
  ledoff();
}

void queueFeedback(){
  messageToSend = true;
}

void sendFeedback() {
  if (messageToSend) {
    mqttSend(isWaterRunning);
    messageToSend = false;
  } 
}

void setup() {
  Serial.begin(115200);
  Serial.println();
  Serial.println("============ Hello Hello ===========");


  // timer
  lasttime = millis();
  now = millis();


  pinMode(START_PIN, OUTPUT);
  pinMode(STOP_PIN, OUTPUT);
  pinMode(LED_BUILTIN, OUTPUT);

  
  blink();
 
  
  // connect to Wifi and send everything to the server
  //WiFi.disconnect();
  //WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE);
  WiFi.setHostname("Wassersteuerung");
  WiFi.mode(WIFI_STA);
  WiFi.begin(SSID, PSK);
  int cnt = 0;
  while (status != WL_CONNECTED and cnt < 20) {
    cnt++;
    status = WiFi.status();
    // wait 10 seconds if not yet connected.
    if (status != WL_CONNECTED) {
      delay(1000);
      Serial.print(".");
    }
  }

  // we only send if we have WLAN
  // otherwise we just go to sleep again and wait for better times.
  if (status == WL_CONNECTED) {
    mqttConnect();
      Serial.println("WLAN ok.");
      Serial.println("IP address: ");
      Serial.println(WiFi.localIP());
      blink();
  } else {
    cnt = 0;
    while (cnt < 1000) {
      cnt++;
      blink();
    }
  }

  // finally, stop evenually running water:
  water_stop();

}





// main loop
void loop() {
  sendFeedback();
  client.loop();
  if (isWaterRunning && checkTimeElapsed()) {  
    water_stop();
  }
}

Hi, I am looking forward something very similar. Would it be possible that you also share your circuit schematics? Do you power all by a 9V battery?