Atomated plant watering using esp32

Hello everybody,
this is my first post so please excuse my not-so-professional post.
Currently iam building a system that is able to measure the soil moisture and if its under a certain threshold it should pump water for a certain time. All this should not be to much electricity consuming because i would like to run it on battery.
The cherry would be if iam able to see in HA the last measured moisture, last time it pumped and force a pumping.

My Setup is this:


Just with the difference that i use an esp32 instead of an esp8266.

My current code in ESPHome is this:

esphome:
  name: esp32-gieskanne1  

esp32:
  board: az-delivery-devkit-v4
  framework:
    type: arduino

sensor:
  - platform: adc # airdry: 2,8V; waterwet: 1,58V
    pin: 34
    name: "soil Humidity"
    id: Moisturesensor
    update_interval: 10s
    attenuation: auto
    unit_of_measurement: "%"
    filters:
    - median:
        window_size: 7
        send_every: 4
        send_first_at: 1
    - calibrate_linear:
        - 2.80 -> 0.00
        - 1.58 -> 100.00
    - lambda: |
        if (x < 0) return 0; 
        else if (x > 100) return 100;
        else return (x);
    accuracy_decimals: 0
    on_value_range:
      - below: 50.0
        then:
          #- logger.log: "1"
          - switch.turn_on: pump1
          - delay: 5s
          - switch.turn_off: pump1

switch:
  - platform: gpio
    pin: 22
    name: "pumping"
    id: pump1
    restore_mode: ALWAYS_OFF
    icon: "mdi:water-pump"

# Enable logging
logger:

# Enable Home Assistant API
api:

ota:
  password: "xxxx"

wifi:
  ssid: xxxx
  password: xxxx

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Esp32-Gieskanne1"
    password: "xxxx"

captive_portal:

I can imagine that someone had the same idea and its already solved but i didnt find it. So if you provide a link to this topic it would be a big help.
Otherwiese i would like to get help in how to make some kind of loop that ist doing something like this:

  1. wake up
  2. Check if pumping is forced, if yes pump and go to short deep sleep
  3. measure moisture
  4. if under threshold pump and go in short deep sleep
  5. if no pumping needed go back for longer deep sleep

Thank you very much in advance for help!

4 Likes

esp home has a component component deep sleephere look.

Hello,
thank you for the fast answer. I have seen this one but i dont know how i can still embed this into my code.
especially if i want to kind of loop it.
Till now i have the problem that the pumpuing is once triggered an the never again. and where do you put this deep sleep command?

I am highly interested in your interest, but you took a high step step… you have interesting interesting ideas, but first you should understand the elementary functions of automation. everything is very accessible in the espd home documentation. I will only say one thing: your idea with autonomy is unsolvable.

Deep sleep runs in a loop, you’ll need to set an interval to check, below it’s 6 hours. If when it comes out of sleep and the sensor is below 50 it will suspend sleep.

It will water for 5s and wait for a minute, then water again for 5s in a loop until the sensor is above 50, then go back to the sleep routine.

deep_sleep:
  run_duration: 10s
  sleep_duration: 6h
  id: deep_sleep1

sensor:
  - platform: adc # airdry: 2,8V; waterwet: 1,58V
    pin: 34
    name: "soil Humidity"
    id: moisturesensor
    update_interval: 10s
    attenuation: auto
    unit_of_measurement: "%"
    filters:
    - median:
        window_size: 7
        send_every: 4
        send_first_at: 1
    - calibrate_linear:
        - 2.80 -> 0.00
        - 1.58 -> 100.00
    - lambda: |
        if (x < 0) return 0; 
        else if (x > 100) return 100;
        else return (x);
    accuracy_decimals: 0
    on_value_range:
      - below: 50.0
        then:
          - deep_sleep.prevent: deep_sleep1
          - while:
              condition:
                sensor.in_range:
                  id: moisturesensor
                  below: 50.0
              then:
                - switch.turn_on: pump1
                - delay: 5s
                - switch.turn_off: pump1
                - delay: 1min
      - above: 50.0
        then:
          - deep_sleep.enter: deep_sleep1

2 Likes

Hallo,
thank your for your example. It helped me a lot!
It didnt worked perfectly because it had still the issue that if the Sensor value was still in the same range it didnt triggered the condition e.g. the pumping.
I modified it and now i have a solution witch is also still triggering if the value doesnt change the range and it is also in deep_sleep after pumping.

deep_sleep:
  run_duration: 30s
  sleep_duration: 2h
  id: deep_sleep1

switch:
  - platform: gpio
    pin: 22
    name: "pumping"
    id: pump1
    restore_mode: ALWAYS_OFF
    icon: "mdi:water-pump"

sensor:
  - platform: adc # airdry: 2,8V; waterwet: 1,58V
    pin: 34
    name: "soil Humidity"
    id: moisturesensor
    update_interval: 10s
    attenuation: auto
    unit_of_measurement: "%"
    filters:
    - median:
        window_size: 7
        send_every: 4
        send_first_at: 1
    - calibrate_linear:
        - 2.80 -> 0.00
        - 1.58 -> 100.00
    - lambda: |
        if (x < 0) return 0; 
        else if (x > 100) return 100;
        else return (x);
    accuracy_decimals: 0
    on_value:
      then:
        - if:
            condition:
              lambda: 'return id(moisturesensor).state < 50;'
            then:
              - deep_sleep.prevent: deep_sleep1
              - switch.turn_on: pump1
              - delay: 5s
              - switch.turn_off: pump1
              - deep_sleep.enter:
                  id: deep_sleep1
                  sleep_duration: 60s
            else:
              - deep_sleep.enter: deep_sleep1

Now i try to find a solution how i can change in the HA UI the Moisture threshold and the Sleep time. My current idea ist this but it doesnt work correctly:

deep_sleep:
  run_duration: 30s
  sleep_duration: 60s
  id: deep_sleep1

switch:
  - platform: gpio
    pin: 22
    name: "pumping"
    id: pump1
    restore_mode: ALWAYS_OFF
    icon: "mdi:water-pump"

  - platform: gpio
    pin: 23
    name: "running"
    id: LED1
    inverted: true

  - platform: gpio
    pin: 14
    name: "dry"
    id: LED2
    on_turn_on:
    - delay: 2s
    - switch.turn_off: LED2

sensor:
  - platform: homeassistant
    name: "Moisture Set Point"
    entity_id: input_number.desired_moisture1
    id: moisture_set
    
  - platform: homeassistant
    name: "Sleep Time"
    entity_id: input_number.desired_sleeptime1
    id: sleeptime_set

  - platform: adc # airdry: 2,8V; waterwet: 1,58V
    pin: 34
    name: "soil Humidity"
    id: moisturesensor
    update_interval: 10s
    attenuation: auto
    unit_of_measurement: "%"
    filters:
    - median:
        window_size: 7
        send_every: 4
        send_first_at: 1
    - calibrate_linear:
        - 2.80 -> 0.00
        - 1.58 -> 100.00
    - lambda: |
        if (x < 0) return 0; 
        else if (x > 100) return 100;
        else return (x);
    accuracy_decimals: 0
    on_value:
      then:
        - logger.log:
            level: DEBUG
            format: "value %.1f"
            args: ['id(moisture_set).state']
        - if:
            condition:
              lambda: 'return id(moisturesensor).state < id(moisture_set).state;'
            then:
              - deep_sleep.prevent: deep_sleep1
              - switch.turn_on: LED2
              - switch.turn_on: pump1
              - delay: 5s
              - switch.turn_off: pump1
              - deep_sleep.enter:
                  id: deep_sleep1
                  sleep_duration: 10s
            else:
              - delay: 30s
              - deep_sleep.enter: deep_sleep1

I still trying to figuring it out where the problem is. Becaus of this there are so many status LEDs and logging code. I just wanted to show my current status. :slight_smile:

The below compiles fine for me. I am guessing it’s the sleep component is the problem, it’s showing that the duration is not templatable. I don’t know anyway to get around that, you’ll have to update the sketch to change that value.

Hello Mikefila,
i could narrow it down to the comparing:

lambda: 'return id(moisturesensor).state < id(moisture_set).state;'

It seems like it doesnt do its job.
For example, this works well:

lambda: 'return id(moisturesensor).state < 50;'

It compiles flawlessly but the esp doesnt trigger the pumping when i use the HA UI to set the threshold higher then the moisture value.

I’m not sure why that doesn’t work, you could try putting that portion in a separate binary sensor.

      - platform: template
        id: dry
        name: "soil dry"
        lambda: |-    
          if (return id(moisturesensor).state < id(moisture_set).state){
            return true;}
            else  {
            return false;
          }

and use the condition

binary_sensor.is_on: dry

Hello,
i think we solved it now. :slight_smile:
I used your idea but at the end i figured it out that there was an issue to update the threshold value from HA. For this i added a condition to check for this value.
Here the code:

deep_sleep:
  run_duration: 30s
  sleep_duration: 60s
  id: deep_sleep1

sensor:
  - platform: adc # airdry: 2,8V; waterwet: 1,58V
    pin: 34
    name: "soil Humidity"
    id: moisturesensor
    attenuation: auto
    unit_of_measurement: "%"
    accuracy_decimals: 0
    update_interval: 5s
    filters:
    - median:   #median over the last 7 values
        window_size: 7
        send_every: 4
        send_first_at: 1
    - calibrate_linear: #setting the voltage values to equalent percantages
        - 2.80 -> 0.00
        - 1.58 -> 100.00
    # limit the value to 0 and 100%
    - lambda: |-
        if (x < 0) return 0; 
        else if (x > 100) return 100;
        else return (x);
    on_value:
      then:
        - if:
            condition:  #check that the threshold value from HA UI is imported
              sensor.in_range:
                id: moisture_set
                below: 100.0
            then:
              - if:
                  condition:  #compare value from sensor with thresholdvalue
                    lambda: 'return id(moisturesensor).state < id(moisture_set).state;'
                  then:
                  - deep_sleep.prevent: deep_sleep1
                  - switch.turn_on: pump1
                  - delay: 10s
                  - switch.turn_off: pump1
                  - deep_sleep.enter:
                      id: deep_sleep1
                      sleep_duration: 30s
                  else:
                  - switch.turn_on: LED2
                  - delay: 15s
                  - switch.turn_off: LED2
                  - deep_sleep.enter: deep_sleep1

  - platform: homeassistant
    name: "Moisture Set Point"
    entity_id: input_number.desired_moisture1
    id: moisture_set
    accuracy_decimals: 0

  - platform: homeassistant
    name: "Sleep Time"
    entity_id: input_number.desired_sleeptime1
    id: sleeptime_set

switch:
  - platform: gpio
    pin: 22
    name: "pumping"
    id: pump1
    restore_mode: ALWAYS_OFF
    icon: "mdi:water-pump"

  - platform: gpio
    pin: 23
    name: "running"
    id: LED1
    inverted: true

  - platform: gpio
    pin: 14
    name: "status"
    id: LED2
    restore_mode: ALWAYS_OFF
1 Like

Hello,
i think i solved it. For the community here my Solution. In the future i will provide a description about the hole setup.
The HA UI that i use is this:
Unbenannt

The esp32 wakes up after the set sleep time and is doing this:

  1. measuring the moisture and sending the value to HA
  2. If its under the set Threshold it pumps for 10s
  3. Sending the last time pumped to HA
  4. If pumped it goes just for a few minutes to deep sleep
  5. Check if it is allowed to go to deep sleep (I have this to be able to update the code)
  6. Only if no pumping is needed it goes to the set duration into deep sleep

In HA i had to make two input.number for the moisture threshold and the Sleep time duration, as well as one input.boolean for the prevention of deep sleep.

The code for the esp32 ist here:
Note: i commented the interesting parts that where complicated.

esphome:
  name: esp32-gieskanne1

esp32:
  board: az-delivery-devkit-v4
  framework:
    type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:

ota:
  password: "xxxx"

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

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Esp32-Gieskanne1"
    password: "xxx"

captive_portal:

time:
  - platform: homeassistant
    id: homeassistant_time

deep_sleep:
  run_duration: 30s
  sleep_duration: 5min
  id: deep_sleep1

sensor:
  - platform: adc # airdry: 2,8V; waterwet: 1,58V
    pin: 34
    name: "soil Moisture"
    id: moisturesensor
    attenuation: auto
    unit_of_measurement: "%"
    accuracy_decimals: 0
    update_interval: 5s
    filters:
    - median:   #median over the last 7 values
        window_size: 7
        send_every: 4
        send_first_at: 1
    - calibrate_linear: #setting the volatge values to equalent percantages
        - 2.80 -> 0.00
        - 1.58 -> 100.00
    # limit the value to 0 and 100%
    - lambda: |-
        if (x < 0) return 0; 
        else if (x > 100) return 100;
        else return (x);
    on_value:
      then:
        - if:
            condition:  #check that the threshold value from HA UI is imported
              sensor.in_range:
                id: moisture_set
                below: 100.0
            then:
              - if:
                  condition:  #compare value from sensor with threshold value, it is more complicated that "on_range" bcs it also triggers now when the value didnt changed
                    lambda: 'return id(moisturesensor).state < id(moisture_set).state;'
                  then:
                  - deep_sleep.prevent: deep_sleep1
                  - switch.turn_on: pump1
                  - delay: 10s
                  - switch.turn_off: pump1
                  - sensor.template.publish: #sending Timestamp of pumping to HA UI                                                                                                                                 
                      id: pump1_last_update                                                                                                                                                   
                      state: !lambda 'return id(homeassistant_time).now().timestamp;'
                  - if:
                      condition: #prevent esp32 from entering deep_sleep for updating purpose
                        binary_sensor.is_on: preventdeepsleep_set
                      then:
                      - deep_sleep.prevent: deep_sleep1
                      else:
                      - deep_sleep.enter:
                          id: deep_sleep1
                          sleep_duration: 2min
                  else:
                  - switch.turn_on: LED2
                  - delay: 15s
                  - switch.turn_off: LED2
                  - if:
                      condition: #prevent esp32 from entering deep_sleep for updating purpose
                        binary_sensor.is_on: preventdeepsleep_set
                      then:
                      - deep_sleep.prevent: deep_sleep1
                      else:
                      - deep_sleep.enter:
                          id: deep_sleep1
                          sleep_duration: !lambda 'return id(sleeptime_set).state*60000;' #for converting in min *60000, for converting in h *3600000

  - platform: homeassistant
    name: "Moisture Set Point"
    entity_id: input_number.desired_moisture1
    id: moisture_set
    accuracy_decimals: 0

  - platform: homeassistant
    name: "Sleep Time"
    entity_id: input_number.desired_sleeptime1
    id: sleeptime_set
  
  - platform: template
    name: "Pump1 last watered"
    device_class: timestamp
    id: pump1_last_update

binary_sensor:
  - platform: homeassistant 
    name: "prevent deep_sleep"
    entity_id: input_boolean.prevent_deep_sleep
    id: preventdeepsleep_set

switch:
  - platform: gpio
    pin: 22
    name: "pumping"
    id: pump1
    restore_mode: ALWAYS_OFF
    icon: "mdi:water-pump"

  - platform: gpio
    pin: 23
    name: "running"
    id: LED1
    inverted: true

  - platform: gpio
    pin: 14
    name: "status"
    id: LED2
    restore_mode: ALWAYS_OFF 

Following problems that i struggled with i solved here:

  1. the “on_value_range” command only triggers when the new value is inside the range and the previous value was outside the range. With this it didnt triggered the pumping if the value was still under the threshold. So i used the “on-value” and if condition.
  2. Becuse of the excessive use of deep_sleep for battery saving reasons, often on booting the script had the measured moisture value but dont had the threshold value from HA and went to completly strange conditions. For this i build an if condition to just check the threshold value if its available. (I think i abused it a bit. :slight_smile: )

The part of the code that iam talking about is this:

    on_value:
      then:
        - if:
            condition:  #check that the threshold value from HA UI is imported
              sensor.in_range:
                id: moisture_set
                below: 100.0
            then:
              - if:
                  condition:  #compare value from sensor with threshold value, it is more complicated that "on_range" bcs it also triggers now when the value didnt changed
                    lambda: 'return id(moisturesensor).state < id(moisture_set).state;'

So this is my walk of pain to this solution. I hope it helps further anonymus masochists in the future.
As i mentioned i will make a post in the future about the hole project with Part list and how it is set up in my plants.

5 Likes

@Grieche

Can you also share the YAML for the HA Lovelace card you have shown in your post above above ?

BlockquoteUnbenannt

Have a similar project for automated plant watering if it helps: https://github.com/rasclatt-dot-com/ESP32-Plant-Waterer-for-ESPHome

1 Like

Thanks @Rasclatt!

Will try it out.

Thank you very much for your work! Looks awesome on the first look and was exactly what I was looking for. As a beginner with HA and ESPHome your documentation helps a lot!

Can anyone point me to the right direction how i can integrate the moisture slider in the dashboard ?
I do not see the entities for every “sensor” with platform: homeassistant.
EDIT: I kinda understood it, it is a concept for sending Data from the HomeAssistant to ESPHome Device, so to get the code running one need to create appropriate helper and then add these on the Dashboard.

There are examples of the dashboard configs on the GitHub page

Example dashboard configs
It’s easier to just copy the yaml code rather than use the gui

Sorry for the late answer.
You mean this one?

type: entities
entities:
  - entity: sensor.soil_moisture
    icon: mdi:waves-arrow-up
  - entity: sensor.pump1_last_watered
  - entity: input_number.desired_moisture1
  - entity: input_number.desired_sleeptime1
  - entity: input_boolean.prevent_deep_sleep
title: AZDelivery esp32
1 Like

Hi @Grieche,
Thanks for the nice post !

I converted a WD-01ADE dual pump watering controller. It is equipped with 2 pumps.
Removed the old chip and installed an ESP32. Also wired 2 moisture sensors to ADC inputs.

I tried your code and works flawlessly for a single pump.

Any ideas on how to adapt it for 2 pumps ? I am getting confused with sleep and if 2 sensors can evaluate conditions in parallel.

Thanks !

Hi,
i think if you just copy the code from the adc pin:

sensor:
  - platform: adc # airdry: 2,8V; waterwet: 1,58V
    pin: 34
    name: "soil Moisture"
    id: moisturesensor
    attenuation: auto
    unit_of_measurement: "%"
    accuracy_decimals: 0
    update_interval: 5s
    filters:
    - median:   #median over the last 7 values
        window_size: 7
        send_every: 4
        send_first_at: 1
    - calibrate_linear: #setting the volatge values to equalent percantages
        - 2.80 -> 0.00
        - 1.58 -> 100.00
    # limit the value to 0 and 100%
    - lambda: |-
        if (x < 0) return 0; 
        else if (x > 100) return 100;
        else return (x);
    on_value:
      then:
        - if:
            condition:  #check that the threshold value from HA UI is imported
              sensor.in_range:
                id: moisture_set
                below: 100.0
            then:
              - if:
                  condition:  #compare value from sensor with threshold value, it is more complicated that "on_range" bcs it also triggers now when the value didnt changed
                    lambda: 'return id(moisturesensor).state < id(moisture_set).state;'
                  then:
                  - deep_sleep.prevent: deep_sleep1
                  - switch.turn_on: pump1
                  - delay: 10s
                  - switch.turn_off: pump1
                  - sensor.template.publish: #sending Timestamp of pumping to HA UI                                                                                                                                 
                      id: pump1_last_update                                                                                                                                                   
                      state: !lambda 'return id(homeassistant_time).now().timestamp;'
                  - if:
                      condition: #prevent esp32 from entering deep_sleep for updating purpose
                        binary_sensor.is_on: preventdeepsleep_set
                      then:
                      - deep_sleep.prevent: deep_sleep1
                      else:
                      - deep_sleep.enter:
                          id: deep_sleep1
                          sleep_duration: 2min
                  else:
                  - switch.turn_on: LED2
                  - delay: 15s
                  - switch.turn_off: LED2
                  - if:
                      condition: #prevent esp32 from entering deep_sleep for updating purpose
                        binary_sensor.is_on: preventdeepsleep_set
                      then:
                      - deep_sleep.prevent: deep_sleep1
                      else:
                      - deep_sleep.enter:
                          id: deep_sleep1
                          sleep_duration: !lambda 'return id(sleeptime_set).state*60000;' #for converting in min *60000, for converting in h *3600000

and just adapt to the input from the second Humidity Sensor and Pump it should work for the second pump.
If you want to get the Stats shown on the HA UI and control from there you need also to adapt the “moisture_set” and implement a second one.
I hope this helps.