Skip to content

Instantly share code, notes, and snippets.

@bryanbr23
Last active July 26, 2024 21:47
Show Gist options
  • Save bryanbr23/777a18140dd93b35023c1a8b34e2990e to your computer and use it in GitHub Desktop.
Save bryanbr23/777a18140dd93b35023c1a8b34e2990e to your computer and use it in GitHub Desktop.
Automated Herb Plant Watering System

Automated Herb Plant Watering System

Objective

Ok, I admit it: I generally excel at killing plants largely due to neglect. Sure, I could claim work, cooking, hobbies, etc. If I’m honest, I generally forget to water them. So, I wanted to implement an automated watering system linked to home automation. I wanted to do it on the cheap. This was also a test, so if needed a throw-away experiment. Turns out it works for few (3) herb-plants that I needed to keep alive.

Ingredients

  • D1_Mini (Wemo, ESP8266).
  • Kit link.
    • 5v Relay
    • 5v Pump
    • Capacitive Soil Moisture Sensor
  • We’ll install firmware via ESPHome plus HomeAssistant
  • Outdoor Box
  • 5V Micro-USB (leftover phone charger
  • One momentary push-button (manual over-ride)

Principles

  1. Use cheap-as possible parts
  2. Software should:
    • a) Water daily – but only if soil sensor says plant needs it
    • b) Report back to HomeAssistant and notify that things are working correctly.
    • c) Store last-triggered timings.

Gotchas to Know Before You Start – Dummie Avoidance Strategy

  1. While relays can support 5V, D1_Mini Wemos signal at 3V. So, wire all IN, VCC for 3V, and you will be happy. For other ESP32 or ESP8266 “your mileage may vary.”
  2. In ESPHome, ensure you live by either GPIO nomenclature for pin-mappings OR D1, D4, D syntax. This will save you getting lost when you think pins are mapping appropriately, but may not be. I’m the dummie who got lost. Don’t be a dummie.

Special Thanks

James Callaghan (@jcallaghan) has been a value contributor to collaborate on this project. We effectively built this together. So, huge shout-out and virtual hug. One days we will raise a beer together (that I buy).

Otto Winter(@OttoWinter) also needs to be thanked. My mind was rocked when I learned how cool ESPHome is, and the enormous capability it has. The entire community is thankful for his contribution, but I’m trying to extend my gratitude here. Truly thankful!

Hardware and Wiring

Basically, here is the simple wiring. One addition that I added was the use of a manual button to toggle on/off for manual over-rides.

Again, picture shows NodeMCU board, however I used D1_Mini which signals at 3V, while pump operates at 5V. So, ensure you are wiring IN, VCC, GND to 3V on D1_Mini (see Don’t Be a Dummie).

The Software Needed to Run The Solution

Approach

This project is strongly coupled to ESPHome (thank you again Otto) which is an add-on to HomeAssistant. If you are not familiar with both, I highly recommend you invest the time on HomeAssistant (general home automation hub) first, learn pains of YAML (“damn you strict indenting!”), and then ESPHome (firmware compilation/management).

The core of this project is to:

  • Have a ESP8266 (D1_Mini) “controller” plus sensor and pump
  • Have the “controller” use some inputs (when to pump) and store some values back in HomeAssistant (Date and Time last pumped).

Understanding that the “controller” and HomeAssistant work together is key. The software for this project is driven primary by ESPHome. We’ll get to that in a minute. On HomeAssistant side, there are several Input_Boolean’s, and Input_DateTime values that are used.

Home Assistant UI

The UI looks like this:

HomeAssistant Helpers

To achieve the UI we put in some Input_Booleans, Sensors, etc. I used “Packages” in HomeAssistant, so each of these becomes its own file. I have adopted Frenck’s technique on Packages, and separate things out in Input_Booleans folder, Input_Select, Sensors, etc. Check Frank’s GitHub config to see the approach. You may have these Input_Booleans elsewhere often found in your configuration.yaml.

Input_Booleans

zone_enabled.yaml

zone1_enabled:
  name: Automatic Watering Enabled
  icon: mdi:watering-can

zone1_switch.yaml

zone1_switch:
  name: Pump
  icon: mdi:pump

zone1_water_available.yaml

zone1_water_available:
  name: Water Available
  icon: mdi:water-off

Input_datetime esph_watering_d1_mini_triggered.yaml

esph_ watering_d1_mini_triggered:
    has_date: true
    has_time: true

zone1_watering_scheduled_time.yaml

zone1_watering_scheduled_time:
    name: Schedule Time
    icon: mdi:clock
    has_date: false
    has_time: true

Input_number zone1_pump_run_time.yaml

zone1_pump_run_time:
  name: Pump Run Time
  min: 0
  max: 15
  step: 1
  unit_of_measurement: "seconds"

zone1_moisture.yaml

zone1_moisture:
  name: Moisture Level
  min: 0
  max: 100
  step: 1
  unit_of_measurement: "%"
  mode: box

Automation

zone_1_auto_watering.yaml

alias: 'Zone 1 Watering' – zone_1_auto_watering.yaml
id: 8fe93cfc-d8f4-4242-8679-94e22fc9d0ee
trigger:
  - platform: template
    value_template: "{{ states('sensor.time') == (state_attr('input_datetime.zone1_watering_scheduled_time','timestamp') | int | timestamp_custom('%H:%M', False)) }}"

condition:

  condition: and
  conditions:

    # Only run once a day (86400 seconds in a day).
    # //TODO Consider using an input_datetime helper to avoid issues with Home Assistant restarts.
    # //TODO Move throttling time to a input_number help and allow the throttling to be changed in the UI [Done].
    # //TODO This might also need to be considered as a trigger too.
    #- condition: template
    #  value_template: "{{ (as_timestamp(states.sensor.date_time.last_changed) - (as_timestamp(state_attr('automation.zone_1_watering','last_triggered')))) > (states('fake_zone1_time_between_watering') | int * 60) }}"

    # Watering must only occur whe the moisture is below the moisture low threshold.
    - condition: template
      value_template: "{{ states('sensor.esph_austria_watering_d1_mini_soil_moisture_percent') | int < states('input_number.zone1_moisture_threshold') | int }}"

    # This automatic water enabled helper must be enabled for automatic watering to work.
    - condition: state
      entity_id: input_boolean.zone1_enabled
      state: "on"

    # The pump should not be activated if the water is low.
    - condition: state
      entity_id: input_boolean.zone1_water_available
      state: "on"

action:

  # Turn on the pump.
  - service: homeassistant.turn_on
    entity_id: switch.esph_austria_watering_d1_mini_pump

  # Add a delay to allow the pump to work based on how long we want it to run for in the UI.
  - delay:
      seconds: "{{ states('input_number.zone1_pump_run_time') | int }}"

  # Turn off the pump.
  - service: homeassistant.turn_off
    entity_id: switch.esph_austria_watering_d1_mini_pump

  # //TODO check-in notification. Send notification to say the plants have been watered and their moisture level was 20% and is now 56%. Grow biggy grow! 🌻

# //TODO consider creating a secondary automation to send a notification/alert when the water is low.

# Tests
# 1. The automation should only work when "automatic watering enabled" and "Water Available" are both on.
# 1. The automation will only run after the duration "xxxx" has passed since it last ran.
# 1. The automation should trigger when the "Moisture Level" from the plant falls below the "Moisture Level Threshold" or the time matches "Water at".
# 1. The pump should only run for the duration set via "Pump Run Time".

ESPHome

See esphome.io for samples and getting images setup. This might take some learning. Also, ESPHome-Flasher-1.2.0-windows-x64 (1)

substitutions:
  system_name: watering_d1_mini
  friendly_name: Esph Watering D1
  relay_gpio: D5  #D5=GPIO14
  #dht22_gpio: D7
  button_switch_gpio: D2
  soil_moisture_adc: A0
  
esphome:
  name: "esph_${system_name}"
  platform: ESP8266
  board: d1_mini
  
wifi:
  ssid: !secret ssid
  password: !secret ssid_password
  power_save_mode: NONE

api:

ota:

time:
  - platform: homeassistant
    id: homeassistant_time

captive_portal:

logger:
  #level: DEBUG #DEBUG, VERY_VERBOSE

text_sensor:
  - platform: version
    name: "esph_${system_name}_version"
    on_value:
      then:
        - lambda: |-
            ESP_LOGD("main", "The current version is %s", x.c_str());
  - platform: wifi_info
    ip_address:
      name: esph_${system_name}_ip
    ssid:
      name: esph_${system_name}_ssid
    bssid:
      name: esph_${system_name}_bssid


switch:
  ## relay / pump - used by ESPHome internally, not visible from HomeAssistant
  - platform: gpio
    id: relay
    restore_mode: ALWAYS_OFF
    #name: "${system_name} Relay"
    pin: 
      number: $relay_gpio 
      inverted: True
    on_turn_on:
      then:
        # Do something
    on_turn_off:
      then:
        - homeassistant.service:        
            service: input_datetime.set_datetime
            data_template:          
              entity_id: "input_datetime.esph_${system_name}_triggered" 
              datetime: !lambda return id(homeassistant_time).now().strftime("%Y-%m-%d %H:%M:%S");
              
        - homeassistant.service:
            service: notify.html5_notification
            data:
              title: Plants Watered
            data_template:
              message: Plants were watered at {{ my_variable }}.
            variables:
              my_variable: |-
                return id(homeassistant_time).now().strftime("%Y-%m-%d %H:%M:%S");

  ## Timed Pump
  - platform: template
    name: "esph_${system_name}_timed_pump"
    id: timed_pump
    icon: "mdi:siren"
    lambda: !lambda |-    
      if (id(relay).state) {
        return true;
      } else {
        return false;
      }
    turn_on_action:
    - switch.turn_on: relay
    - delay: !lambda return (id(zone1_pump_run_time).state * 1000);
    - switch.turn_off: relay
    - homeassistant.service:        
        service: input_datetime.set_datetime
        data_template:          
          entity_id: "input_datetime.esph_${system_name}_triggered" 
          datetime: !lambda return id(homeassistant_time).now().strftime("%Y-%m-%d %H:%M:%S");
  
  ## Normal Pump
  - platform: template
    name: "esph_${system_name}_pump"
    id: pump
    icon: "mdi:siren"
    lambda: !lambda |-    
      if (id(relay).state) {
        return true;
      } else {
        return false;
      }
    turn_on_action:
    - switch.turn_on: relay
    turn_off_action:
    - switch.turn_off: relay
    - homeassistant.service:        
        service: input_datetime.set_datetime
        data_template:          
          entity_id: "input_datetime.esph_${system_name}_triggered" 
          datetime: !lambda return id(homeassistant_time).now().strftime("%Y-%m-%d %H:%M:%S");

binary_sensor:
  #Breadboard Button
  - platform: gpio
    id: button_switch
    pin: 
      number: $button_switch_gpio
      mode: INPUT_PULLUP
      inverted: TRUE
    filters:
      - delayed_on_off: 200ms
    on_press:
      then:
        - switch.toggle: relay

sensor:
  - platform: homeassistant
    name: "Pump Runtime"
    entity_id: input_number.zone1_pump_run_time
    id: "zone1_pump_run_time"

  - platform: wifi_signal
    name: "esph_${system_name}_wifi_signal"
    update_interval: 300s

  - platform: uptime
    name: "esph_${system_name}_uptime"
    update_interval: 300s

  - platform: adc  #Volts will flow to Template Sensor Below
    pin: $soil_moisture_adc
    name: "esph_${system_name}_soil_v"
    id: soil_v
    update_interval: 60s
    unit_of_measurement: 'v'
    accuracy_decimals: 2
    
  - platform: template
    name: "esph_${system_name}_soil_percent"
    id: soil_percent
    unit_of_measurement: '%'
    icon: "mdi:water-percent"
    update_interval: 60s
    accuracy_decimals: 0
      # Bryan Reading: 0.44 = in water, 0.84 = dry
    lambda: |-
      const float x = id(soil_v).state;
      if (x > 0.8) {
        return 0;
      } else if (x < 0.4) {
        return 100;
      } else {
        return (0.84 - x) / (0.84-0.44) * 100.0;
      }
      ESP_LOGD("main", "We hope soil value in volts is: %d", x); 

Finally a pic:

@nicolas-t
Copy link

Hello,

Thanks for the write up! What is the name of the orange things in the soil ? Do you have a link so I can buy the same ?

Thank you

@bryanbr23
Copy link
Author

Hi,
I was intentionally being cheap on this project. The orange top is the sprinkler/watering system. I found cheap watering system on Amazon. https://www.amazon.de/gp/product/B0824YH5W7/ref=ppx_yo_dt_b_asin_title_o01_s00?ie=UTF8&psc=1

There are many watering systems, I just found something cheap for me.
Nice to see you are reading the wiki, and hope you found it informative and helpful.

I still use it today!

~Bryan

@kris86k
Copy link

kris86k commented Jun 22, 2022

Hi. Please tell me if I shut down HA server system still works?

@bryanbr23
Copy link
Author

bryanbr23 commented Jun 22, 2022 via email

@bryanbr23
Copy link
Author

bryanbr23 commented Jun 22, 2022 via email

@kris86k
Copy link

kris86k commented Jun 22, 2022

I dont have HA now but I think about this. Do u use HA on Windows? I'am toddly green in HA and ESPhome. Its hard to install your "system" on D1 and HA? I see some file but I don't know what do with it. Some tutorial?

@bryanbr23
Copy link
Author

bryanbr23 commented Jun 23, 2022 via email

@kris86k
Copy link

kris86k commented Jul 3, 2022

Back again... Tell me one thing. Is possible to add more sensor to ESP32 in ESPhome and see in HA? I wont to use one ESP32 like NodemMCU ESP-32S to thermometer, humdity and of course for your watering system in one module. Possible?
I ask because I can't find Arduino code so if I good understand ESPome use own code, yes?

@bryanbr23
Copy link
Author

bryanbr23 commented Jul 3, 2022 via email

@aviadlevy
Copy link

Thanks for this guide. It helped me with my little project 😄 (still WIP but the main functionality is done).

image

@bryanbr23
Copy link
Author

bryanbr23 commented Jan 15, 2023 via email

@kris86k
Copy link

kris86k commented Jan 16, 2023

Hi.
Tell me one more thing. Is it hard too add one or two schedule? I need too run pump more then one time per day.

@aviadlevy
Copy link

aviadlevy commented Jan 18, 2023

Hi. Tell me one more thing. Is it hard too add one or two schedule? I need too run pump more then one time per day.

@kris86k, It's quite easy with home assistant. just add as much triggers as you like

@aviadlevy
Copy link

That is wonderful! Thanks for sharing! Ping back if you have more questions. ~Bryan

@bryanbr23 , I want my irrigation system to run on batteries. For that I think I need to add "deep sleep" to you ESPHome implementation.
I don't have much experience with ESPHome yet, so before I dive into the documentation, do you have any hints or suggestion on that topic?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment