Introduction: Automatic Plant Rotator With Sunlight Sensor

This is a writeup of a project I did for my Introduction to Making class. I was inspired by my wife who wanted a device that automatically rotates her plant so that each section gets an even distribution of sunlight.

Supplies

Step 1: Connect BYJ48 Motor With Raspberry Pi

I mainly followed this tutorial to get the motor working with my Raspberry Pi.

You can modify the 512 (360 degrees) number in the test script to change how much you want the motor to rotate by.

for i in range(512):

Modify the following line to change the speed of rotation.

time.sleep(0.001)

Step 2: Attach Plate, Motor, and Foundation

I miraculously found a smart cart chassis kit that fit well with the BYJ48 stepper motor and also provided stability to the plate so I decided to combine everything.

  1. Superglued 3D printed plate with a wheel from the smart car kit
  2. Screwed in BYJ48 motor to the smart car body (now the foundation of this device)
  3. Had to apply a thin layer of scotch tape around the motor shaft so that it fit perfectly with the wheel

Before attaching everything, I would recommend placing your plant/pot on the plate and making sure that it's stable even when it rotates. My plant weighs roughly 1kg.

Step 3: Attaching Grove Sunlight Sensor

I mainly followed this tutorial to get the sunlight sensor working with my Raspberry Pi.

If you can get the test script to run, you should be set!

python3 examples/BasicRead.py

I then taped the sensor to the foundation so that the sensor is always in the same position.

Step 4: Collecting Sunlight Data

I followed this tutorial to set up InfluxDB and Grafana on my raspberry Pi.

InfluxDB is a time series database designed to handle high write and query loads
Grafana is a multi-platform open source analytics and interactive visualization web application

After getting influx running, I created a database called plant and set a retention policy so that we don't retain the data forever.

influx
> create database plant
> create retention policy "two_weeks" on "plant" duration 2w replication 1

I wrote up a quick script to save the sunlight level to InfluxDB

# collect.py

from influxdb import InfluxDBClient


def read_metric():
import seeed_si114x
SI1145 = seeed_si114x.grove_si114x()
return SI1145.ReadVisible


def save_metric(metric, value):
client = InfluxDBClient('localhost', 8086, 'pi', 'password', 'plant')
point = {
'measurement': metric,
'fields': {
'value': value
}
}
client.write_points([point])


if __name__ == '__main__':
sun_value = read_metric()
save_metric('sunlight', sun_value)

I added this script to a cron job so that it is executed every minute

* * * * * python3 /home/pi/plant-rotator/collect.py

I graphed the collected data in Graphana to establish a baseline.

Step 5: Determining Baseline and Rotation Threshold

After a couple days of data collection, I noticed that the sunlight level typically ranges from about 258~1200. I decided to ignore all values below 265 as those numbers were observed during the night and set a threshold of 50,000 accumulative delta.

In other words, I keep track of the sunlight level for one section every minute, and keep a running count of how much sunlight exposure exceeding 265 that section gets. When that aggregated number exceeds 50,000, I rotate the plant 90 degrees, reset the counter, and start tracking again for the new section.

Logic (pseudo code):

If ∑max(sunlight_level - 265)  > 50000:
rotate section = (section + 1) % 4

If the plant is getting the max sunlight level around 1200, the device should rotate every 50 minutes, but in practice, it rotates about once a day.

Step 6: Combining Everything

Based on the threshold from the previous section, I combined everything and wrote a program that automates the data collection and rotation of the plant. The program consists of a script and two text files that keep track of the the current section and the running total of sunlight exposure.

# run.py

import RPi.GPIO as GPIO
import time
from influxdb import InfluxDBClient


THRESH = 265
ROTATE_THRESH = 50000


def rotate():
GPIO.setmode(GPIO.BOARD)
control_pins = [7,11,13,15]
for pin in control_pins:
GPIO.setup(pin, GPIO.OUT)
GPIO.output(pin, 0)
halfstep_seq = [
[1,0,0,0],
[1,1,0,0],
[0,1,0,0],
[0,1,1,0],
[0,0,1,0],
[0,0,1,1],
[0,0,0,1],
[1,0,0,1]
]
for i in range(128):
for halfstep in range(8):
for pin in range(4):
GPIO.output(control_pins[pin], halfstep_seq[halfstep][pin])
time.sleep(0.002)
GPIO.cleanup()


def get_section():
with open('/home/pi/plant-rotator/section_sunlight.txt', 'r') as f:
section_value = int(f.readline())
with open('/home/pi/plant-rotator/current_section.txt', 'r') as f:
section = int(f.readline())

if section_value > ROTATE_THRESH:
section = (section + 1) % 4
with open('/home/pi/plant-rotator/current_section.txt', 'w') as f:
f.write('%s\n' % section)
section_value = 0
rotate()
return section, section_value


def read_metric():
# the rotation has to happen before this import
import seeed_si114x
SI1145 = seeed_si114x.grove_si114x()
return SI1145.ReadVisible


def save_metric(metric, value):
client = InfluxDBClient('localhost', 8086, 'pi', 'password', 'plant')
point = {
'measurement': metric,
'fields': {
'value': value
}
}
client.write_points([point])


def update_section_value(new_value):
with open('/home/pi/plant-rotator/section_sunlight.txt', 'w') as f:
f.write('%s\n' % new_value)


if __name__ == '__main__':
section, section_value = get_section()
sun_value = read_metric()
delta = max(sun_value - THRESH, 0)

save_metric('sunlight-%s' % section, sun_value)
save_metric('sunlight-delta-%s' % section, delta)

update_section_value(section_value + delta)

We replace the data collection cron job with this new script so that it runs every minute.

* * * * * python3 /home/pi/plant-rotator/run.py

That's it!

Gardening Challenge

Participated in the
Gardening Challenge