Adventures, Sustainability, and Technology

Building a Plant Watering Robot

Posted on

Due to some holiday travel, some big, unwieldy plants in the apartment, and a pandemic making all of humanity skittish around itself (eek!), we found ourselves brainstorming an automated way to water said plants. We eventually came to the conclusion that it would be easy enough and cheap enough to build a little waterbot for this with a Raspberry Pi.

As usual, the process began with a search for prior art. I came across several examples of people having done this, and having documented the process. My favorite was Alan Constantino's Writeup, and we ended up using a very similar design. They have great pictures, and you should check out their writeup as well. The three notable pieces of my writeup that diverge are:


The first thing I noticed was that there seemed to be two schools of thought on how to design the circuit.

We decided to go for the second option, since both of our electronics experience is a bit, ahem, stale, at this point. And the additional 12V power source was only $5. With that decision out of the way, we could now finalize our component list!



         __                           __
         | \   ---------             /  \----
         |  \__|       |-----   ----|->  |   |
5V     ----    |       |    |   |    \__/  ----
power   --     |  rPi  |    D|\ o           --   12V
source ----    | Zero  |    D| \           ----  power
        --     |       |    D|  o           --   source
         |     |       |    |   |            |
         ------|       |-----   --------------
               ---------      ^
                           5V relay

The circuit should be fairly self-explanatory from the above wiring diagram, with the exception of pin handling on the rPi. You have to do a couple things here - first, solder the 40-pin connector block to the rPi, and second, figure out which pins to use.

I gather you can use any of the GPIO pins for INPUT or OUTPUT, but some of them can also be used for more specialized tasks, as per the diagram1. All we are doing is simple OUTPUT to engage the relay, so I chose one of the pins that only does simple GPIO, and left the more specialized pins free for other things. Maybe after version one is complete I can add a soil moisture sensor!

Pins connected to the relay are as follows:


Next up came the software - both a small amount of application code, as well as some configuration management for the rPi itself, which runs Raspbian2. The software only needs to set high and low states appropriately for PIN 12, as PIN 2 (power) and PIN 34 (ground) will always be doing the same thing.

In addition to the salient snippets embedded directly into this writeup, you can find the complete code here.

Application Code

There is a good Python library for handling rPi GPIO called gpiozero., so we whipped up a quick little Python program to handle everything. The biggest source of complexity in this ~80-line program is tracking the watering times via serializing and deserializing to a file. This seemed like a good approach to manage the risk of the program dying for any reason - if it does3, whenever it restarts, it will naturally pick up where it left off by reading whatever data it last saved to file.

import os
import time
from datetime import datetime, timedelta
from pathlib import Path

from gpiozero import OutputDevice

SECONDS_TO_SLEEP = 60 * 60 # 1 hour
WATER_INTERVAL = timedelta(days=3)

class Relay(OutputDevice):
    def __init__(self, pin, active_high):
        super(Relay, self).__init__(pin, active_high)

RELAY = Relay(12, False)

def water_plant(relay=RELAY, seconds=SECONDS_TO_WATER):
    log_message("💧 Starting water")
    log_message("💧 Stopping water")

def read_water_time():
    Read an ISO-formatted datetime from a file on disk, which represents
    the datetime at which the plant was last watered. If the file either
    does not exist, or raises an exception while being parsed, return the
    minimum possible datetime value, to trigger an immediate watering, and
    subsequent call to `persist_water_time`, which should persist a valid
    ISO-formatted datetime to the file (and thus allow this function to
    traverse a happy code path the next time it's called).
    p = Path(DATA_FILENAME)
    if not p.is_file():
        return datetime.min
    text = p.read_text().replace('\n', '')
        water_time = datetime.fromisoformat(text)
        return water_time
        return datetime.min

def persist_water_time():
    Write the current datetime to a file in ISO format. This should be called
    immediately after watering the plant, to update the most recent watering
    time. It will create the file if it doesn't exist yet, or overwrite it
    if it does already exist.
    p = Path(DATA_FILENAME)

def log_message(message):
    log_line = f"{}: {message}"

def main():
    while True:
        last_water_time = read_water_time()
        if - last_water_time >= WATER_INTERVAL:

if __name__ == "__main__":

Configuration Management

First, you'll need to flash a Raspbian OS image onto a microSD card. I started by trying to use the official Raspberry Pi Imager, and Etcher, but both ended up encountering errors. I was frustrated with flashing, and SD cards - I feel like every time I deal with this, it's always a painful adventure. Then I discovered that somehow, Linux cp just magically handles all of this. You simply download the image, then copy it to the unmounted drive.

sudo cp ./raspbian.img /dev/mmcblk0

You can find the drive on your machine by running lsblk (just make sure you use the drive identifier, not the partition identifier if it has one). After that, you should be able to boot it up and go through a few housekeeping steps to set up and secure the machine. Definitely make sure to secure the machine if you plan to expose any ports to the internet. Some suggestions to do so are below, but securing Linux is outside the scope of this writeup, so you should probably search around a bit for some targeted advice on that!

Since I'm using Poetry to run the application, I installed that using their installer script. Poetry was a bit confused when I first trying running something with it, and it seemed like Python2/3 errors. My hunch was that it was expecting python to be synonymous with python3, which is frankly pretty reasonable at this point, but Raspbian's python still resolved to python2. So, I used update-alternatives to make python3 the default.

# First, create a link group for Python
sudo update-alternatives --config python

# Next set the priorities for each member
sudo update-alternatives --install /usr/bin/python python /usr/bin/python3 2
sudo update-alternatives --install /usr/bin/python python /usr/bin/python2 1

# Finally, verify the results
sudo update-alternatives --display python

# python - auto mode
#   link best version is /usr/bin/python3
#   link currently points to /usr/bin/python3
#   link python is /usr/bin/python
# /usr/bin/python2 - priority 1
# /usr/bin/python3 - priority 2

Lastly, I wanted to make sure the program automatically restarted itself in the case of an error or system reboot. For this, I turned to systemctl and made a little service unit, which lives at /etc/systemd/system/waterbot.service.

Description=Waterbot Service

ExecStart=/home/pi/.poetry/bin/poetry run waterbot


After creating this file, test it out:

# reload units
sudo systemctl daemon-reload

# start the new unit one time
sudo systemctl start waterbot.service

# check status, and see the last few lines of log output
sudo systemctl status waterbot.service

# look at all the log lines, if you need more information to debug an issue
sudo journalctl -u waterbot

# You can stop and start the unit repeatedly, if you need to iterate...

Once you're satisfied the unit works well, you can enable it with systemctl, so it will run whenever the appropriate conditions trigger (as written, it will start at system boot, and then try and always keep it running).

sudo systemctl enable waterbot.service
sudo systemctl start waterbot.service

That's about it!



In addition to the diagram I embedded in this writeup, I also found quite helpful. You can click on each pin and get more information about it.


Raspbian is Debian-based, so if you're accustomed to Debian or Ubuntu, you should feel right at home. Also, I just learned that they changed their name from Raspbian to Raspberry Pi OS, which I'm slightly bummed about. Raspbian is such a cute name.


Let's be honest, this is software... "when it does"