My name is Eric Weidow, and this project is a part of the Applied IoT summer course at Linnaeus University (student credentials: ew223me), Sweden. My goal with this project is to monitor and indoor plant and send a notification when the plant needs to be watered. It will be used to remind to to water my plants, as I have always been bad at taking care of them. Additionally, it gives me a great amount of expericene with IoT, microcontrollers, sensors, Python, and much more!
My implementation uses a Raspberry Pi Pico WH (henceforth called RP2) with two sensors to measure air temperature, air humidity, and the soil moisture for an indoor plant. The data is sent via Wi-Fi to Adafruit IO (AIO) using the MQTT protocol. The data is stored in the Adafruit IO account and displayed using a dashboard. A Adafruit IO Reactive Action is used to send a notification when the soil moisture is below a set threshold.
Approximate build time: 3-4 hours
The DHT11 Temperature & Humidity Sensor is a cheap but reliable sensor with a digital signal output, which requires a supply voltage VCC of 3.3-5 V. Measurement specifications are included in Table 2, below.
Datasheets recommend not sending instructions to the sensor in within one second of supplying power to it, to pass the unstable status. If, for example, Wi-Fi and a MQTT Broker is connected before taking measurements, this is not a problem.
The FC-28 Soil Moisture Sensor measures resistance between two exposed pads. The resistance is converted to a voltage between 0 and VCC, which can be measured by a microcontroller. The voltage can then be used to determine the moisture of the soil. The sensor requires an input voltage VCC of 3.3-5 V. The voltage measured by the sensor is available on two different pinouts:
AO
(Analog output) pinout can be measured to get an analog voltage with a value between 0 and VCC.DO
(Digital output) pinout can be measured to get either 0 (LOW
) or VCC (HIGH
). The sensor has a chip with a comparator and a variable resistor. By rotating the variable resistor, it can be decided at what analog voltage the DO
pinout should be set to either LOW
or HIGH
.The online user guide supplied by Electrokit on the product page, says:
"As the probe passes current through the soil, it carries ions that will damage the surface layer over time. As such the sensor should not be operated permanently. Instead it should only be powered up when a measurement is taken and then instantly shut down again."
Therefore the sensor should only ever be on for a few seconds before taking measurements.
Make sure to go through every step of this setup so you don't miss downloading or installing anything.
The circuit diagram shows how the microcontroller is connected to the sensors. All wires are male-male except for the wires between the FC-28 chip and probe, which are female-female wires and were included with the FC-28.
A voltage of VCC = 3.3 V was chosen for powering the DHT11. The power is connected to the middle leg of the DHT11 and is supplied by pin 36 (3V3(OUT)
) on the RP2. The signal pin (the left leg as seen in the circuit diagram) on the DHT11 is connected to pin 31 (GP26
) on the RP2 to take measurements. The version of the DHT11 bought from Electrokit has a built-in 10 kΩ pullup resistor, so no extra resistors are needed in the circuit.
A supply voltage of VCC = 3.3 V was chosen for the FC-28 as well. However, the 3V3(OUT)
pin is not used. As mentioned in the materials section, keeping the sensor powered on will damage it. Instead I investigated using a GPIO to supply power to the sensor:
When suppying power with the 3V3(OUT)
pin, it could be measured with a multimeter that the VCC pin on the sensor received a current of 2.9 mA. There does not appear to be any official documentation of how much current a GPIO pin is allowed to use. However, discussions in many forums suggest 16 mA to be the absolute max current from any one pin, and that the GPIO pins were designed for a current draw of at least 3 mA.
Therefore pin 32 (GP27
) was used as a digital output pin to provide the supply voltage to the FC-28. The sensor is only ever kept on for 2 seconds before each measurement. This may not be the best value to use when taking both the lifetime of the sensor and the accuracy of the measurements into account. But it seemed to produce quite stable values.
The measurement is done with pin 34 (ADC2
), which is connected to the AO
pinout on the FC-28. The ADC (Analog-Digital Converter) in the RP2 converts the 0-3.3 V voltage to a 16-bit number, between 0 and 65535. 0 corresponds to very low resistance (high moisture) and 65535 corresponds to very high resistance (low moisture). The read value is translated to a moisture percentage using the following equation:
\text{Moisture percentage} = 100 - \frac{\text{read value} \cdot 100}{65535}$$
With the code equivalent:
moisturePercent = 100 - (soil_moisture_sensor.read_u16() / 65535 * 100)
The platform chosen for this project is Adafruit IO (AIO). It is very simple to setup, and also offers the option of building dashboards. It has a free tier which allows up to 2 devices, 5 groups, 10 feeds, 5 dashboards, a data rate of 30 messages per minute, and also provides 30 days storage. This project only needs 1 group, 3 feeds, 1 dashboard and a very low data rate, which means the free tier works perfect.
To use the platform, it is required to make an account. Then a group with three feeds need to be setup: one feed for the DHT11 temperature values, one for the DHT11 humidity values and one for the FC-28 soil moisture values. Adafruit IO has great basics tutorials for Feeds to help with these steps.
The file structure is:
boot.py - # Runs on startup
main.py - # Runs when boot is completed
env.py - # Containing environment variables
env.py.example - # Example for environment variables
lib/* - # Library files
├─ __init.py__ - # Init file to allow importing from lib
├─ mqtt.py - # Library for creating an MQTTClient
└─ wifi.py - # Handling connection to WiFi
pymakr.conf - # Pymakr configuration file
boot.py
runs on startup, but does not contain any code in this project.
main.py
is where most of the code is. It contains all setup and measuring of sensors. It uses functions from lib/wifi.py
to connect to and disconnect from Wi-Fi, and functions from lib/mqtt.py
to connect to, publish to and disconnect from Adafruit IO.
The program itself is in a very long loop. However, each step is quite simple. The first step of the loop is connecting to Wi-Fi and the MQTT broker (Adafruit IO). The onboard LED blinks three times after connecting to Wi-Fi, and three times after connecting to the MQTT broker:
When the Wi-Fi connection and MQTT connection are setup, values are measured from the sensors and printed to the console:
After measuring, the values are published to Adafruit IO. The onboard LED will blink once for each successful publishing:
If we have gotten this far without an exception being thrown, we are done with the publishing! We now want the RP2 to sleep (set with MQTT_PUBLISH_INTERVAL
, default is 20 minutes). But before going to sleep, we disconnect the RP2 from Wi-Fi and Adafruit IO, and turn off the soil moisture sensor's power, using our own disconnect_all
function:
After the RP2 wakes up, it will start at the beginning of the loop, and continue publishing every MQTT_PUBLISH_INTERVAL
seconds! However, if an exception is thrown at any moment, the RP2 will sleep (set with WiFi_TRY_RECONNECT_INTERVAL
, default is 1 minute) and then the loop starts over. Because of this, a temporary loss of Wi-Fi will be solved by itself.
The only way of exiting the loop is by a KeyboardInterrupt. Then the disconnect_all
function is called once again, and then the program is stopped by breaking the loop:
The sleep times MQTT_PUBLISH_INTERVAL
and WiFi_TRY_RECONNECT_INTERVAL
, along with all other environment variables, are set in env.py
. The file has to be created by the user and should contain all variables from the file env.py.example
, but with the values changed to the corresponding usernames, password, keys, etcetera:
# WiFi configuration
WIFI_SSID = ''
WIFI_PASSWORD = ''
WIFI_TRY_RECONNECT_INTERVAL = 60 # Delay before reconnecting after a failed connection, in seconds
# Adafruit IO configuation
AIO_BROKER = 'io.adafruit.com'
AIO_PORT = 1883
AIO_USERNAME = '' # AIO username
AIO_ACCESS_KEY = '' # AIO access key (Something like: aio_lotsofnumbersandletters)
AIO_CLIENT_ID = ubinascii.hexlify(machine.unique_id())
AIO_PUBLISH_INTERVAL = 1200 # Delay between measurements, in seconds
AIO_FEED_TEMPERATURE = '' # Feed for temperature (Something like: Your_Username/f/picow.dht11-temperature)
AIO_FEED_HUMIDITY = '' # Feed for humidity (Something like: Your_Username/f/picow.dht11-humidity)
AIO_FEED_MOISTURE = '' # Feed for soil moisture (Something like: Your_Username/f/picow.fc28-moisture)
Finally we have the files in the library folder (lib/*
):
__init.py__
has to be in the lib
folder to be able to import mqtt.py
and wifi.py
from main.py
.mqtt.py
contains functions to create a MQTTClient, connect to a broker, publish a MQTT message and disconnect. It is a copy of this file and was provided by Linnaeus University from their Applied IoT GitHub repository.wifi.py
contains functions that connects to and disconnects from Wi-Fi. It is written by me, but takes inspriation from this file, once again from Linnaeus University's Applied IoT GitHub repository.In the figure below, we see what the terminal output can look like if everything works as intended!
The data is transmitted using Wi-Fi, as the plant that is monitored will be inside a room with Wi-Fi-connectivity. The MQTT protocol is used to be able to send data to Adafruit IO, and the data itself is sent every 20 minutes. The long interval between publishing information is mainly because of two reasons:
For this project, Wi-Fi works very well, even if the data rate is overkill when only sending three measurements every 20 minutes. If the power consumption would be taken into account, LoRaWAN could be used to provide a much lower power consumption. That would also make it possible to monitor plants out of range of Wi-Fi connectivity.
The data is stored in Adafruit IO every time data is received, which is every 20 minutes. With the free tier, data is stored for 30 days. This is more than enough for the current extent of this project, as 30 days covers several watering cycles for a plant. If the purpose would change in the future, for example if one would like to analyse data to predict when a plant needs to be watered, another database solution may be needed. But right now the current implementation works very well.
A dashboard in Adafruit IO is used to visualize the published data. To setup a dashboard and visualize data in Adafruit IO, I would refer to their basics tutorials for Dashboards as these explain the topic very well.
A notification is sent when the soil moisture is lower than a certain threshold. This is once again done with Adafruit IO. This time, their Reactive Actions are used to send an email. And Adafruit IO has a great tutorial for how to use Reactive Actions to set up notifications. When set up, you can receive an email like in figure 5, below!
I am very satisfied with this project. It has taught me a lot, and I believe it provides a great starting point for further development. The code works well, and it will continue trying to reconnect to Wi-Fi and Adafruit IO until it succeeds. The circuitry is also extremely simple, as it doesn't require any extra components other than the microcontroller, the sensors themselves and wires to connect them. There is still a lot of room for improvement, but as a first IoT project it works very well!
Some ideas for how to improve on this project are: