Plant Monitor Tutorial

This tutorial includes a summary of the original article for the Plant Monitor Kit on Nuts and Volts - check out the original article for more info! Special thanks to John Gavlik for putting this kit together and documenting it well!
If you have got a green thumb and everything grows for you - this Kit may not be for you. But if you’re like most of us who see a plant at the store that’s growing like crazy (under the worst of conditions), buy it and then bring it home just to watch it die in a few days (under the best of conditions), then this kit may be for you!
The Plant Monitor Kit takes a lot of the guesswork out of growing and maintaining an indoor plant. Two important elements for good growth are moisture and light. This Kit monitors both and alerts you to when one, or the other, is less than optimal. In the end, we can’t guarantee a thriving plant with this Kit but, at least, you’ll have a better fighting chance for growing it successfully.
No soldering required!
Materials
Hardware
- TinyScreen+ Processor
- Wireling Adapter TinyShield
- Soil Moisture Sensor Wirelings
- Buzzer Wireling
- Ambient Light Sensor
- 290 mAh Rechargeable Lithium Polymer Battery
- Wireling Cables (2 200mm Cables, and 2 100mm Cables)
- Micro USB Cable
Everything above is included in the Plant Monitor Kit!
Extras
- Plant Monitor Arduino Sketch
- Plant Monitor Assembly Manual
- Plant Monitor Enclosure (Acrylic)
- Plant Monitor Kit User Manual
Hardware Assembly
Plug in the Wirelings into the ports on the Wireling Adapter TinyShield indicated by the table below, then place the TinyScreen+ on top of the Wireling Adapter TinyShield.
Wireling Port Assignments | |
---|---|
Port 0 | Buzzer |
Port 1 | Soil Moisture |
Port 2 | Soil Moisture |
Port 3 | Ambient Light Sensor |
Upload Program
Download the project code: Plant Monitor Arduino Sketch
Open the program in the Arduino IDE and make the following Tools/ tab selections to upload the program to your TinyScreen+ (If this is your first time using the TinyScreen+, check out the TinyScreen+ Getting Started Tutorial):
- Board: "TinyScreen+"
- Build Option: "Default"
- Port:
- Mac: “/dev/cu.usbmodemXXXX (TinyScreen+)”
- Windows: “COMXX (TinyScreen+)”.
- (this number will be different for everyone, check the Port Trouble Learn Page if you are not sure which one it is)
Code
// Plant Monitor_v1.00
// copyright LearnOnLine, inc
// author: John Gavlik
// with contributions from tinycircuits.com (noted within)
// Created 12/30/2019
// Updated 02/09/2020
//
//--------------Program Description ---------
//
// The Plant Monitor program is a graphical presentation of 2 soil monitor wirelings and 1 ambient light wireling
// on the TinyScreen+ color OLED
// It also uses a buzzer to alert the user to low moisture and low light levels
// Hardware:
// TinyScreen+ (1)
// Wireling Adapter (1)
// Soil Monitor Wireling (2)
// Ambient Light Sensor Wireling (1)
// Buzzer Wireling (1)
// 1 beep - soil #1 sensor low
// 2 beeps - soil #2 sensor low
// 3 beeps - low light level
// Custom plastic enclosure (1)
// Assorted Wireling cables (4)
// USB cable (micro to A) (1)
//
//
//--------------Libraries---------------------
//
#include <Wire.h>
#include <SPI.h>
#include <TinyScreen.h>
#include <RTCZero.h>
#include <Wireling.h>
//Library must be passed the board type
//TinyScreenPlus for TinyScreen+
TinyScreen display = TinyScreen(TinyScreenPlus);
//
//--------------General variables---------------------
//--------------Global for Debugging------------------
//
byte tempByte;
//
//--------------RTC variables---------------------
//--------------Global for Debugging------------------
//
/* Create an rtc object */
RTCZero rtc;
/* Change these values to set the current initial date */
byte day = 1; //just some values that arn't displayed
byte month = 1;
byte year = 20;
/* Change these values to set the current initial time */
byte seconds; //these time values are over written by user setting time
byte minutes; //including 24hr or am/pm
byte hours; //see set_time() for details
//For set_time()
byte am_pm_24 = 0; //0 = am, 1 = pm, 2 = 24hr
//These are the x/y coordinates for time on the OLED 96x x 64y screen
const byte hours_x = 18; //OLED x for hours display
const byte hours_y = 0; //OLED y for hours display
const byte minutes_x = 34; //OLED x for minutes display
const byte minutes_y = 0; //OLED y for minutes display
const byte seconds_x = 49; //OLED x for seconds display
const byte seconds_y = 0; //OLED y for seconds display
const byte am_pm_24_x = 64; //OLED x for am/pm/24hr display
const byte am_pm_24_y = 0; //OLED y for am/pm/2rhr display
//
//--------------OLED Variables---------------------
//--------------Global for Debugging------------------
//
byte pixel; //horizontal pixel from 0 to 95
byte line; //vertical line from 0 to 63
//
//--------------Time Variables---------------------
//--------------Global for Debugging------------------
//
int loop_time; //time around the main Loop set in Setup
int loop_time_increment; //number of milliseconds for each loop - set in Setup
//
//--------------Measurement Variables---------------------
//--------------Global for Debugging------------------
//
const byte soil_1_start_x = 3; //horizontal pixel starting point for soil 1 icon
const byte soil_2_start_x = 78; //horizontal pixel starting point for soil 2 icon
byte soil_1_moisture_percent; //soil 1 moisture percent (0-99)
byte soil_2_moisture_percent; //soil 2 moisture percent (0-99)
byte soil_moisture_percent; //used in draw_soil_icon()
byte soil_moisture_percent_moving; //used in draw_soil_icon() to convert line # to soil moisture percent
byte soil_1_temp; //soil 1 temperature in degrees C
byte soil_2_temp; //soil 2 temperature in degrees C
int lux; //immediate lux value (0-60K)
int lux_ave_value; //average lux value for storage to the lux_array @ lux_ptr
int lux_ave_divisor; //count of the number of times lux is averaged
int lux_to_y_value; //conversion from lux value to y-position on plot
const byte lux_x_plot_min = 25; //min and max x/y for Lux line plots
const byte lux_x_plot_max = 72;
const byte lux_y_plot_min = 62; //this is no mistake
const byte lux_y_plot_max = 24; //this is no mistake
//
//--------------Limit Variables---------------------
//--------------Global for Debugging------------------
//
byte limit_index; //used in set_limits()
//0 = do nothing
//1 = inc/dec soil_1 limit line - reload limit_counter
//2 = inc/dec soil_2 limit_line - reload limit_counter
//3 = inc/dec max lux for plotting - reload limit_counter
byte limit_counter; //used to time set_limit() aciivity
//automatically gets out of set_limit() when limit_counter == 0
//counted down each time thru and reloaded
//for every button increment/decrement
const byte limit_counter_reload = 10*4; //10 second reload time based on 4 updates/second
const byte soil_moisture_limit_min = 20; //min and max soil_moisture_limit values for limit_lines
const byte soil_moisture_limit_max = 80;
byte soil_1_moisture_limit; //minimum to activate buzzer - set in setup() - can be changed in set_limits()
byte soil_2_moisture_limit; //minimum to activate buzzer - set in setup() - can be changed in set_limits()
int soil_1_moisture_limit_counter; //counts all occurrances of soil_1_moisture_percent below soil_1_moisture_limit - buzzer()
int soil_2_moisture_limit_counter; //counts all occurrances of soil_2_moisture_percent below soil_2_moisture_limit - buzzer()
byte soil_moisture_limit; //used in draw_soil_icon()
byte limit_line; //white line in soil icon to denote boundry between green and red
const int lux_top_plot_level_min = 10; //min top lux
const int lux_top_plot_level_max = 1000; //max top lux
int lux_top_plot_level; //level to use for top of lux plot - set in set_limits()
int total_lux_this_day; //lux_top_level_plot_level times the sum of all y_values in lux_array
//
//---------------Array Variables-----------------------
//--------------Global for Debugging------------------
//
int lux_array[48]; //light array to store 12 hours of light levels @ 15 min/sample
byte lux_array_ptr; //pointer to lux_array[]
byte lux_array_ptr_last; //last pointer value for computing average lux
const byte lux_plot_offset = 24; //add this to the lux_array_ptr for plotting update_plot_area()
const byte lux_array_ptr_min = 0; //min and max array pointers
const byte lux_array_ptr_max = 47;
byte mp_lux_array_ptr; //mp is for MakerPlot - see output_serial_data()
//
//--------------Button Variables---------------------
//--------------Global for Debugging------------------
//
//see Button_Actions for details
byte buttons_get; //value for this sample of buttons
byte buttons_last; //last button_get value
const byte buttons_none = 0; //no buttons pushed value
const byte button_set = TSButtonUpperRight; //set time
const byte button_clr = TSButtonLowerRight; //set soil moisture / lux limits and clear buzzzer
const byte button_plus = TSButtonUpperLeft; //increment time/limit value
const byte button_minus = TSButtonLowerLeft; //decrement time/limit value
byte button_set_counter; //single button counters
byte button_clr_counter;
byte button_plus_counter;
byte button_minus_counter;
const byte push_action = 0; //counter value to trigger push action
const byte hold_action = 3; //counter value to trigger hold action
//based on loop_time = 250 milliseconds
bool hold_action_flag; //0 = cleared, 1 when hold_action value from above is exceeded
//used to clear button counters when all buttons are released
//so that inadvertant "push" does not occur
byte date_time_index; //0 = display date and time
//1 = set month
//2 = set day
//3 = set year
//4 = set am/pm/24hr
//5 = set hours
//6 = set minutes
//7 = set seconds
//8 = set rtc to above and set date_time_index = 0
bool button_set_flag; //set in button_set_action() cleared in set_time()
bool button_clr_flag; //set in button_clr_action() cleared in update_buzzer()
bool button_plus_flag; //set in button_plus_action() cleared in set_time()
bool button_minus_flag; //set in button_minus_action() cleared in set_time()
//
//---------------Wireling Port and A2D pins-----------------
// from tinycircuits.com
// relative to display.setFlip(false);
#define LUX_PORT 3 //light sensor (far-right)
//the soil ports are swapped to make up for where
//they are physically
#define SOIL_1_PORT 1 //soil 1 moisture/temp monitor (top-left
#define SOIL_2_PORT 2 //soil 2 moisture/temp monitor (top- right)
#define pin (uint8_t) A0 //buzzer - uses A0 - no port as it is not addressed (far-left)
//
//---------------Light Sensor Variables-----------------
// from tinycircuits.com
//
#define TSL2572_I2CADDR 0x39 //hex address of light sensor
#define GAIN_1X 0 //gain levels
#define GAIN_8X 1
#define GAIN_16X 2
#define GAIN_120X 3
#define GAIN_DIVIDE_6 true //only use this with 1x and 8x gain settings
int gain_val = 0; //initial gain setting
float luxLevel; //lux as a float here
//
//---------------Moisture Sensor Variables-----------------
// from tinycircuits.com
//
#define MINCAPREAD 710
#define MAXCAPREAD 975
#define ANALOGREADMAX 1023
#define THERMISTORNOMINAL 10000
#define TEMPERATURENOMINAL 25
#define BCOEFFICIENT 3380
#define SERIESRESISTOR 35000
int moistureLevel;
float tempLevel;
//
//---------------Buzzer Variables-----------------
//
byte buzzer_flag; //0 = OFF, 1 = ON
const byte buzzer_start_time = 16; //24hr start time for buzzer if below minimums - set_limits()
const byte buzzer_stop_time = 17; //24hr stop time for buzzer if below minimums - set_limits()
int lux_total; //total lux count for each element in lux_array
const int lux_buzzer_min = 48*5; //lux_total must be greater than this to avoid the buzzer beeping - see update_buzzer()
//#define pin (uint8_t) A0 //buzzer - uses A0 - no port as it is not addressed (far-right)
const unsigned int soil_1_frequency = 2700;
const unsigned int soil_2_frequency = 3000;
const unsigned int lux_level_frequency = 3300;
unsigned int frequency; //frequency choice from above
const unsigned long duration = 200;//tone duration
// ------------------------------------------------------------
// Simple templated averaging class based on Running Average
// by Rob Tillaart: http://arduino.cc/playground/Main/RunningAverage
// ------------------------------------------------------------
template <const unsigned int N>
class RunningAverageFloat
{
public:
void addValue(float val)
{
_ar[_index] = val;
_index++;
if (_index == N) _index = 0;
};
void fillValue(float val)
{
for (unsigned int i = 0; i < N; i++)_ar[i] = val;
};
float getAverage()
{
float sum = 0.0;
for (unsigned int i = 0; i < N; i++)sum += _ar[i];
return sum / (float)N;
};
protected:
int _index = 0;
float _ar[N];
};
RunningAverageFloat<35> moistureAverage;
RunningAverageFloat<35> temperatureAverage;
RunningAverageFloat<35> luxAverage;
//
//---------------Setup-----------------------
//
void setup()
{
display.begin(); //OLED settings
display.setBrightness(10);
display.setFlip(false); //flip the OLED display
display.setFont(thinPixel7_10ptFontInfo);
// display.setFont(liberationSans_10ptFontInfo);
// display.setFont(liberationSans_14ptFontInfo);
// display.setFont(liberationSans_16ptFontInfo);
// display.setFont(liberationSansNarrow_8ptFontInfo);
// display.setFont(liberationSansNarrow_10ptFontInfo);
// display.setFont(liberationSansNarrow_12ptFontInfo);
// display.setFont(liberationSansNarrow_14ptFontInfo);
// display.setFont(liberationSansNarrow_16ptFontInfo);
// display.setFont(liberationSansNarrow_22ptFontInfo);
Wire.begin();
Wireling.begin(); // Enable power & select port
delay(200); // boot sensor
//fillValue on light and moisture sensors
Wireling.selectPort(LUX_PORT);
TSL2572Init(GAIN_16X);
luxAverage.fillValue(Tsl2572ReadAmbientLight());
Wireling.selectPort(SOIL_1_PORT);
moistureAverage.fillValue(readMoisture());
temperatureAverage.fillValue(readTemp());
Wireling.selectPort(SOIL_2_PORT);
moistureAverage.fillValue(readMoisture());
temperatureAverage.fillValue(readTemp());
SerialUSB.begin(9600); //set the serial USB port to 9600 baud
rtc.begin(); //initialize RTC
hours = rtc.getHours(); // Get the time
minutes = rtc.getMinutes();
seconds = rtc.getSeconds();
display.clearScreen(); //clear the entire screen
clear_lux_plot_area(); //and the lux plot area
//plus draw a white rectangle around it
clear_lux_array(); //and clear the lux array that contains data for it
lux_top_plot_level = lux_top_plot_level_min; //can be adjusted in set_lux_limit()
button_set_flag = 0; //clear the button flags
button_clr_flag = 0;
button_plus_flag = 0;
button_minus_flag = 0;
soil_1_moisture_limit = 35; //buzzer below this value - can be changed in set_limits()
soil_2_moisture_limit = 35; //buzzer below this value - can be changed in set_limits()
limit_index = 0; //initial state of set_limits() switch case
buzzer_flag = 1; //buzzer is initially ON
date_time_index = 0; //show the time
lux_array_ptr_last = 99; //so lux plot will reset immediately
mp_lux_array_ptr = lux_array_ptr_max; //mp stands for "MakerPlot"
//this ensures that mp_lux_array_ptr will start at lux_array_ptr_min
//see output_serial_data() for details
initialize_lux_plot_area(); //initialize the lux plot area
update_lux_plot_area(); //update the lux plot area
loop_time_increment = 250; //250 milliseconds per loop
loop_time = millis(); //initial loop_time before we begin the loop (next)
}
//
//---------------Loop-----------------------
//
void loop()
{
while (millis() < loop_time + loop_time_increment)
{
// //wait until loop_time_increment has elapsed before going on
}
loop_time = millis(); //re-initialize loop_time
//
// IMPORTANT!.................................................
// Keep functions below in the order as they are now
// IMPORTANT!.................................................
get_measurements(); //get soil moisture and light level values
update_plot_area(); //update plot area
update_buttons(); //update button conditions
set_time(); //set and display the time
set_limits(); //set soil moisture and light threshold limits
update_buzzer(); //update buzzer
output_serial_data(); //output serial data via USB port to MakerPlot software
/// debug(); //-----------remove----------
}
Post-Upload Setup

The four buttons on the sides of the TinyScreen+ can be used to adjust the time, soil moisture thresholds, lux range, and buzzer operation.
Setting the Time
The first thing you want to do after you turn your unit ON is to set the time. And for this you have two choices: AM/PM or 24-hour time. While the unit has a battery it’s not a dedicated battery backup for the clock, so you’ll need to do this every time you turn it ON. It’s simple so here’s what to do...
- Setting starts with choosing AM, PM or 24hr format
- Push the Top Right button and AM is seen blinking
- If you want AM then push the Top Right Button again to advance to hours
- Otherwise, push either the Top Left(+) or Bottom Left (-) button to select PM or 24 as below...
- Push the Top Right button again. The AM/PM/24 are stored, and the hours blink once a second
- Use the Top Left (+) or Bottom Left (-) buttons to adjust the hour if necessary
- Push the Top Right button again. Hours are stored and the minutes blink once a second
- Use the Top Left (+) or Bottom Left (-) buttons to adjust the minutes if necessary
- Push the Top Right button again. Minutes are stored and seconds blink once a second
- Use the Top Left (+) or Bottom Left (-) buttons to adjust the seconds if necessary
- Push the Top Right button again. The seconds value is stored
- Finally, push the Top Right button again and the time display will “flash” indicating that it’s set
- If you made a mistake, then simply repeat the procedure
Changing Soil Moisture Threshold and Lux Range
- Push the Bottom Right button anda red rectangle begins flashing in thesoil moisture #1icon
-
Use the Top Left (+) or Bottom Left (-) buttons to adjust the white line up to 80% or down to 20% or just leave it alone if you want it there.
-
Push the Bottom Right button again and a red rectangle begins flashing in the soil moisture #2 icon
-
Use the Top Left (+) or Bottom Left (-) buttons to adjust the white line up to 80% or down to 20% or just leave it alone if you want it there.
-
Push the Bottom Right button a third time and a red rectangle begins flashing around the LUX plot area–the rectangle in the middle.
- Push the Top Left(+) or Bottom Left (-) buttons to adjust the lux plot range setting (the number in the upper-left corner) to match the level of LUX you desire. The plot will adjust with every push of the + or – buttons and the lux range will change by 10 each time -- up or down -- here we set it to 10.
- Finally, push the Bottom Right button to escape this mode as shown here
- In any of the above cases you can just wait 10 seconds and the flashing red rectangles will disappear. When this happens, the mode is canceled.
Turning the Buzzer Off
The buzzer sounds between 4 PM and 5 PM, once every 10 seconds, to alert you to the following: If either one of the soil moisture levels are below minimums, i.e., below the white line, the buzzer will sound. Also, if the total LUX value for the day is below what it should be the buzzer will also sound. * One beep at 2700 Hz for soil moisture #1 * Two beeps at 3000 Hz for soil moisture #2 * Three beeps at 3300 Hz for low LUX levels
To turn the buzzer off...
- Push the Bottom Right button once
The buzzer will remain off until the following day between 4PM and 5PM and will remain permanently off if the soil or light conditions are fixed before then.
Downloads
Contact Us
If you have any questions or feedback, feel free to email us or make a post on our forum. Show us what you make by tagging @TinyCircuits on Instagram, Twitter, or Facebook so we can feature it.
Thanks for making with us!