Monitoring my plant using sensors, relayr’s cloud, resin.io and the Raspberry Pi
This is part of my Challenge to make 26 things before 2017 ends.
This post is a continuation of the Raspberry Pi powered plant monitoring system post, in which I discussed the project requirements, and briefly showed how to flash the resinOS
to a Raspberry Pi and waterproof a soil sensor.
Nothing too fancy by then.
Now I will show:
- How I actually deployed the application in
Resin.io
usingDocker
- How to connect and read data from the soil moisture sensor (over a ADC to I2C converter) and the atmospheric sensor bundle to the Raspberry Pi
- How to publish the sensor data to the relayr cloud over MQTTS
Disclaimer: I currently work at relayr GmbH as a technical manager, however for me this project was an opportunity to better understand the company’s solution stack, and to put myself in the shoes of my development team towards being a better manager.
The project
The project lives at The Kodama Guardian repository.
Kodama (木霊, 木魂 or 木魅) are spirits in Japanese folklore that inhabit trees, similar to the dryads of Greek mythology. The term is also used to denote a tree in which a kodama supposedly resides. The phenomenon known as yamabiko, when sounds make a delayed echoing effect in mountains and valleys, is sometimes attributed to this kind of spirit
The Docker machine
I just selected one python-based Docker
available distro from resin.io
registry. From the Dockerfile
the most noticeable requirements are the python-smbus
and i2c-tools
used to read data from the I2C sensors. Notice how the i2c-dev
drivers has to be loaded prior running the application.
# Base Image
FROM resin/raspberry-pi2-python
RUN apt-get update && apt-get install -yq \
python-smbus i2c-tools libraspberrypi-bin ca-certificates && \
apt-get clean && rm -rf /var/lib/apt/lists/*
# Copy requirements file first for better cache on later pushes
COPY ./requirements.txt /requirements.txt
# The resin.io python-based images has already the following installed:
# pip, python-dbus, virtualenv, setuptools
# pip install python deps from requirements.txt on the resin.io build server
RUN pip install -r /requirements.txt
# This will copy all files in our root to the working directory in the container
COPY . /usr/src/app
# Set our working directory
WORKDIR /usr/src/app
# switch on systemd init system in container
ENV INITSYSTEM on
# main.py will run when container starts up on the device
CMD modprobe i2c-dev && python src/kodama.py
Enable the I2C driver and increase the baudrate
Add the following to the /boot/config.txt
file:
dtparam=i2c_arm=on,i2c_baudrate=400000
The specific sensor classes
I modified Matt Hawkins’ BME280 example and created a python class. From Seeedstudio’s wiki page I also adapted their example and created a class for the Soil Moisture sensor (over the ADC-to-I2C converter).
The implementation is quite simple as it just returns the sensor value. At the moment of development I moved some useful checks (like validating the sensor value) to the main application itself, which is not a good idea as it makes the sensor’s classes less reusable and clutters the application with more things to check… as I’m writing this post I’m creating a new issue to change this later… see #2 and #3.
The Sensor class
The measurement class is a wrapper for all sensors. It allows to check if a reading is valid (boundary limits), check for alerts (thresholds), and return a JSON-formatted message to be published over MQTT according to relayr’s expected ontology.
{ "name":self.name, "value":self.value, "recorded":timestamp }
The application
The main application is implemented in the kodama.py
file. The most relevant parts are the relayr’s cloud parameters:
CLOUD_HOST = "cloud-mqtt.relayr.io"
CLOUD_CERT = "/usr/src/app/src/cacert.pem"
CLOUD_PORT = 8883
And the credentials and other values configured at resin.io
admin interface (and exposed as environmental variables at runtime) to avoid publishing in the open:
# Values set in resin.io ENV VARS
PERIOD_MEAS = int(os.getenv('PER_MEAS', 30000))
PERIOD_SLEEP = int(os.getenv('PER_SLEEP', 1000))
CLOUD_USER = os.getenv('RELAYR_USER')
CLOUD_PASS = os.getenv('RELAYR_PASS')
CLOUD_DEV = os.getenv('RELAYR_DEV')
CLOUD_ID = os.getenv('RESIN_DEVICE_UUID')
The application takes sensor readings every second (as default) and check for alerts, and publishes instant readings every 30 seconds (as default). Later I will modify the application to publish instead averaged values per hour, along with minimum and maximum values in the same period. At the moment I chose to publish faster to play with the sensor dashboards (to be shown in a next post).
# Run the scheduled routines
threading.Timer(PERIOD_MEAS / 1000, measurements_send).start()
# Run the main loop and check for alerts to be published
while(True):
soil = soil_hum.read_raw()
temp, atmp, humd = bme280.read_all()
MQTT_MEASUREMENT_MAP['measurements']['temp'].is_valid(temp)
MQTT_MEASUREMENT_MAP['measurements']['humd'].is_valid(humd)
MQTT_MEASUREMENT_MAP['measurements']['atmp'].is_valid(atmp)
MQTT_MEASUREMENT_MAP['measurements']['soil'].is_valid(soil)
print "Soil {0}% @ {1}°C {2}%RH {3}hPa".format(soil, temp, humd, atmp)
# This will check for alerts to be sent to the cloud
check_alerts()
# Wait a bit
time.sleep(PERIOD_SLEEP / 1000)
The MQTT_MEASUREMENT_MAP
is a dictionary which contains the Sensor
objects and keeps track of the published Message ID
.
MQTT_MEASUREMENT_MAP = {
'measurement_mid' : 0,
'measurements' : {
'soil' : soil_moist,
'temp' : temperature,
'humd' : humidity,
'atmp' : pressure
}
}
Likewise, the MQTT_ALERTS_MAPS
is a dictionary which contains the alerts
to be sent immediately, including the ones specific to the sensors (thresholds), as well as custom defined ones. The block below summarizes its construction.
# This is the dictionary to keep ongoing alerts (other than sensor's)
my_alerts = {
'alerts' :
{
# Sensor failure
'sensor_failure' : 'clear',
# No water in the tank
'no_water' : 'clear',
# Flood likely!
'valve_loose' : 'clear',
}
}
# Copy the alerts dictionary into this map to keep track of alerts state changes
MQTT_ALERTS_MAP = {
'alerts_mid' : 0,
}
MQTT_ALERTS_MAP.update(deepcopy(my_alerts))
# Add sensor specific alerts
for key, value in MQTT_MEASUREMENT_MAP['measurements'].iteritems():
if value.low_thr_msg is not None:
MQTT_ALERTS_MAP['alerts'][value.low_thr_msg] = value.alerts[value.low_thr_msg]
if value.hi_thr_msg is not None:
MQTT_ALERTS_MAP['alerts'][value.hi_thr_msg] = value.alerts[value.hi_thr_msg]
The alerts are sent only whenever there is a change in the alert status, either set
or clear
, to minimize flooding the broker. One or more alerts can be sent at once in the same message.
The MQTT_COMMAND_MAP
contains the supported remote commands received from relayr’s cloud, at the moment none are implemented. The managed_mode
is my idea to either let the application decide when to water the plant (based on the sensor readings and last watering date), or only allow the user to decide when to water the plant (either manually by activating an electro-valve over a button or via the water_on
command).
# Supported configuration values
MQTT_COMMAND_MAP = {
# Open the sprinkler
'water_on' : None,
# Enable or Disable managed mode
'managed_mode' : None
}
Here’s an example of the application running (logging via the resin.io
console):
The wiring
The Raspberry Pi used for the project had already a ribbon cable exposing the following pin-out:
I used the Raspberry Pi’s I2C and 3.3V pins to connect the atmospheric and soil moisture sensors using a prototyping PCB I had laying around. Later I will also route two unused GPIOs to connect the electrovalve (to water the plant) and a flow sensor (to detect leaks). Here’s the small PCB (back):
And front:
In the previous post the sensor references and datasheets are shown, in case you are wondering about the pin-out.
I keep my plant beside my office’s desk at the moment. As shown in the photo I’m currently using a bucket to host my plant as I prefer to water the bonsai using a fine sprinkler for a nice shower.
Next plans
No wonder, the next phase is to actually water the plant.
I have been playing with the following idea: put the plant on top of a water tank with two sections, one to collect all the water leftover from the watering (and even use it as a fish tank), and the other to store clean water. A submerged pump in the clean water tank would pump the water to a small irrigation hose (around the trunk of the plant) and an elevated sprinkler (to shower from above). Here’s a sketch:
I tested a bilge pump (over 12V), and it has way enough power to supply water to my two watering systems. It can be easily controlled even by a relay over a RPI’s GPIO.