jcallaghan / home-assistant-config

My Home Assistant configuration & documentation.
https://www.jcallaghan.com/
MIT License
173 stars 8 forks source link

Automatic herb watering system πŸšΏπŸŒΏπŸ‘¨β€πŸ³ #222

Open jcallaghan opened 3 years ago

jcallaghan commented 3 years ago

Document the build of an automatic watering system for my kitchen garden herbs. The plan is to upgrade my existing watering solution and integrate it with Home Assistant with a friend who wants to do the same with his outdoor herbs.

Ingredients

Related: #174

jcallaghan commented 3 years ago

This is my existing pump unit. I might consider replacing this with a pump and parts that I can source individually to make it easier to get replacement parts etc https://www.amazon.co.uk/gp/product/B07PDM4KSC/

jcallaghan commented 3 years ago

This is the UV light I brought which I will likely upgrade to something large that I can fit on the ceiling https://www.amazon.co.uk/gp/product/B07GB28WCH

jcallaghan commented 3 years ago

Solar-powered watering system example https://twitter.com/zimmergren/status/1282599157732126721

Twitter
Tobias Zimmergren on Twitter
β€œGreat start of the week. My 3yo daughter helped me build and install a solar-powered automatic irrigation system for the vertical herb-wall. Re-using rain water only. No electricity needed, only solar-driven pumps. We built a wooden box to hide the water container.”
bryanbr23 commented 3 years ago

Food for thought: https://www.splitbrain.org/blog/2017-06/10-automated_plant_watering

Automated Plant Watering [splitbrain.org]
jcallaghan commented 3 years ago

Test Helpers and Automation

As this was all tested without the ESPHome node some fake entities were created so the automation could be created and tested. All fake entities need to be replaced. It assumes an ESPHome device would provide a moisture sensor and a relay/switch for the pump.

The automation has some //TODO items but two considerations or improvements are catching when the pump last ran. This is currently checked via the last_triggeredattribute from the automation. I'm not sure if the state will persist after a reboot so worth considering leveraging a date_timehelper. The other item is the time between runs, effectively a throttle to avoid the automation running multiple times. This is currently driven by an input_numberhelper and assumes the pump runs once a day. This is an area I'd like to spend a little more time on.

image

Automation

alias: 'Zone 1 Watering'

trigger:

  # When the moisture falls below the moisture low threshold.
  - platform: template
    value_template: "{{ states('input_number.fake_zone1_moisture') | int < states('input_number.fake_zone1_moisture_threshold') | int }}"

  # When the time matches the water at helper.
  - platform: template
    value_template: "{{ states('sensor.time') == (state_attr('input_datetime.fake_zone1_water_time1','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('input_number.fake_zone1_moisture') | int < states('input_number.fake_zone1_moisture_threshold') | int }}"

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

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

action:

  # Turn on the pump.
  - service: input_boolean.turn_on
    entity_id: input_boolean.fake_zone1_switch

  # 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.fake_zone1_pump_run_time') | int }}"

  # Turn off the pump.
  - service: input_boolean.turn_off
    entity_id: input_boolean.fake_zone1_switch

  # //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".

input_boolean helpers

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

fake_zone1_switch:
  name: Pump
  icon: mdi:pump

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

input_number helpers

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

fake_zone1_moisture_threshold:
  name: Moisture Low Threshold
  min: 0
  max: 100
  step: 1
  unit_of_measurement: "%"

fake_zone1_pump_run_time:
  name: Pump Run Time
  min: 0
  max: 60
  step: 1
  unit_of_measurement: "seconds"

fake_zone1_time_between_watering:
  name: Minimum time between watering
  min: 0
  max: 1440
  step: 1
  unit_of_measurement: "minutes"
  mode: box

input_datetime helpers

fake_zone1_water_time1:
  name: Water at
  has_date: false
  has_time: true
jcallaghan commented 3 years ago

Failsafe

To avoid issues with overwatering I will replicate something I did for my air fresheners #151 and have ESPHome write to an input_datetime helper in Home Assistant. This way regardless of how the pump is triggered it will record the last time it ran. Then logic can be added to ESPHome to avoid it running too often. If Home Assistant isn't available then it won't run to avoid overwatering.

bryanbr23 commented 3 years ago

Here's input_datetime helper: esph_austria_watering_d1_mini_triggered: has_date: true has_time: true

bryanbr23 commented 3 years ago

Here is the esphome config (d1_mini):


substitutions:
  system_name: austria_watering_d1_mini
  friendly_name: Esph Austria 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
  manual_ip:
    static_ip: 172.16.254.137
    gateway: 172.16.254.1
    subnet: 255.255.255.0
  fast_connect: true

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");

#        - sensor.template.publish
#          id: input_datetime.esph_${system_name}_triggered
#          state: !lambda return id(irrigation_zone1_duration).state;

  ## 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", "Coolaboola -- we have soil value in volts : %d", x);
jcallaghan commented 3 years ago

HomeAssistant input_datetime integration documentation - https://www.home-assistant.io/integrations/input_datetime/

Home Assistant
Input Datetime
Instructions on how to integrate the Input Datetime integration into Home Assistant.
jcallaghan commented 3 years ago

HomeAssistant input_number integration documentation - https://www.home-assistant.io/integrations/input_number/

bryanbr23 commented 3 years ago

Still not happy about manual button. If button is pressed, it is not updating last watered date, so it is defeating the system. Sure moisture readings would still be there. Need to find a way to have the button action "Normal Pump" motion.

jcallaghan commented 3 years ago

https://hometechhacker.com/how-home-assistant-runs-my-irrigation-controller/

HomeTechHacker
How Home Assistant Runs My Irrigation Controller - HomeTechHacker
Here's how Home Assistant locally schedules my EtherRain irrigation controller and takes the weather forecast into consideration.
jcallaghan commented 3 years ago

https://github.com/brianhanifin/brianhanifin.com/blob/5c0c3d0b4bfb4c0d2448ff53c478ac6403c927eb/_posts/2020-02-13-diy-irrigation-controller-esphome-home-assistant.md

GitHub
brianhanifin/brianhanifin.com
My personal knowledge dump, of which you are welcome to share! - brianhanifin/brianhanifin.com
jcallaghan commented 3 years ago

https://www.wikihow.com/Grow-Plants-With-Grow-Lights https://en.wikipedia.org/wiki/Grow_light

wikiHow
How to Grow Plants With Grow Lights
Plants need light to grow and flourish. Some plants cannot survive outdoors in the winter, and if you do not have a yard, your only choice might be to grow plants indoors. Indoor plant growing can be difficult, though. Positioning your...
Grow light - Wikipedia
jcallaghan commented 3 years ago

Growlight