GardenPi, powered by Neptune.py is designed to manage, monitor, and control a series of sprinkler valves and a multitude of sensors for pretty much any sized irrigation / hydroponic/aquaponics project. It can be scaled (using the hardware I used) from 1 to 32 zones for water and 7 zones for power. It is built almost entirely in Python3 with a Flask web interface and relies on a lot of CSS to make the interface very fast. It is written and designed to run on the Raspberry Pi 4.
GardenPi was designed around my family's desire to get more heavily into gardening, hydroponics, and eventually aquaponics. Since we have several fish tanks and do large water changes weekly, we wanted to be able to use that nutrient-rich water for the garden instead of just dumping it down the drain. So weekly my wife would have to cart fish tank water around the garden beds to water the plants manually and then switch back to a hose. This was getting very time consuming and tedious. Because of that, the basic concept of GardenPi was born.
The gardening part would have been pretty easy, we could have used a simple irrigation controller but as you can see we needed several water sources, in our case freshwater (street water) and water from our fish tanks and standard irrigation controllers did not provide the functions we needed to manage the process of moving back and forth between water sources. So I designed a new irrigation layout and my sons and I dug up the yard and installed six separate garden irrigation zones separate from our lawn irrigation and installed valves so we could switch between street water and an IBC tank filled with fish tank water. Now I just needed the software to manage it all.
As with any project, I wanted to look more long term. Our family sat down and talked about what we really wanted to do. One was to get into much larger fish tanks ~200-500 gallons), greatly expand our garden to provide more space for year-around growing (we live in Phoenix), start to dabble into full hydroponics and eventually move into full aquaponics and eliminate street water gardening altogether, relying 100% on old fishtank & aquaponic wastewater instead. Street water would be managed by a RODI system and fed into the fish and aquaponic tanks and all plant water would come from the fish. As you can imagine this would require quite a bit of management and thinking ahead and out-of-the-box.
The system designed for our needs ended up with the following configuration:
- 27 x "Water" Zones (Expandable to 32 Zones total)
- 8 x "Power" Zones (7 user-controlled)
- 6 x "Temperature" Zones (including one for our worm farm)
- 3 x "Humidity" Zones
- 1 x Barometric Sensor
- DC Current and Voltage Sensors
- AC Current and Voltage Sensors
- 4 x Ultrasonic Water Level Detectors
- 4 x Non-Contact Liquid Level Sensors
- 7" Touchscreen for local control
In addition to these zones/sensors, the system also interacts directly with our already installed power and water monitoring systems. This system was built on EmonCMS which is part of the OpenEnergy Project. We use our EmonCMS data to gather water utilization information for GardenPi via smart water meters on our property and do the same for monitoring the AC circuit utilization for GardenPi. Other data such as outside temperature and humidity are likewise drawn from an outside source, namely a Davis Vantage PRO2 system we have installed on the property. This information is written directly to the neptune
database automatically by outside scripts.
If you do not use these readings you can set them to 0 in the database and that is all that will be displayed in the web interface.
PLEASE NOTE: This project IS NOT intended to be a "plug-and-play" installation, rather a starting point for someone that wants to use all (or part) of the repo to monitor and manage their irrigation system(s). There will be significant modifications required by the user even if they are using a clean Pi install. Things in the code such as smart water monitoring and electrical monitoring are integrated with other sensors and automation platforms that I am currently using. I will try my best to point these areas out, but if you do not use those things, major code changes will have to be made to make GardenPi work for you. If you are not comfortable using Python and making these types of changes, this project might not be for you. My goal in later versions if to have flags for this and set them at runtime and not have so much code that needs to be manually corrected, but in version 1.0.0, that code will still need to be modified.
Hopefully, this might provide some inspiration for others in regard to their garden automation projects. Enjoy!
Before I started buying the parts or writing the code for the project, I laid out an overview of what I needed the system to be able to accomplish. I started out by drawing my plans and thinking very long term. Initially, we just needed to get the irrigation and fish water systems online, but I knew the system would grow. I wanted to design the hardware from the beginning to meet all of our needs from day one. The coding could come later.
After a lot of discussion on various gardening, hydroponic, aquaponic, and aquarium forums, I ended up with this:
This diagram consists of everything that we want to do with our setup. The hardware build that I will be sharing handles all of this! The coding for much of it still has to be written, but the hardware is 100%.
The HardwareOnce I had the physical design figured out, I started buying parts. As you can see from the list above, there are a lot of them! Of course, the nice thing about this project is that someone could scale it up or down to suit their needs. In fact, my hardware configuration actually supports a total of 32 water zones even though I am only using 27 of them myself.
The core system is built on the Raspberry Pi4B with 4GB of RAM. It could have easily run on something less powerful, however, I wanted to make sure I had enough power to pretty much do anything that I wanted to do with it.
I have several other projects that I have built so I stuck with products that have worked well for me in the past. Sainsmart relays, RaspberryPi, Samsung SD cards, etc. I am sure there could be cost-saving going with less expensive products, but this will be outside in the Phoenix heat, so I tried to only use parts that I had used before or that had good reviews.
So the first thing was to connect everything up and get it talking to the Pi. I tried to stick with I2C as much as I could for most things, used Onewire for the DS18B20 temp probes, and GPIO for all of my relays and a couple of interrupts for my serial expanders.
Once I had the hardware figured out I moved on to working on my ultrasonic tank level monitors. I wanted to be able to see how much water was available in each of our tanks, along with the temperature of the water.
This required figuring out how to use the sensors (which I had not used before) and to figure out how accurate they actually were for day-to-day use. I needed to make sure they were accurate since I would be making water switching decisions based on how much (or little) water was available in my tanks.
So I rigged up a five-gallon bucket and started playing around with the code to figure out how much water was available. I initially was using a different ultrasonic sensor, but it did not have the range I needed as we were going to be using 275 - 330-gallon IBC tanks for water storage.
Here I am using a 5-gallon bucket and testing the accuracy of the sensor:
In the end, I tried several different sensors until I found one that worked just fine and worked with different sized tanks. I didn't want to have to write code for each size tank so I ended up keeping all the information about the tank in the database and when needed, GardenPi reads the tank information and uses it to determine how much water is left in the tank.
I used THIS website to help me figure out the math and then wrote my code to figure it out on the fly:
tank[tank_name].set_dis_range(dis_min, dis_max)
distance = int(tank[tank_name].getDistance() / 25.4) #Inch to mm conversion
if distance >= tank[tank_name].tank_empty_depth:
return 0
else:
return int(abs((distance * tank[tank_name].gallons_per_inch) - tank[tank_name].max_tank_volume))
Building the EnclosureOnce I had all of my hardware figured out, I had to figure out how I was going to get all of that equipment into a nice enclosure that I could mount in our garden shed. I also needed to make sure I had adequate enclosure ventilation as well since I did not want anything overheating in the hot Phoenix weather. I installed a Bosch BME280 environmental sensor in the enclosure itself. This sensor provides us with enclosure temperature, humidity, and barometric pressure. I use this information for monitoring, notifications, and for the enclosure fan control.
For the enclosure itself, I chose to go with Polycase, a company that I had used in the past. They have a large selection of enclosures with pretty much every possible option and they are priced well for the quality that you get.
The first thing I needed to do was to cut out all of the holes for the electrical outlets. I needed seven exposed 120V 3-prong outlets, a 15 amp circuit breaker, and a power switch with a removable cord. I printed out a little template that I designed and attached it to the enclosure so I could mark all the spots I needed to cut out:
After I marked everything, I tried various different ways to make the holes using a test piece of plastic. Drilling, Dremel, etc nothing seemed to work well. The enclosure was very thick and getting through it with a razor blade just did not seem doable....that was until I decided to heat that razor up to 1000°F!
Then it was just a simple matter of cutting out all the holes I needed and cleaning everything up.
In the end, it all turned out very nicely. I did end up having to epoxy my outlets in the enclosure since it too thick for the tabs to hold them in place.
Once all the electrical was completed, the next arduous task was to figure out how to lay everything out in the enclosure. I wanted to keep my 120V stuff separate (as much as possible) from my 5V stuff, so I designed it in two layers. On the bottom would be all of my outlets, transformers, power supplies, anything that required 120V power. On the top would be everything else. I had to keep n mind the hundreds of wires that would need to be in the box as well, and how to route and keep all of that clean.
So starting on my AC shelf, I tried a bunch of different layouts until I found one I was happy with and drilled and mounted all the lower parts.
And bottom wiring complete:
One I was done with the bottom, I had to do the same thing on the top where all my 5V equipment including relays, Raspberry Pi, temperature, and ultrasonic sensors.
All wired up:
Once that was complete, we needed to solder and connect all of our wiring plugs.
There are the plugs:
All done with the soldering:
Next, we went ahead and drilled out the holes necessary for all of the plugs:
and started to mount all of the plugs:
The last piece to the puzzle was mounting the 7" touchscreen to the front panel. Because of the way the touchscreen was built, this was going to be difficult. However, I had an idea to design a stainless steel or aluminum front plate to hold the screen. After doing some research I came across a company that would make custom one-off designs. They even had their own software to design the parts.
Of course, the problem was that you had to be exactly right the first time, otherwise you would be spending a lot of money to do it again.
I took the Adafuit display and measure everything very carefully. Because of how it is designed, you could not just bolt it to something. I measure where I wanted the front panel to meet the screen and designed the front panel around those measurements. I wanted the entire touchscreen visible but not the area outside the screen itself. To make matters harder, the distances from the mounting posts to where the touch screen part of the display actually started was different side-to-side. I spent many hours with my trusty calipers and the design software before committing to the purchase.
The company I chose was Front Panel Express and they had their own software called Front Panel Designer, of all things.
The software was pretty easy to use and included both 2D and 3D renderings of the product:
Once I was satisfied with my 80th remeasure, I sent the part off to be made, It was not inexpensive, but I did have different colors, and engraving, counter-sunk holes, and posts. It ended up running about $140 in total.
When it arrived in less than a week, I held my breath as I mated up the display. I was very happy that it all lined up exactly as I intended.
The end result was (IMHO) very much worth the extra time and cost. Not only does it look nice, but it gave my project that final touch that truly made it amazing!
Zone and Sensor Connection/Extension BoxesFrom the enclosure, we will connect into the zone and sensor boxes. These boxes make it easy to have the enclosure mounted in our garden shed and have all of the other boxes mounted outside where we need them. All of the connections are IP68 rated, so having those boxes outside should not be an issue.
Here we are working on first zone plug which controls our freshwater zone, our fish water zone and irrigation zones 1 - 6:
And here we are working on one of our sensor boxes. This particular box houses five temperature probes:
Here are the electrical schematics for the GardenPi system.
GardenPi is designed to automate, as much as possible, our garden irrigation system, hydroponic system (still in the works) and manage in a semi-automated fashion, our fish tank water changes with that old water being pumped into a 275-gallon IBC holding tank. When water is added to that tank, a sensor on the tank tells GardenPi that water is available and during the next scheduled irrigation run it will use that water. The end-user has the ability to toggle this capability on or off at will, or to set it to automatic. In automatic mode, the system will start and stop external pumps, open and close a variety of valves, manage the scheduling of all zones (including power zones), and provide a wide variety of notifications with very granular settings.
GardenPi is powered by a python scripted named neptune.py. The basic premise of the system is that every 60 seconds (it could be less if needed) a cronjob kicks off Neptune and does a few things. It checks to see if there are any scheduled jobs to run both irrigation and power, it checks all of the various temperature probes and updates our database with that information, it checks the level and temperature of water in our various tanks we are monitoring, checks our enclosure temperatures and starts or stops our ventilation fans as necessary, check the temperature of our Raspberry Pi CPU and more.
The core system is programmed in Python3, Flask, SQLAlchemy, and CSS. Most of the settings needed for the zones, monitoring, notifications, and other parameters are stored in a MySQL database and read when necessary. There is a system information file called system_info.py that stores some information not practical to store in the database, but over time I plan on rewriting code so that everything is in the database.
I won't get into installation and basic configuration of the code since that is already done on my Github site for the repo.
Irrigation and Power Zone ManagementIn my particular case, we have six irrigation zones and seven power zones that we can control. Each zone has the following:
- Enable / Disable the Zone
- Two Separate Job Schedules
- Individual Manual Run Capability
- Individual Notification Settings for Email, SMS, and Pushbullet (By Zone)
- Water Usage Tracking for Water Zones
Here are some of the various screens for our irrigation zones, power zones look the same:
Likewise, all of the same settings are available for all of our power zones as well:
Because all of the different zones required the same types of actions: run, stot, run job, stop job, enable, disable, I wrote a class that read all of the zone information from a database when instantiated:
class ZoneController:
def __init__(self, name, number, description, gpio, enabled, running, running_manually, mcp, notifications, sms, pb, email):
self.zone_name = name
self.zone_number = number
self.description = description
self.gpio = gpio
self.enabled = enabled
self.running = running
self.running_manually = running_manually
self.mcp = mcp
self.notifications = notifications
self.sms = sms
self.pb = pb
self.email = email
@classmethod
def read_config(cls, zone_name):
with engine.begin() as conn:
stmt = select([zones]).where(zones.c.zone_name.in_(zone_name))
return [
cls(
name=row[zones.c.zone_name],
number=row[zones.c.zone_number],
description=row[zones.c.description],
gpio=row[zones.c.gpio],
enabled=row[zones.c.enabled],
running=row[zones.c.running],
running_manually=row[zones.c.running_manually],
mcp=row[zones.c.mcp],
notifications=row[zones.c.notifications],
sms=row[zones.c.sms],
pb=row[zones.c.pb],
email=row[zones.c.email]
)
for row in conn.execute(stmt).fetchall()
]
Then it becomes a simple matter to create the rest of the zone functionality. Here is the run() function:
def run(self):
with engine.begin() as conn:
enabled = (conn.execute(select([zones.c.enabled]).where(zones.c.zone_name == self.zone_name))).scalar()
if enabled:
with engine.begin() as conn:
running = (conn.execute(select([zones.c.running]).where(zones.c.zone_name == self.zone_name))).scalar()
if running:
log.debug(f'Zone {self.zone_number} ({self.zone_name}) is already running.')
else:
self.running = True
with engine.begin() as conn:
# With this db update we are updating the individual zone db record for the zone that is running.
conn.execute(zones.update().where(zones.c.zone_name == self.zone_name).values({zones.c.running: True}))
conn.execute(zones.update().where(zones.c.zone_name == self.zone_name).values({zones.c.running_manually: True}))
# With this db update we are updating the system_wide indication that "a" or "any" zone is running.
# To access this, call use_database.zones_running_now() and it will return True or False on a systemwide
# basis.
conn.execute(zones_currently_running.update().values({zones_currently_running.c.currently_running: True}))
conn.execute(zones_currently_running.update().values({zones_currently_running.c.run_manually: True}))
conn.execute(zones_currently_running.update().values({zones_currently_running.c.run_by_job: False}))
conn.execute(zones_currently_running.update().values({zones_currently_running.c.job_id: 0}))
mcp.pinMode(self.gpio, 1)
mcp.digitalWrite(self.gpio, 1)
log.debug(f'Zone {self.zone_number} ({self.zone_name}) is now RUNNING.')
In our main script we instantiate the zones:
zones = {zone.zone_name: zone for zone in ZoneController.read_config([n for [n] in use_database.read_all_zone_names()])}
and then we can use the zone:
def run_zone(zone_name):
"""
Runs a zone. For water zones, determines the correct water source to use
and also runs that zone. Also triggers water usage function so we can
track water utilization by zone.
"""
log.debug(f'run_zone() called with {zone_name}.')
notify(zone_name, f'{zone_name} is Running', f'{zone_name} is now Running.')
if zone_name in ([n for [n] in use_database.read_all_garden_zone_names()]):
source_selected_this_job = (get_water_source()['source_to_use'])
use_database.sa_update_job_water_source(source_selected_this_job)
turn_on_water_source(source_selected_this_job)
zones[zone_name].run()
start_job_water_usage(zone_name)
elif zone_name in ([n for [n] in use_database.read_all_power_zone_names()]):
power[zone_name].run()
elif zone_name in ([n for [n] in use_database.read_all_hydroponic_zone_names()]):
sprinkler_transformer("on")
hydroponics[zone_name].run()
Water Source ControlOne of the major reasons I wrote my own code was to automate the usage of our old fish water for irrigation purposes. The design is pretty simple, we have two "water inputs" to our irrigation system. The first is connected to a pump which is connected to a 275-gallon IBC tank. This IBC tank is where we put our old fish tank water. Where there is water in the tank, a non-contact liquid level sensor notifies the system that Fish Tank water is available for use. If the water source is set to "Automatic" or "Fish Tank", then the next time the irrigation system runs it will use the fish water. If the system is in automatic and you run out of fish water, the system will automatically switch to freshwater. If not, then the system will automatically stop all jobs and disable itself until water becomes available.
Both sources available:
In the next picture, our fish water tank is empty but since the system is set to Automatic, irrigation jobs can still run:
Here we have taken the source selection out of automatic and set it to only use Freshwater:
Here we have set the source to be Fish Water and because there is no fish water available it shuts down the system:
Fish Source is Unavailable
On the main screen, it also gives us a warning that we have no available water and when you enter an irrigation zone, it tells you the same thing:
You will also notice that the "Manual Run" option is also unavailable on the above screen.
Zone Running InformationWhen a zone is running, the system provides your with visual clues to let you know what is happening and provides you with different options including the ability to "force stop" a zone job. Here we see that a zone is running:
It looks like Zone Four is running right now:
And when we enter zone four, not only do you see we have the option to stop the zone from running but at the bottom of the screen you can see water utilization information including GPM, total gallons this run (at zero since the job just started) and total gallons for all time. You lose your ability to do any schedule or notification changes when the zone is running since it could affect the currently running zone:
Once the zone stops running, the menu goes back to normal:
If you have smart water monitoring installed in your home and are using it, when zones are running you will see water utilization information like you see above, otherwise, it will just show zero. In addition to the individual zones showing this information, the system shows it for all zones:
Go to system tools from the main menu and select the "Water Stats" menu item:
Systemwide notification settings set what types of notifications are available systemwide. Depending on what you would like to use you can choose between E-Mail, SMS text messaging ($$ via Twilio), or Pushbullet.
You can enable or disable them on-demand:
And if notification is disabled at the system level, it also will not be available anywhere else in the system that utilizes notifications, in fact, it won't even show up as an option. Here SMS is disabled systemwide and it is not available as an option:
Here it has been reenabled at the system level and once again it shows up as an option:
As we previously mentioned, we track a lot of different environmental sensors. To the extent necessary, we can also provide notifications on those readings.
Here is our environmental screen:
Here we can decide what to monitor and what alert setting to use for the notifications:
When you make any changes to the notifications settings, you are given a flash "success" message letting you know that the new settings have been entered into the database.
Just like our environmental monitoring, we also monitor power utilization on both our 5V bus and our 120V circuit. We monitor voltage and currently mostly but also provide 5V bus power and DC shunt voltage readout as well thanks to our DC wattmeter installed on the system:
And again, we can set our notifications and alert settings:
The last thing available in Version 1.0.0 of GardenPi is the ability to monitor the capacity and temperature of our RODI water tank. We utilize RODI water as the base water for our fish tank water changes.
Here is how we do our RODI water now!
Once we have everything working on the hardware and software side, it was time to actually design, build, and install the plumbing to make everything actually work.
Here are our two input valves, one for fish tank water, and one for freshwater. Note the one-way valves that prevent water from flowing backward!
Here is the completed input manifold:
Freshwater enters right below the hose bib in the middle and Ts off to our "Fresh Water" valve and to our RODI valve. The hose bib to the left connects to our RODI system. The valve on the far right will be connected to our fish tank water pump.
Here it is installed along with Zones 1 & 2:
My youngest son getting ready to help mount the manifold for Zones 3 - 6:
Zones 3 - 6 installed:
Zone connection box. The cable on the left is the 9-pin connector/cable that runs back to the zone outlet on the enclosure box:
Here are a few pictures of our Garden and Tank. Both are a work in progress and as we continue to grow and expand both (currently working on a 200+ Gallon replacement tank) the code will also grow and expand to meet the needs of our garden and fish tanks.
When you build an interface, how it looks it very important. I would not have been able to have such a nice looking interface without a lot of cool icons. These icons were mostly free and came from FlatIcon.com. Check them out!
TODONeedless to say, anyone looking at this project and reading the code can tell there is a lot left to be done. I want to do so much more and clean up the code a lot more, but frankly, Version 1 has to come at some point. We are currently running our GardenPi in production and I will continue to expand its capabilities as my family and I continue to grow in our knowledge of both Gardening and programming. I enjoy getting feedback from everyone, good or bad. I have learned a lot about programming from both good and negative comments about my code :-)
ConclusionThank you for taking the time to read about our family project. From my wife building the gardens to my youngest son helping build the system enclosure, my middle son and youngest sons helping to dig up the yard to my daughters running to Home Depot even though they hate it, it has been a fun and interesting project. This is just the start of the project as we grow in our gardening and add in our hydroponics and aquaponics, the software will continue to expand and grow to meet all of the capabilities of the hardware.
Comments