The goal: create a system that can sense when a houseplant or small garden needs water, then water it.
This system uses a particle photon, the Sparkfun soil moisture sensor, a light level sensor and a electronically controlled valve to keep the soil moisture above a specified level. The solenoid valve has standard garden hose threads on it. It also is designed to work with gravity feed systems, so that this system can operate from a rain barrel instead of working from the plumbing of the house.
The power cord used in this project provides 12 V and 600mA. The photon can only run on 3.3-5V so I used a voltage regulator to step down the 12 V to 5V. It's necessary to have 12 V available because that's what's required to actuate the solenoid. The electrical circuit used to control the solenoid is shown in the image below.
At first glance this might seem overly complex. I'm not an electrical engineer so there may be a simpler way to use a 3.3V signal to control a 12V device, I just don't know what it is. This circuit works because when a high voltage (3.3V) is written to the D0 pin it allows current to flow through the transistor. This results in a voltage across the diode which energizes the coil in the relay. This closes the circuit through the solenoid and actuates the solenoid. The end result is that when your code applies a voltage at D0, water will flow through the valve.
Part 2: SensingThis system has 2 sensors. The first is a light level sensor. Because of the specifics of your plant/garden it might be undesirable to water at night or at the peak of the day. So in order to prevent this, I included a photo resistor in order to sense the light level. It is wired in series with a normal resistor to create a voltage divider and the voltage reading sensed from analog pin 0 (A0) is taken to represent the light level. Note that resistance in the photo resistor is inversely proportional to the light intensity (more light = less resistance). In order to create a voltage reading that was directly related to the light level (more light = more voltage) I wired the photoresistor before the normal resistor in the light-sensor circuit.
The second sensor is the soil moisture sensor. It's simply two legs of a board, plated in gold that are stuck into the soil. As the soil moisture changes, the resistance of the sensor changes and the voltage at analog pin 2 (A2) changes.
Here's what it all looks like soldered to the board.
The underside of the board is a mess of solder and wiring so I didn't include a picture.
Part 3: Code// This #include statement was automatically added by the Particle IDE.
#include "Ubidots/Ubidots.h"
#include <math.h>
//Specify pins
int voutPin = D1;//pin to supply power to sensors
int soilPin = A2;//pin to read soil moisture level
int lightLevelPin = A0;//pin to read light level
int relayPowerPin = D0;//pin to control the power to the valve relay
//Create variables
int soilMoisture = 0;//variable to store soil moisture level
int lightLevel = 0;//variable to store light level reading
int errorSignal = 0;//variable to store error data
//error signal meanings:
// 0 - everything ok, no errors reported
// 1 - watering timed out before the moisture rose above the specified level
// 2 - did not water because the light sensor was too low
int soilMoistureLow = 200;//any reading below this triggers the watering algoritm
int soilMoistureHigh= 1000;//once the watering begins, it waters until the sensor reading goes above this value
int timLimit = 300;//how long (in seconds) to wait until water timeout occurs
int lightLevelLimit = 1000;//1000;//threshold level to specify night lighting conditions
#define TOKEN "COPY AND PASTE YOUR TOKEN IN HERE!"
Ubidots ubidots(TOKEN);
void setup() {
Particle.connect(); //connect the particle to the internets
Particle.variable("soilMoisture",soilMoisture);//alert the particle cloud to the existence of the soil moisture variable
Particle.variable("lightLevel",lightLevel);//alert the particle cloud to the existence of the light level variable
Particle.variable("errorSignal",errorSignal);//alert the particle cloud to the existence of the error signal variable
pinMode(voutPin ,OUTPUT);//specify the sensor power supply pin as an output
pinMode(relayPowerPin ,OUTPUT);//specify the relay control pin as an output
pinMode(lightLevelPin ,INPUT);//specify the the light level reading pin as an input
pinMode(soilPin ,INPUT);//specify the soil moisture reading pin as an input
}
void loop() {
digitalWrite(voutPin,HIGH); //Turn on power to the sensors
lightLevel = averageReading(lightLevelPin,1000,5); //take an average reading from the light sensor (photoreistor)
if (Particle.connected()==FALSE){ //if the particle is not connected to the internet then connect
Particle.connect();
}
//If it's night time then don't water, if it's bright enough then water
if (lightLevel>lightLevelLimit){
soilMoisture = averageReading(soilPin,10,20);//read the soil moisture level, 10 readings separated by 10 miliseconds each
if (soilMoisture<soilMoistureLow){
errorSignal = waterTheGarden(relayPowerPin,soilMoistureHigh,timLimit);
}
}
else{
soilMoisture = 0;// if we diddn't water because it was too dark out then set soil moisture =0;
errorSignal = 2; // if we diddn't water because it was too dark out then set error signal =2;
}
publishSensorReadings(soilMoisture,lightLevel,errorSignal);
digitalWrite(voutPin,LOW);//Turn of power to sensors
System.sleep(SLEEP_MODE_DEEP,1200);//20 min deep sleep
}
//function turns on power to the relay which controls water flow
// it then waits for timLimit seconds and if the moisture reading does not rise above the specified limit
// it times out and returns an error signal
int waterTheGarden(int relayPowerPin, int soilMoistureHigh, int timLimit){
int secondCounter = 1;//variable to store how long the watering valve has been opened
digitalWrite(relayPowerPin,HIGH);//turn on power to the valve that controls water flow
delay(1000);
soilMoisture = averageReading(soilPin,10,10);
while(soilMoisture<=soilMoistureHigh && secondCounter<=timLimit){
delay(1000);
soilMoisture = averageReading(soilPin,10,10);
secondCounter++;
}
digitalWrite(relayPowerPin,LOW);//turn off power to the valve that controls water flow
if(secondCounter == timLimit){
return 1;
}
else{
return 0;
}
}
// function takes 5 readings an averages the result
// inputs are portNumber and waitTime. waitTime is the
// time in miliseconds to wait in between each reading.
int averageReading(int portNumber, int waitTime, int numOfReadings){
int reading = 0;//create variable to store sum total of reading
for(int i = 1 ; i<=numOfReadings ; i++){
reading += analogRead(portNumber);//add new reading to the cumulative sum
delay(waitTime);//wait for the specified time
}
return round(reading/numOfReadings);//calculate the average reading
}
//PUBLISH SENSOR READINGS TO THE INTERNET
void publishSensorReadings(int soilMoisture, int lightLevel, int errorSignal){
int failureCount = 0;//variable to keep track of how many attempts have been made to publish data to the internets
bool soilSuccess;//variable to keep track of if the soil moisture measurement has been published to internet or not
bool lightSuccess;//variable to kepe track of if the light level measurement has been published to the internet or not
bool errorSuccess;//variable to keep track of if the error signal has been published to the internet or not
//attempt to publish the results to the internet
soilSuccess = Particle.publish("soil_moisture",String(soilMoisture)); //publish the reading results to the particle cloud
lightSuccess = Particle.publish("light_level",String(lightLevel)); //publish the reading results to the particle cloud
errorSuccess = Particle.publish("error_signal",String(errorSignal));//publish the error readings to the particle cloud
ubidots.add("soil_moisture",soilMoisture);//publish the reading results to the ubidots cloud
ubidots.add("light_level",lightLevel);//publish the reading results to the ubidots cloud
ubidots.add("error_signal",errorSignal);//publish the error signal to the ubidots cloud
ubidots.sendAll();
//If any of the results fail to publish them, attempt to publish them 10 more times
while (soilSuccess==FALSE && failureCount<10){//ensure that the results are published, in case they diddn't go through for some reason
soilSuccess = Particle.publish("soil_moisture",String(soilMoisture));
failureCount++;
}
failureCount = 0;
while (lightSuccess==FALSE && failureCount<10){//ensure that the results are published, in case they diddn't go through for some reason
lightSuccess = Particle.publish("light_level",String(lightLevel));
failureCount++;
}
failureCount=0;
while (errorSuccess==FALSE && failureCount<10){//ensure that the results are published, in case they diddn't go through for some reason
lightSuccess = Particle.publish("error_signal",String(lightLevel));
failureCount++;
}
}
How the code works:
First, it checks to see if the light level is over the user-defined threshold. If it is, then it checks the soil moisture. If the soil moisture is below the used-defined threshold, then it waters the garden. It then publishes the sensor readings to the internet. It then goes into deep sleep mode for 20 minutes in order to save power.
There are three functions in this code. The waterTheGarden function works by opening the valve and monitoring the soil moisture level for a set time. If the soil moisture does not rise above the specified limit in that time window then the function returns an error. The averageReading function polls an analog pin multiple times and averages the value. As inputs, it takes the pin number to poll. the number of times to poll the pin and the time in between pollings. The publishSensorReadings function exists to publish the sensor readings to multiple places on the internet (Ubidots and the particle cloud). It contains code to increase robustness, ensuring that the data is published.
Part 4: Ubidots Data Interface:The website https://ubidots.com/ interfaces smoothly with the particle photon and provides a convenient way to monitor the sensors attached to the Photon. I built this interface to display the quantities I was interested in but you can build yours any way you want. The data shown here is from a period of about 3 days when the soil was extremely damp. Also, I was unplugging and reprogramming the system a lot on the 4th day so there is a lot of nonsensical data on the right.
Finally, you'l want to mount your project in the way that works best for you house and your garden. I'm still attempting to find a good weatherproof housing for the photon and a separate housing for the solenoid valve. I want to put them in separate housings to keep the water away from the electronics.
Comments