This post may contain affiliate links. Please read my disclaimer for more info.

Welcome to part 2 of my series on my DIY WiFi Sprinkler Controller using ESP8266. Last week I wrote an article on the hardware involved in the project, definitely check it out if you haven’t already. It goes in depth to all the components used. Today’s article will be looking closer at the software of the system. Including the firmware running on the device and the Home Assistant integration.

To set the stage again, the goals of the project were:

Control my sprinkler system in an automated fashion, but still turn on individual zones manually when needed

No reliance on the cloud, should work over local network

Be extendable to any number of zones relatively easy

Keep the controller simple, keep the scheduling logic on the home automation platform

Inexpensive to build, but must be reliable

You can view my Home Assistant configuration on GitHub. You can also view the Sprinkler Firmware on GitHub.

ESPHome

First off, let’s tackle the firmware running on the NodeMCU ESP8266. My design uses a 74HC595 shift register to control the 8 relays individually. You could extend this to more than 8 zones by simply daisy-chaining shift registers together to get more IOs. Again, refer back to the my DIY WiFi Sprinkler Controller using ESP8266 (Part 1) article to learn more about the shift register.

Custom Shift Register

If you’ve read some of my previous tutorials, I’ve recently been using esphome for all my DIY ESP8266 projects. Unfortunately, there is not a dedicated 74HC595 component (yet). However, we can make use of the Custom Switch Component to implement our own.

Custom Switch Class

First off, we’ll need to write a little C++ code to model each one of the shift register outputs as an esphome switch object. I suggest reading through the esphome docs on custom switches to get a baseline for what we’re trying to do here.

Essentially the below code makes use of the open source ShiftRegister74HC595 library. It creates a global object named sr for accessing the shift register pins. The code defines a new class called ShiftRegisterSwitch which implements some methods that are required to be an esphome switch. Each instance of this class resembles a single output from the shift register. So if you’re using 8 channels, you’ll need to create 8 instances of this class, one for each output pin.

#include <ShiftRegister74HC595.h> #include "esphome.h" // Number of shift registers // Data Pin // Clock Pin // Latch Pin ShiftRegister74HC595 sr (1, 5, 4, 14); using namespace esphome; // namespace is called 'switch_' because 'switch' is a reserved keyword class ShiftRegisterSwitch : public Component, public switch_::Switch { private: int index; public: ShiftRegisterSwitch(int output) { index = output; } void setup() override { sr.set(index, HIGH); } void write_state(bool state) override { if(state) { sr.set(index, HIGH); } else { sr.set(index, LOW); } // Acknowledge new state by publishing it publish_state(state); } }; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <ShiftRegister74HC595.h> #include "esphome.h" // Number of shift registers // Data Pin // Clock Pin // Latch Pin ShiftRegister74HC595 sr ( 1 , 5 , 4 , 14 ) ; using namespace esphome ; // namespace is called 'switch_' because 'switch' is a reserved keyword class ShiftRegisterSwitch : public Component , public switch_ : : Switch { private : int index ; public : ShiftRegisterSwitch ( int output ) { index = output ; } void setup ( ) override { sr . set ( index , HIGH ) ; } void write_state ( bool state ) override { if ( state ) { sr . set ( index , HIGH ) ; } else { sr . set ( index , LOW ) ; } // Acknowledge new state by publishing it publish_state ( state ) ; } } ;

The code implements two methods that must exist to work as an esphome switch:

setup() is called by esphome to setup the switch when the ESP8266 turns on. In this implementation, we use the shift register library to set the pin high.

is called by esphome to setup the switch when the ESP8266 turns on. In this implementation, we use the shift register library to set the pin high. write_state() is called when Home Assistant wants to set the state of the switch. Here the code just checks which state is being requested and uses the shift register library to set the pin’s new value.

ESPHome Configuration File

Now that we have our C++ class written, we need to integrate it into our esphome configuration file. For this project, the shift register is going to be adding switches that switch the relays for each watering zone. To use the shifter register, we’re going to include the ShiftRegister74HC595 Arduino library and the header file we wrote in the previous section. Below is a snippet from my esphome configuration file.

esphome: name: sprinkler_system platform: ESP8266 board: nodemcuv2 includes: - shift_register_switch.h libraries: - ShiftRegister74HC595@1.2 switch: - platform: custom lambda: |- std::vector<switch_::Switch *> switches; for(int i = 0; i < 8; i++) { auto zone_switch = new ShiftRegisterSwitch(i); App.register_component(zone_switch); switches.push_back(zone_switch); } return switches; switches: - name: "Sprinkler Zone 1" inverted: yes - name: "Sprinkler Zone 2" inverted: yes - name: "Sprinkler Zone 3" inverted: yes - name: "Sprinkler Zone 4" inverted: yes - name: "Sprinkler Zone 5" inverted: yes - name: "Sprinkler Zone 6" inverted: yes - name: "Sprinkler Zone 7" inverted: yes - name: "Sprinkler Zone 8" inverted: yes 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 esphome : name : sprinkler_system platform : ESP8266 board : nodemcuv2 includes : - shift _ register _ switch . h libraries : - ShiftRegister74HC595 @ 1 . 2 switch : - platform : custom lambda : |- std : :vector < switch _ : :Switch * > switches ; for ( int i = 0 ; i < 8 ; i++ ) { auto zone _ switch = new ShiftRegisterSwitch ( i ) ; App . register _ component ( zone _ switch ) ; switches . push _ back ( zone _ switch ) ; } return switches ; switches : - name : "Sprinkler Zone 1" inverted : yes - name : "Sprinkler Zone 2" inverted : yes - name : "Sprinkler Zone 3" inverted : yes - name : "Sprinkler Zone 4" inverted : yes - name : "Sprinkler Zone 5" inverted : yes - name : "Sprinkler Zone 6" inverted : yes - name : "Sprinkler Zone 7" inverted : yes - name : "Sprinkler Zone 8" inverted : yes

Let’s take a look at the interesting portions:

We need to include our custom code, so we can add includes to the esphome configuration block to add additional source files to the compilation. Here we add the shift_register_switch.h file that contains our custom class.

to the configuration block to add additional source files to the compilation. Here we add the file that contains our custom class. Our custom code uses an open source Arduino library. Using the libraries attribute we can pull in any open source libraries to use for our project.

attribute we can pull in any open source libraries to use for our project. Finally, we actually create the custom switches for esphome. The lambda configuration block allows you to write custom C/C++ code that will be inserted into your application. Not to tangent too far into a C++ tutorial, but here we create a vector for storing all our switches. Using a for loop we create each one using the custom ShiftRegisterSwitch class. The lambda function ultimately returns the vector containing all the switch objects we created. The switches attribute maps one to one between the vector returned in the lambda function and the list of switches.

Other ESPHome Features

You’ll notice I enable a few other things in my esphome configuration file. First off, api is enabled to use the Home Assistant API to communicate with Home Assistant, rather than using MQTT. The ota component is enabled so that I can push out a firmware update to the device without having to physically plug it into my computer. The status_led component is added so that the LED on the NodeMCU board blinks until it has successfully joined WiFi and Home Assistant has connected. Finally, the text_sensor component is used with the version platform to send version information to Home Assistant. This allows me to easily see what version of esphome my devices were compiled with and when the firmware was deployed.

status_led: pin: 16 # Enable logging logger: # Enable Home Assistant API api: ota: text_sensor: - platform: version name: esphome_sprinkler_system_esphome_version 1 2 3 4 5 6 7 8 9 10 11 12 13 14 status _ led : pin : 16 # Enable logging logger : # Enable Home Assistant API api : ota : text _ sensor : - platform : version name : esphome_sprinkler_system_esphome_version

Home Assistant

One of my goals for the project was to keep the controller simple, and the scheduling logic in my home automation system. I use the powerful open source project Home Assistant to run my home automation.

Groups and Customizations

First off, I wanted to group all my sprinkler switches so that they show up in the same panel on the Home Assistant UI. This can easily be done by using the Group component. Simply list all your switches in a new group so that they show up together in the UI.

--- sprinklers: name: Sprinkler System entities: - switch.sprinkler_zone_1 - switch.sprinkler_zone_2 - switch.sprinkler_zone_3 - switch.sprinkler_zone_4 - switch.sprinkler_zone_5 - switch.sprinkler_zone_6 - switch.sprinkler_zone_7 - switch.sprinkler_zone_8 1 2 3 4 5 6 7 8 9 10 11 12 13 --- sprinklers : name : Sprinkler System entities : - switch . sprinkler _ zone _ 1 - switch . sprinkler _ zone _ 2 - switch . sprinkler _ zone _ 3 - switch . sprinkler _ zone _ 4 - switch . sprinkler _ zone _ 5 - switch . sprinkler _ zone _ 6 - switch . sprinkler _ zone _ 7 - switch . sprinkler _ zone _ 8

You might also want to customize the icon associated with each switch so it looks more “sprinkler-like”. This can be done through the Home Assistant Customize menu or through your YAML configuration. I decided to put it in my YAML configuration:

"switch.sprinkler_zone_*": icon: mdi:water-pump 1 2 "switch.sprinkler_zone_*" : icon : mdi :water-pump

Once you reload Home Assistant, you should get a UI panel looking something like:

Alternatively, you can also list the icon to use in the esphome configuration file itself. I prefer to do this in Home Assistant though so that I don’t need to update the firmware on my devices if I decide to change the icon down the road.

Scripts

Next, let’s make some Home Assistant scripts for different watering scenarios. I’m going to have four different scripts, each watering a different area of my yard:

Frontyard

Sideyard

Backyard

Whole House

Each one of these will turn on specific zones for a set amount of time. The “Whole House” script goes through all the zones, while the other scripts only target a few zones. You can see my “Water Backyard” script below, which waters zones 2, 3 and 5.

--- sprinkler_water_backyard: alias: "Sprinkler Water Backyard" sequence: - service: switch.turn_off entity_id: group.sprinklers - delay: '00:01' - service: switch.turn_on entity_id: switch.sprinkler_zone_2 - delay: '00:40' - service: switch.turn_off entity_id: switch.sprinkler_zone_2 - delay: '00:01' - service: switch.turn_on entity_id: switch.sprinkler_zone_3 - delay: '00:40' - service: switch.turn_off entity_id: switch.sprinkler_zone_3 - delay: '00:01' - service: switch.turn_on entity_id: switch.sprinkler_zone_5 - delay: '00:40' - service: switch.turn_off entity_id: switch.sprinkler_zone_5 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 --- sprinkler _ water _ backyard : alias : "Sprinkler Water Backyard" sequence : - service : switch.turn_off entity _ id : group.sprinklers - delay : '00 :01' - service : switch.turn_on entity _ id : switch.sprinkler_zone_2 - delay : '00 :40' - service : switch.turn_off entity _ id : switch.sprinkler_zone_2 - delay : '00 :01' - service : switch.turn_on entity _ id : switch.sprinkler_zone_3 - delay : '00 :40' - service : switch.turn_off entity _ id : switch.sprinkler_zone_3 - delay : '00 :01' - service : switch.turn_on entity _ id : switch.sprinkler_zone_5 - delay : '00 :40' - service : switch.turn_off entity _ id : switch.sprinkler_zone_5

Once you write these scripts you’ll be able to trigger them from the UI or through an automation.

Automations

The most basic automation we want to add is running the scripts on certain days at certain times. To get that working, all we have to do is set the trigger to be a time of day and the condition to be a certain day of the week. For example, I want to water my backyard on Sunday and Thursday an hour after sunrise, so my automation would look like this:

--- alias: Water Backyard trigger: - platform: sun event: sunrise offset: "+01:00:00" condition: - condition: time weekday: - thu - sun action: - service: script.sprinkler_water_backyard 1 2 3 4 5 6 7 8 9 10 11 12 13 14 --- alias : Water Backyard trigger : - platform : sun event : sunrise offset : "+01 :00 :00" condition : - condition : time weekday : - thu - sun action : - service : script.sprinkler_water_backyard

Pretty simple right? But what if we also wanted to only run the sprinkler if it wasn’t going to rain that day? We can adapt the condition to also take into account the probability it will rain. You need to enable the dark sky sensor in your Home Assistant configuration. In particular, you need to enable the daily forecast with the precip_probability monitored condition.

platform: darksky api_key: !secret dark_sky_api_key forecast: - 0 monitored_conditions: - precip_probability 1 2 3 4 5 6 platform : darksky api_key : ! secret dark_sky_api_key forecast : - 0 monitored_conditions : - precip_probability

Then, you can check what the probability to rain is for the day to determine if the sprinkler system should run. In this example I’m only watering if there is less than a 60% chance of rain.

--- alias: Water Backyard trigger: - platform: sun event: sunrise offset: "-01:00:00" condition: - condition: time weekday: - thu - sun - condition: numeric_state entity_id: "sensor.dark_sky_precip_probability_0d" below: 60 action: - service: script.sprinkler_water_backyard 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 --- alias : Water Backyard trigger : - platform : sun event : sunrise offset : "-01 :00 :00" condition : - condition : time weekday : - thu - sun - condition : numeric_state entity _ id : "sensor.dark_sky_precip_probability_0d" below : 60 action : - service : script.sprinkler_water_backyard

This isn’t a perfect solution, but is a step up from my current dumb controller. If you’ve got some good ideas on how to improve it going forward let me know in the comments! I’d also like to get some moisture sensors to actually use data from my lawn, so if you have suggestions on those let me know in the comments as well.

Google Assistant Integration

Finally, it might be useful to be able to trigger the sprinklers using your voice control. Check out my article on Zoned Cleaning with the Xiaomi Roborock S5 Robotic Vacuum that details how to use IFTTT and Home Assistant to create custom Google Assistant phrases.

Closing Thoughts

This has been a really fun project for me and is definitely one of the more useful electronics/Home Automation projects I’ve done. All in all I’m pretty happy with the outcome and think it meets all my initial goals. In the future I’d like to add some local buttons for each zone and an LCD panel to display it’s current status. I’d love to hear in the comments about any other enhancements you think of! I’d also like to beef up my automation to only water when we really need to, using a moisture sensor or rain gauge. I really enjoyed designing and building this DIY WiFi Controller using ESP8266.

If you’re interested in some other ESP8266 projects I’ve done, check out the following articles:

If you’ve found this series helpful, please consider supporting the blog by joining my mailing list, following the blog on social media or directly through Buy Me a Coffee. Thanks for reading!