Having plants at home is a luxury that only households that always count with someone's presence can enjoy. If you live alone and travel for a few days, you are likely to return home and find a mumified version of your beloved basil plant.
One may argue: "Why not buy a timer on Home Depot for 5 bucks?". That's an excellent point, but why not make it overly complicated and learn a couple of things during the process?
In this article I will show you how I created an IoT application that controls the irrigation of the plants in my house.
This application independently irrigates 5 pots: 1 lemon tree, 2 passion fruit plants, 1 gardenia and 1 pot with herbs (basil, fennel and oregano). Because I'm planting these plants on the balcony of my apartment, I don't have running water, so I'm using a 40 liters tank. This tank has an ultrasonic sensor that monitors the volume and once the water level is below 30%, my Losant app sends me an email telling me to fill the tank. Well, to be honest, it sends an email to my wife, I hate filling that tank.
Besides that, the system gets the current temperature and humidity here in Houston, TX and based on that increases the volume of irrigation water to compensate for the extra heat. I realized that I needed to implement this feature once I was done with the hardware, so I'm pulling the temp info from a free weather API. It's pretty cool that Losant allows you to integrate info from so many different sources.
The general system diagram is shown below:
The electronics are very simple, these are the components of this project:
The basic idea is the following:
Similarly, the pump (underwater aquarium pump) is controlled by the Photon which drives AC current through its motor.
Lets put everything together in a nice, presentable box.
Remember, if you use a metallic enclosure like I did, you need to ground the box. I can't stress this enough!
I got the cheapest solenoid valves I could get from AliExpress.com, I think I paid 3 bucks for each. This is not bad at all, in the US they go for 10 bucks each. I also got some valves, tubing and an aquarium manifold. I've put everything together in a fancy plastic box. Note that the electrical connection from this wet module to the electronics in done by a multi-core cable and a connector. The final result is pretty neat.
The Particle Photon, like Arduino, uses C++. It has a some conveniences like Particle.function() and Particle.publish(). These features allow the user to trigger functions from external events and publish local events respectively.
In order to trigger functions remotely you'll need to declare them in the void setup. In my system I have 7 Particle functions, they are:
These Particle functions only accept one argument and it must be a String.
Conversaly, you can publish events with Particle.publish(). Please check out my code below and next we will see how to create an interface to interact with the microcontroller.
// ports to control and read hardware
const int V1 = 0;
const int V2 = 1;
const int V3 = 2;
const int V4 = 3;
const int V5 = 4;
const int pump = 5;
const int trigger = 6;
const int echo = 7;
// tank volume measurement
int volume;
// temperature alarm
bool hot = FALSE;
// manual mode
bool manual_mode = FALSE;
void setup() {
// declaring particle functions
Particle.function("tooHot", tooHot);
Particle.function("mode", mode);
Particle.function("pf1", pf1);
Particle.function("lemon", lemon);
Particle.function("herbs", herbs);
Particle.function("gardenia", gardenia);
Particle.function("pf2", pf2);
// defining i/o's
pinMode(trigger, OUTPUT);
pinMode(echo, INPUT);
pinMode(pump, OUTPUT);
pinMode(V1, OUTPUT);
pinMode(V2, OUTPUT);
pinMode(V3, OUTPUT);
pinMode(V4, OUTPUT);
pinMode(V5, OUTPUT);
// setting time zone
Time.zone(-5);
// initiallizing actuators
digitalWrite(V1, HIGH);
digitalWrite(V2, HIGH);
digitalWrite(V3, HIGH);
digitalWrite(V4, HIGH);
digitalWrite(V5, HIGH);
digitalWrite(pump, HIGH);
Serial.begin(9600);
}
void loop() {
// selects the u.FL antenna
STARTUP(WiFi.selectAntenna(ANT_EXTERNAL));
if (manual_mode == FALSE){
// morning irrigation schedule
if(Time.hour() == 7 && Time.minute() == 00){
auto_irrigation();
}
// if it's warm, the system water the plants again at 3pm
if (hot == TRUE){
if(Time.hour() == 15 && Time.minute() == 00){
auto_irrigation();
}
}
}
// measure water tank level twice a day
if ((Time.hour() == 8 || Time.hour() == 16) && Time.minute() == 00){
volume = measure_level();
Particle.publish("tank_volume", (String)volume);
}
}
/* FUNCTIONS */
// irrigation
int auto_irrigation(){
digitalWrite(V1, LOW);
digitalWrite(pump, LOW);
delay(80000);
digitalWrite(V1, HIGH);
digitalWrite(V2, LOW);
delay(50000);
digitalWrite(V2, HIGH);
digitalWrite(V3, LOW);
delay(80000);
digitalWrite(V3, HIGH);
digitalWrite(V4, LOW);
delay(40000);
digitalWrite(V4, HIGH);
digitalWrite(V5, LOW);
delay(40000);
digitalWrite(V5, HIGH);
digitalWrite(pump, HIGH);
}
// measure the tank level, averages 100 measurements
int measure_level(){
int result;
long duration = 0;
for (int i = 0; i <= 100; i++)
{
digitalWrite(trigger, LOW);
delayMicroseconds(2);
digitalWrite(trigger, HIGH);
delayMicroseconds(10);
digitalWrite(trigger, LOW);
duration = duration + (pulseIn(echo, HIGH)/100);
delay(50);
}
result = 100*(25.4-duration*0.017)/25.4;
return result;
}
/* this function changes the boolean value of the variable hot and its logic is executed on Losant.
the weather api provides the temp and if greater than 30C, it will trigger toohot. */
int tooHot(String command){
if (command == "yes"){
hot = TRUE;
return 1;
}
else if (command == "no"){
hot = FALSE;
return 0;
}
else {
return -1;
}
}
// this function changes the logic value of manual_mode and it's triggered on losant.
int mode(String command){
if (command == "true"){
manual_mode = TRUE;
return 1;
}
else if (command == "false"){
manual_mode = FALSE;
return 0;
}
else {
return -1;
}
}
// passion fruit 1 manual irrigation
int pf1(String command){
if (manual_mode == TRUE){
if (command == "true"){
digitalWrite(V1, LOW);
digitalWrite(pump, LOW);
}
else if (command == "false"){
digitalWrite(V1, HIGH);
digitalWrite(pump, HIGH);
}
}
}
// lemon tree manual irrigation
int lemon(String command){
if (manual_mode == TRUE){
if (command == "true"){
digitalWrite(V2, LOW);
digitalWrite(pump, LOW);
}
else if (command == "false"){
digitalWrite(V2, HIGH);
digitalWrite(pump, HIGH);
}
}
}
// herbs manual irrigation
int herbs(String command){
if (manual_mode == TRUE){
if (command == "true"){
digitalWrite(V3, LOW);
digitalWrite(pump, LOW);
}
else if (command == "false"){
digitalWrite(V3, HIGH);
digitalWrite(pump, HIGH);
}
}
}
// gardenia manual irrigation
int gardenia(String command){
if (manual_mode == TRUE){
if (command == "true"){
digitalWrite(V4, LOW);
digitalWrite(pump, LOW);
}
else if (command == "false"){
digitalWrite(V4, HIGH);
digitalWrite(pump, HIGH);
}
}
}
// passion fruit 2 manual irrigation
int pf2(String command){
if (manual_mode == TRUE){
if (command == "true"){
digitalWrite(V5, LOW);
digitalWrite(pump, LOW);
}
else if (command == "false"){
digitalWrite(V5, HIGH);
digitalWrite(pump, HIGH);
}
}
}
Receive an email notification
Losant is an IoT platform that allows the developper to create a workflow logic that will receive and trigger events. It's beyond the scope of this article to provide a detailed Losant tutorial, however I'll explain the intuition behind it and give you references in case you decide to develop your own IoT app. Let's start checking how to implement an email notification to alert the user that the water level is getting low.
First you'll need to create a device and a particle integration in Losant. Upon doing that, go to your workflow and build the following block logic.
Let's examinate how to set the nodes to implement this logic.
1. Particle: This node establishes communication with your device. Click on it and select the integration you have created.
2. Function: Remember Particle.publish()? We will set up this node to listen for events published from the Photon. In order to do that, we need to define the structure of the payload with some javascript. The event published by the microcontroller will be transmitted in a json package, more precisely inside data. In our case, the variable being passed is 'volume', so we can retrieve this data by accessing 'data.volume'.
Click on the function node and add the following code.
var parts = payload.data.data.split(':');
payload.data.volume = parts[0];
3. Device Get: This node will attribude the data from you payload to a device and the attributed you've defined upon its creation. This will allow us to create a dashboard later.
Select the query method to: 'Match all tags query'
Set 'Key Templates' to 'particle_device_id' and 'Value Templates' to '{{ data.coreid }}'
Set 'Result Path' to 'device_to_update'
4. Device State: Stores the state in a device attribute.
'Device ID JSON path' = 'device_to_update.id'
'Attribute' = '{{data.volume}}'
5. Debug: This block allows you to debug and understand what is going on with you payload. You can trigger events from the workflow and see how your payload is being constructed. This block is not necessary to make the app work.
6. Conditional: Executes a conditional logic. Remember you'll need to refer to the variable according to the way that your JSON payload was defined, in our case {{data.volume}}.
7. Email: This one is pretty self explanatory. Upon the activation of the condition previously defined, this block will send an email.
To summarize, we will retrieve the variable 'volume' by setting up a logic to listen to the events being published from the IoT microcontroller. Upon getting this data from a JSON package we will compare it to a threshold and if the value is less than 30% of the total volume, an email will be sent asking the user to re-fill the tank.
Get local temperature from an API
First you'll need an API key, go to
https://openweathermap.org/api
and create an account. You can use their free plan for this project. If I'm not wrong, the free plan includes 1000 api calls a day. If you're not in the agricultural business, this should be more than enough.Build the following workflow:
The http block is responsible from calling the API, let's check on how to set it up.
In the 'URL template', add the following:
http://api.openweathermap.org/data/2.5/weather?q='YOUR CITY'&appid='YOUR API KEY'
The 'request method' should be set to GET and the API response should be stored in the 'Payload path to store response' field.
Set it to 'working.weather'.
Set the timer to call the API every hour so you don't exceed the daily limit of calls.
Now your payload should look like this:
Now the temperature should be accessible through:
{{working.weather.body.main.temp}}
Triggering Functions Remotely
Now it's time to finally trigger our Particle functions previously defined in our firmware.
Let's start by building a workflow:
Basically we have a 'Virtual Button' node triggering a 'Particle Call' node for each one of our remotely operated functions.
In the 'Particle Call' node, we need to:
Now, we need to create an interface to trigger this workflow we've just created. Go to Dashboard and add an input control. Add toggles and buttons like so:
The toggle state is sent via payload to the microcontroller upon triggering the workflow via the button. Set the button like so (note, the value between the double brackets should be the same of your toggle, my toggle is also named 'lemon').
Do the same for all the other inputs and test it:
Data visualization
Add some charts and link then to your devices attributes.
I hope you guys enjoyed this article. This project has several moving parts and I know that I have covered some of them too fast without providing the level of detail that the reader deserves, for that reason do not hesitate to contact me if you'd like to get more info or simply ask a question.
Moving forward I'd like to add a strain gauge to monitor the weight of the plant. Over 98% of the mass of plants comes from the air (interesting fact isn't it?). That would be an interesting step into starting optimizing fertilization and irrigation.
If you are interested in learning more about Losant, check this tutorial out: https://www.losant.com/blog/how-to-integrate-particle-with-losant
See y'all soon!