Introduction
Over the past several years, the Internet of Things (IOT) has gained increasing attention from developers and consumers alike. You can now control almost everything in your home from your AC to your lights with a simple click of an app on your phone. In this article we make a plant monitor using the Intel® Edison board that autonomously waters a plant when the moisture level is too low and gives you the option to water on your command. Now you never have to worry about forgetting to water your plants, and your plants will be watered even when you’re out of town. You can follow this project on Twitter*: Intel Edison @PlantMonitor. We use the Arduino* IDE to write our sketch program to control the Intel Edison board. The data and other variables are sent to ThingSpeak* for data visualizations and tweeting. Links to all the necessary external libraries can be found at the end of this article. For more information on the Intel Edison board and Arduino IDE downloads: https://www-ssl.intel.com/content/www/us/en/do-it-yourself/edison.html.
Contents:
- Moisture Sensor
- Light Sensor
- LED Pixel Matrix
- Peristaltic Pump
- ThingSpeak
- Sketch Setup()
- Sketch Loop()
Figure 1: Plant Monitor
1.Moisture Sensor
The main component of our plant monitor is the moisture sensor; we use the Sunkee* soil hygrometer detection module. It measures the resistance between the two prongs in the soil to determine moisture content. We record the max and min of the sensor in the setup() to map the plant value to a percentage in the main loop(); below they are currently set to initial dummy values. The max value is the reading of air when the resistance is the greatest and the min value is when the sensor is completely submerged in water when resistance is the least. The value comes over as an analog input and we power it when needed using one of the digital pins to reduce the effects of electrolysis. We also set a percentage threshold for the plant to determine if it is overwatered, under-watered, or just right, and from those values trigger watering the plant when needed.
// Analog pin which we're monitoring const int soilSensorPin = A0; const int soilSensorPower = 7; // soil variables: int soil=0; int soilSensorValue = 0; // the sensor value int soilSensorMin = 1000; // min/wet sensor value int soilSensorMax = 0; // max/dry sensor value int overWaterThreshold=75; int underWaterThreshold=50;
Code Example 1: Globals for the moiture sensor**
2.Light Sensor
As an added feature we also record the light level of the plant. Maybe the sunny spot it’s in only lasts a few hours but it needs sun all day long, so you know to move it to another spot. The Adafruit* TSL2561 high dynamic range digital light sensor is a sophisticated sensor that provides a much more accurate read than a photocell. It uses an I2C clock and an I2C data pin to communicate with the board. It requires two external libraries, the Adafruit Sensor library and the Adafruit TSL2561 library. You can learn more about it here: https://learn.adafruit.com/tsl2561/use
//for light and 8x8 bicolor LED #include <Wire.h> //for light sensor #include <Adafruit_Sensor.h> #include <Adafruit_TSL2561_U.h> Adafruit_TSL2561_Unified tsl = Adafruit_TSL2561_Unified(TSL2561_ADDR_FLOAT, 12345); void configureSensor(void){ tsl.enableAutoGain(true); /* Auto-gain ... switches automatically between 1x and 16x */ /* Changing the integration time gives you better sensor resolution (402ms = 16-bit data) */ tsl.setIntegrationTime(TSL2561_INTEGRATIONTIME_13MS); /* fast but low resolution */ }
Code Example 2: Globals for the light sensor**
3.LED Pixel Matrix
As a visual indicator of the plant status, we added an 8x8 Adafruit BiColor LED square pixel matrix with an I2C backpack to the plant monitor. It requires the same I2C pins as the light sensor, which is fine as I2C can communicate with multiple devices on the same bus. We can easily print words to the matrix, but to print our own shapes, we must define a binary 8x8 matrix so it knows which LEDs to turn on and off. When we write the shape to the matrix we also choose the color to indicate the status being bad or good. Below we defined a custom shape for the number ten as the default text version looks oddly small compared to numbers one through nine, a Wi-Fi signal to indicate the board cannot connect to ThingSpeak, a leaf for the plant status, and a question mark for when the data cannot be parsed from ThingSpeak. You can find more information about the LED matrix and the two external required libraries: https://learn.adafruit.com/adafruit-led-backpack/bi-color-8x8-matrix
//for light and 8x8 bicolor LED #include <Wire.h> //for 8x8 bicolor LED #include "Adafruit_LEDBackpack.h" #include "Adafruit_GFX.h" Adafruit_BicolorMatrix matrix = Adafruit_BicolorMatrix(); static const uint8_t PROGMEM ten_bmp[] = { B01001111, B11001001, B01001001, B01001001, B01001001, B01001001, B11101111, B00000000 } , wifi_bmp[] = { B11100001, B01000001, B01000101, B01000101, B01010101, B01010101, B01010101, B00000000 } , leaf_bmp[] = { B00010000, B00101000, B01000100, B01001010, B01001010, B00101010, B00011100, B00001000 } , question_bmp[] = { B00111000, B01000100, B01000100, B00001000, B00010000, B00010000, B00000000, B00010000 } ;
Code Example 3: Globals for the LED Matrix**
4.Peristaltic Pump
Also from Adafruit, a 12V peristaltic liquid pump with silicone tubing is used to water the plant when the moisture level gets too low. The advantage of a peristaltic pump is that it won’t be damaged if there is no more liquid to draw from the reservoir. Adding an eTape* Liquid Level Sensor to the water reservoir itself to alert the user if that water level is low would be a good addition to this project. The pump requires its own separate 12V power supply, and as the board can only put out a max of 5V, the power supply is controlled by a relay switch that is in turn controlled by a pin on the board.
// Pin to drive the relay attached to the pump; high is off, low is on int pump=2;
Code Example 4: Globals for the pump**
Figure 2: Diagram of the plant monitor
5.ThingSpeak
It wouldn’t be an IOT device without the Internet! So we will use a website that supports real time data collection and visualizations. For this project we use ThingSpeak which is currently offering its services for free and also tweets your project’s status (https://thingspeak.com/ ), but many other providers are available to choose from as well.
Below are the global variables that ThingSpeak needs, the most important being the thingSpeakChannel number and the writeAPIKey that will allow you to post to your feed. We also use the ArduinoJson Parser library to parse the ThingSpeak reads.
//for ThingSpeak #include <WiFi.h> #include <JsonParser.h> // ThingSpeak Settings WiFiClient clientWifi; char thingSpeakAddress[] = "api.thingspeak.com"; String thingSpeakChannel = "24066"; int TSF_moisture_sensor; int TSF_light_sensor; int TSF_raw_moisture_data; int TSF_raw_moisture_wettest_data; //min int TSF_raw_moisture_driest_data; //max int TSF_use_server_moisture_calibration; int TSF_manual_water; String writeAPIKey = "1KPP2Y48IZMDPISB"; // ThingSpeak Variable Setup int failedCounter = 0; int useServerMoistureValues= 0; int initialSetup =0; String twitterTweet= "";
Code Example 5: Globals for ThingSpeak**
Below is the method to read from your specified ThingSpeak feed. To fetch the data from your channel make sure that “Make Public?” is checked in your Channel Settings on the ThingSpeak website. If we have any issues connecting to the service, we change the LED to a red Wi-Fi signal for the user to see.
void subscribeToThingSpeak(String tsChannel){ if (clientWifi.connect(thingSpeakAddress, 80)){ failedCounter = 0; clientWifi.println("GET /channels/"+tsChannel+"/feeds/last"); clientWifi.println(); } else{ failedCounter++; Serial.println("Read Connection to ThingSpeak Failed ("+String(failedCounter, DEC)+")"); Serial.println(); matrix.clear(); matrix.drawBitmap(0, 0, wifi_bmp, 8, 8, LED_RED); matrix.writeDisplay(); } }
Code Example 6: Update method for ThingSpeak **
To update ThingSpeak, we send our string of data for it to parse and add to our graphs as well as our tweet. The composition of the data string will be discussed later on. To update Twitter make sure that the desired Twitter account is linked into ThingTweet in the apps section of ThingSpeak. If there are any issues connecting, we again set the LED matrix to the red Wi-Fi signal.
void updateThingSpeak(String tsData) { if (clientWifi.connect(thingSpeakAddress, 80)){ clientWifi.print("POST /update HTTP/1.1\n"); clientWifi.print("Host: api.thingspeak.com\n"); clientWifi.print("Connection: close\n"); clientWifi.print("X-THINGSPEAKAPIKEY: "+writeAPIKey+"\n"); clientWifi.print("Content-Type: application/x-www-form-urlencoded\n"); clientWifi.print("Content-Length: "); clientWifi.print(tsData.length()); clientWifi.print("\n\n"); clientWifi.print(tsData); if (clientWifi.connected()){ Serial.println("Updated ThingSpeak"); Serial.println(); failedCounter = 0; } else{ failedCounter++; Serial.println("Update connection to ThingSpeak failed ("+String(failedCounter, DEC)+")"); matrix.clear(); matrix.drawBitmap(0, 0, wifi_bmp, 8, 8, LED_RED); matrix.writeDisplay(); Serial.println(); } } else{ failedCounter++; Serial.println("Updating ThingSpeak Failed ("+String(failedCounter, DEC)+")"); Serial.println(); matrix.clear(); matrix.drawBitmap(0, 0, wifi_bmp, 8, 8, LED_RED); matrix.writeDisplay(); } }
Code Example 7: Read method for ThingSpeak**
ThingSpeak then graphs each field of your data in a separate chart. In the image below you can see the moisture percentage slowly decreasing and the raw moisture data slowly increasing as resistance increases between the probes. We can also see that the light in the room came on at around 6:30am that day.
Figure 3: Screenshot of ThingSpeak
6.Sketch Setup()
The first main part of the Arduino sketch is the setup loop that runs once on start up. It is here that we setup everything we need for our moisture sensor. First is the setup of the pump pin and then turning it off in the code to make sure the pump is not pumping water.
void setup() { Serial.begin(9600); pinMode(pump, OUTPUT); //turn the relay off digitalWrite(pump, HIGH);
Code Example 8: setup() method and pump initialization**
Then we initialize the LED matrix with the address that it is using.
//init the LED matrix matrix.begin(0x70); // pass in the address
Code Example 9: setup() method and LED initialization**
Next we set up the calibration of our plant monitor. The raw data that comes over from the moisture sensor has no meaning without a baseline high and low to compare it to. Below we show a green leaf on start-up and then print out “Calibrate Me!” and count down from ten for the user to calibrate the moisture sensor. The best way to calibrate it is to transition the sensor from being in air to submerged in a cup of water during those ten seconds and then stick it into the plant when done. If the values the user wants to use are already in ThingSpeak, then those are taken into account during the loop() part of the sketch.
Serial.println("Starting plant monitor..."); Serial.println(); matrix.clear(); matrix.drawBitmap(0, 0, leaf_bmp, 8, 8, LED_GREEN); matrix.writeDisplay(); delay(3000); Serial.println("Soil moisture sensor"); // add a calibration setup // calibrate during the first ten seconds Serial.println("Please calibrate your moisture sensor in the next 10 seconds"); matrix.setTextWrap(false); // we dont want text to wrap so it scrolls nicely matrix.setTextSize(1); matrix.setTextColor(LED_GREEN); for (int8_t x=7; x>=-94; x--) { matrix.clear(); matrix.setCursor(x,0); matrix.print("Calibrate Me!"); matrix.writeDisplay(); delay(150); } int offset=1; for(int i=10; i>-1;i--){ soilSensorValue = analogRead(soilSensorPin); // record the maximum sensor value if (soilSensorValue > soilSensorMax) { soilSensorMax = soilSensorValue; } // record the minimum sensor value if (soilSensorValue < soilSensorMin) { soilSensorMin = soilSensorValue; } matrix.setTextWrap(true); matrix.setTextSize(1); matrix.setTextColor(LED_GREEN); matrix.clear(); if(i<10){ matrix.setCursor(0,0); matrix.print(i); } else{ switch(i){ case 10: //the standard 10 looks odd, so we created our own matrix.drawBitmap(0, 0, ten_bmp, 8, 8, LED_GREEN); break; } } matrix.writeDisplay(); delay(1000); } matrix.clear();
Code Example 10: setup() method and moisture sensor calibration**
And finally we configure our light sensor set-up and delay an additional amount of time to ensure the user has enough time to place the probe in the soil.
/* Initialise the light sensor */ if(!tsl.begin()) { /* There was a problem detecting the ADXL345 ... check your connections */ Serial.print("Ooops, no TSL2561 detected ... Check your wiring or I2C ADDR!"); while(1); } /* Setup the sensor gain and integration time */ configureSensor(); } //in case the sensor is still in the water for calibration delay(3000); }
Code Example 11: setup() method and light sensor initialization**
Tip
If you encounter a sketch transfer incomplete error with a message of ‘Retry 0: Got ZCAN’, it is because your logs have filled it up with entries, leaving no more space in memory. You can either expand the memory of your board with an sdcard, or login as root over a serial connection and run ‘rm –rf /var/log/journal/*’ to delete all the logs.
7.Sketch Loop()
The loop is the part of the sketch that repeats over and over, collecting data and then submitting it to ThingSpeak. It begins by reading the data from ThingSpeak using an HTTP GET call that returns a string of data in JSON format. To parse this data into local variables to see if any of the user command variables have been set, we use the ArduinoJson library to load it into a hashTable from which we can easily obtain the individual values as strings. If there is a parse failure, we assume the first failure is due to the fact that there is no data there yet and proceed forward with the sketch to upload values. If the failures persist, then we wait a while giving the issue time to clear up and then restart the loop from the beginning.
void loop() { if (clientWifi.connected()){ clientWifi.stop(); } if(!clientWifi.connected()){ subscribeToThingSpeak(thingSpeakChannel); } String response= ""; char charIn; if (clientWifi.available() > 0){ do { charIn = clientWifi.read(); // read a char from the buffer response += charIn; // append that char to the string response } while (clientWifi.available() > 0); Serial.print("Reading ThingSpeak: "); Serial.println(response); Serial.println(); int len= response.length()+1; char myString[len]; response.toCharArray(myString,len); JsonParser<32> parser; JsonHashTable hashTable = parser.parseHashTable(myString); if (!hashTable.success()){ Serial.println("JsonParser.parseHashTable() failed"); initialSetup++; if(initialSetup>1){ matrix.clear(); matrix.drawBitmap(0, 0, question_bmp, 8, 8, LED_RED); matrix.writeDisplay(); delay(30000); return; } } else{ TSF_moisture_sensor = atoi(hashTable.getString("field1")); TSF_light_sensor = atoi(hashTable.getString("field2")); TSF_raw_moisture_data = atoi(hashTable.getString("field3")); TSF_raw_moisture_wettest_data = atoi(hashTable.getString("field4")); TSF_raw_moisture_driest_data= atoi(hashTable.getString("field5")); TSF_use_server_moisture_calibration = atoi(hashTable.getString("field6")); TSF_manual_water = atoi(hashTable.getString("field7"));
Code Example 12: loop() method and ThingSpeak data fetching**
If the user wants to input their own calibration values, then we check the TSF_use_server_moisture_calibration variable and set our local max and min to use the server values instead. Additionally, so the user doesn’t have to re-calibrate the sensor every time and instead keep the probe in the soil continuously, we check that the difference between the max and min is not great enough to indicate a calibration was done and proceed to use the server values. To input data into ThingSpeak outside of the sketch requires just a simple HTTP call to the ThingSpeak website using your writeAPIKey and the field numbers and values. For example, to input new calibration values and use them would be: https://api.thingspeak.com/update?key=1KPP2Y48IZMDPISB&field4=479&field5=1015&field6=1. One thing to note is that any value not included in this call will be changed to null, hence the sketch check for values that exactly equal one or are not null to prevent any errors from null data entry.
if((TSF_use_server_moisture_calibration==1) || ((soilSensorMax-soilSensorMin)<300)){ if(TSF_raw_moisture_wettest_data !=NULL && TSF_raw_moisture_driest_data !=NULL){ soilSensorMin= TSF_raw_moisture_wettest_data; soilSensorMax= TSF_raw_moisture_driest_data; } } } }
Code Example 13: loop() method and moisture sensor values set-up**
After we have our finalized max and min values we can get the soil reading by setting the pin HIGH to power the sensor before turning it off again and then mapping the value to an easily readable percentage. Based on the global threshold values, the sketch reports the status of the plant with an appropriately colored leaf on the LED matrix, compose the tweet string, and if necessary turn on the pump to water the plant.
digitalWrite(soilSensorPower, HIGH); delay(2000); soilSensorValue = analogRead(soilSensorPin); digitalWrite(soilSensorPower, LOW); soilSensorValue = constrain(soilSensorValue, soilSensorMin, soilSensorMax); //map the value to a percentage soil = map(soilSensorValue, soilSensorMin, soilSensorMax, 100, 0); if (soil>=overWaterThreshold){ Serial.println("Water Level Too High"); matrix.clear(); matrix.drawBitmap(0, 0, leaf_bmp, 8, 8, LED_YELLOW); matrix.writeDisplay(); // Update Twitter via ThingTweet twitterTweet="&twitter=PlantMonitor&tweet=Plant water level is too high: "+ String(soil) + "%25."; } else{ if (soil <overWaterThreshold && soil>underWaterThreshold){ Serial.println("Water Level Good"); matrix.clear(); matrix.drawBitmap(0, 0, leaf_bmp, 8, 8, LED_GREEN); matrix.writeDisplay(); twitterTweet="&twitter=PlantMonitor&tweet=Plant water level is good: "+ String(soil) + "%25."; } else{ Serial.println("Water Level Bad"); matrix.clear(); matrix.drawBitmap(0, 0, leaf_bmp, 8, 8, LED_RED); matrix.writeDisplay(); twitterTweet="Plant water level is too low: "+ String(soil) + "%25. Watering now. "; digitalWrite(pump, LOW); delay(30000); // set how long you want the motor to run... 1000 = aprox 1ml digitalWrite(pump, HIGH); } }
Code Example 14: loop() method and moisture level settings**
If a user wants to remotely trigger watering of their plant, they can do so by setting the TSF_manual_water variable to one (https://api.thingspeak.com/update?key=1KPP2Y48IZMDPISB&field7=1).
if(TSF_manual_water==1){ digitalWrite(pump, LOW); delay(10000); // set how long you want the motor to run... 1000 = aprox 1ml digitalWrite(pump, HIGH); TSF_manual_water=0; }
Code Example 15: loop() method and manual watering**
Next we collect the light measurement.
/* Get a new light sensor event */ sensors_event_t event; tsl.getEvent(&event); /* Display the results (light is measured in lux) */ if (!event.light) { /* If event.light = 0 lux the sensor is probably saturated and no reliable data could be generated! */ Serial.println("Sensor overload"); }
Code Example 16: loop() method and light sensor data collection**
And then it is time to submit all the data to ThingSpeak and tweet our plant’s status. The data string has every numbered field with its value and ends with our twitter handle and our tweet string.
// Disconnect from ThingSpeak if (clientWifi.connected()){ clientWifi.stop(); } // Update ThingSpeak if(!clientWifi.connected()){ updateThingSpeak("field1="+String(soil) + "&field2=" + String((int)event.light) + "&field3=" + String(soilSensorValue) + "&field4=" + String(soilSensorMin) + "&field5=" + String(soilSensorMax) + "&field6=" + String(useServerMoistureValues) + "&field7=" + String(TSF_manual_water)+ "&twitter=PlantMonitor&tweet="+twitterTweet); }
Code Example 17: loop() method and ThingSpeak data submission**
At the very end of the loop we put a delay of one hour. The moisture level of the plant doesn’t need to be checked often, and the delay gives the water more time to disperse and settle between watering’s.
delay(3600000); }
Code Example 18: loop() method and delay**
Figure 4: Screenshot of Twitter
Summary
Now we have built our Edison plant monitor to water our plant for us. You can experiment with your own threshold levels and setting how much to water your plant. I used a small Nephthytis house plant for this project so your plants needs may be different.
References and Library Links
https://github.com/bblanchon/ArduinoJson
https://github.com/iobridge/ThingSpeak-Arduino-Examples
https://github.com/adafruit/Adafruit_TSL2561
https://github.com/adafruit/Adafruit-GFX-Library
https://github.com/adafruit/Adafruit-LED-Backpack-Library
https://github.com/adafruit/Adafruit_Sensor
About the Author
Whitney Foster is a software engineer at Intel in the Software Solutions Group working on scale enabling projects for Android applications and IOT.
Notices
No license (express or implied, by estoppel or otherwise) to any intellectual property rights is granted by this document.
Intel disclaims all express and implied warranties, including without limitation, the implied warranties of merchantability, fitness for a particular purpose, and non-infringement, as well as any warranty arising from course of performance, course of dealing, or usage in trade.
This document contains information on products, services and/or processes in development. All information provided here is subject to change without notice. Contact your Intel representative to obtain the latest forecast, schedule, specifications and roadmaps.
The products and services described may contain defects or errors known as errata which may cause deviations from published specifications. Current characterized errata are available on request.
Copies of documents which have an order number and are referenced in this document may be obtained by calling 1-800-548-4725 or by visiting www.intel.com/design/literature.htm.
Intel and the Intel logo are trademarks of Intel Corporation in the U.S. and/or other countries.
*Other names and brands may be claimed as the property of others
**This sample source code is released under the Intel Sample Source Code License AgreementLike SubscribeAdd new commentFlag as spam .
© 2015 Intel Corporation.Flag as inappropriate Flag as Outdated