Here in the Denver area, it's not unheard of for the weather to switch from sunny and gorgeous to heavy rains and lightning for an hour or so, and then back to sunny all within smaller isolated areas. You can see this in the following pictures taken from the same spot seconds apart, where the area over me is dark and raining, but not far away it was bright, sunny and dry the entire day.
Where this causes problems for me is that I love to garden. I have planters of tomatoes, onions, carrots, lettuce, cabbage, peppers, pumpkins, spaghetti squash, zucchini and raspberries around that shouldn't be over watered, but it's impossible to know how much rain actually fell in a specific area here without physically being there when it's raining (you can take a guess by touching the soil, but I have pretty good drainage in my raised garden beds, too :)).
In addition, I have grass that I hadn't really watered over the last year, though I plan to rent out my home soon. Because I need to bring the grass back to life, I have to water, but don't want to overwater. Additionally, I grew up in the drought-prone city of Fresno, California, so I already feel awful wasting water on something like grass.
In order to work around these issues, I put together a simple device that logs the amount of rain received at the house each day, stores that information online, and then checks once a day to see if the yard should be watered before turning on the sprinklers. For good measure, I'm also logging other environmental conditions, such as wind direction, wind speed, and temperature.
Another Practical Usage
In farming areas (such as central California, where I'm from), one big issue is unexpected freezes at night. A lot of the time farmers will use a burning pot of oil, called a smudge pot, to keep crops warm. Another option is to wet down plants so that only the outer later freezes, protecting the main fruit. Using a system like the one outlined here, you could wet down your plants automatically to prevent the fruit from rotting due to cold weather.
Rachio Sprinkler ControllerOne thing to note is that I am using a Rachio smart sprinkler controller to turn on/off the sprinkler system via their API, and they do technically support intelligent weather skipping per their site: "With advanced Rain, Freeze and Wind Skips, Weather Intelligence™ automatically adjusts watering schedules for predicted weather". Where the problem lies is in using a local weather service - the nearest one to me is about 10 miles (16 km) away, but that still isn't accurate enough for the area I'm in, which is tucked right between the Rocky Mountains and the great plains north of Denver and east of Boulder. This means that my sprinklers will often turn on in the middle of the night even after the yard has been somewhat flooded by rain. You can see this in the following pictures where the app says a low percentage chance of rain while a heavy rain is falling in my area (I was actually at the grocery store at the time and closer to the official weather station), and then the next scheduled run still happens because the station didn't get any rain.
The main reason I wanted to use a Rachio is the ability to turn on the sprinkler system via an API without having to build another system to trigger some solenoids. They also allow you to create a schedule that will link multiple zones together, then run each zone for a specific amount of time with a single API call.
One thing that I did find interesting about the Rachio is that they do support the ability to create your own weather station and feed that data into other services, but those are a lot more official than I'd want to work with while figuring out this project, and they do support other rain sensors, but I wanted to collect a variety of information in addition to rain levels (because why shouldn't life be made more difficult, right?) and I also wanted to be able to use a sensor that wasn't wired to the controller, as my controller is set up in my basement, rather than an easily accessible place to the outside. I will say that I am super impressed with the Rachio, and if it weren't for the uniquely localized weather in the Denver area, I'd just use it as a standalone unit.
How Sprinkler Systems WorkBefore we dig into our new sprinkler input system, we should go over how a sprinkler system works. Starting from inside the house, there's a control unit that enables you to turn each 'zone' (smaller sections that we'll discuss later) of the sprinkler system on and off via an electrical signal.
Also inside the house is the main water line, which has a junction that leads to outside the house.
On my system, the red handle can be turned to be parallel to the line, allowing water to flow out of that junction and outside the house. This pipe leads to a small utility box outside my home that contains three valves - one for each zone. Each zone is a subset of the entire sprinkler system that is limited in size in order to maintain water pressure along that system. For my home, one zone is the front yard, and two are in the back yard. Each valve has a solenoid attached to the top that, when enabled by the controller, opens that valve and allows water to flow into the specified sprinkler zone.
As water flows into a specific zone, the pressure forces each sprinkler head to pop up and water to shoot out onto the lawn.
The system I built to work with the sprinklers consists of two major hardware parts: the smart sprinkler controller, and the weather monitor. The controller is a single Rachio device, which will be controlled from a custom Firebase backend. For the meter, I grabbed one of the Elegoo starter kits for an Arduino Uno and some general sensors since I had wanted a variety of hardware parts for various projects.
The major sensor for the monitor is a large add-on that has a rain gauge, an anemometer for measuring wind speed, and a wind vane for tracking the direction of the wind, which I picked up locally from SparkFun (they happen to be based here in Boulder, Colorado, and it's great - I'm also totally open to an IoT developer relations role if any of you read this :P).
This is connected to the Arduino Uno using two RJ-11 connectors. Additionally, the device has a DHT-11 sensor. In order to connect the device to a network, I'm using a Helium shield for Arduino so that it can be connected back to Google IoT Core, which will then lead to Firebase and other Google Cloud tools.
Before we start assembling the weather meter, we should go over how some of the components work; in particular the anemometer, rain gauge and wind vane.
The meter I'm using in this project connects the RJ-11 from the anemometer to the wind vane, as the wind vane uses pins 1 and 4, and the anemometer uses pins 2 and 3. Combining these allows us to use one RJ-11 connector to get readings from both sensors.
The way the anemometer works is that it has one switch inside of the unit that is closed as the cups spin around the pivot point. By counting the number of closes over a period of time, we can figure out how fast wind is blowing. For an example, one click per second would represent wind speeds of 1.492 miles per hour, or 2.4 kilometers per hour.
Along the same lines is the rain gauge. Every momentary contact of the rain gauge represents 0.011" of rain.
The wind vane allows you to check wind direction at any point in time, and works in a little more interesting of a way. The vane itself consists of eight reed switches with resistors in series.
You can figure out the direction of the wind by figuring out which reed switches are closed by reading the voltage output by the vane. The chart below gives you a rough idea of directions vs voltage using a 10k resistor and 5v input through a voltage divider, though you should verify these values on your device, as I have seen slight variations (often a hundredth of a volt) that should be accounted for.
When setting up your wind vane and anemometer, your circuit should look similar to this (but using the RJ-11 pins discussed above)
The rain meter is wired the same way as the anemometer, though using pin 3 instead of pin 2.
Now that the hardware is set up for the weather meter, let's put together the Arduino sketch. The first thing to do is define our constants and variables at the top of the program.
#define uint unsigned int
#define ulong unsigned long
#define PIN_ANEMOMETER 2
#define PIN_WATER 3
#define PIN_VANE A0
#define NUM_OF_DIRS 16
// How often we want to calculate wind speed
#define MSECS_CALC 5000
// How often we want to calculate rain level and direction to upload
#define MSECS_UPLOAD 1800000
char *strVals[NUM_OF_DIRS+1] = {
"N", "NNW", "NW", "WNW", "W", "WSW", "SW", "SSW",
"S", "SSE", "SE", "ESE", "E", "ENE", "NE", "NNE", "?" };
int directions[NUM_OF_DIRS] = {
384, 198, 226, 40, 44, 31, 88, 60,
139, 118, 307, 292, 462, 405, 434, 343 };
volatile int numRevsAnemometer = 0; // Incremented in the interrupt
volatile int numWater = 0;
ulong nextCalcSpeed; // When we next calc the wind speed
ulong time; // Millis() at each start of loop().
float totalWater = 0.0f;
String windSpeed = "";
String windDir;
Our directions
values need to match up to the readings off of the voltage divider for the wind vane, and the position of the value when it's found will correlate to our strVals
array items.
Next we will fill out the setup()
function. For this program, the setup method simply allocates pins and establishes interrupts.
void setup() {
Serial.begin(9600);
pinMode(PIN_ANEMOMETER, INPUT);
digitalWrite(PIN_ANEMOMETER, HIGH);
attachInterrupt(0, countAnemometer, FALLING);
pinMode(PIN_WATER, INPUT);
digitalWrite(PIN_WATER, HIGH);
attachInterrupt(1, countWater, FALLING);
nextCalcSpeed = millis() + MSECS_CALC;
}
The numRevsAnemometer
and numWater
values will count the number of times that the interrupts are triggered for measuring rain fall and wind speed. For rain fall, we will simply add 0.011 inches of rainfall to the totalWater
value until we're ready to upload it.
void countWater() {
numWater++;
}
void calcWater() {
float level = 0.011f;
level *= numWater;
numWater = 0;
totalWater += level;
}
With the anemometer, we will do a bit of math to calculate wind speed by multiplying the number of interrupts by 14920 (1.492 miles/hour times ten for more math later in the function), then dividing it by the amount of time that has passed since the last calculation.
void countAnemometer() {
numRevsAnemometer++;
}
// 1 rev/sec = 1.492 mph
void calcWindSpeed() {
int x;
// This will produce mph * 10
// (didn't calc right when done as one statement)
long speed = 14920;
speed *= numRevsAnemometer;
speed /= MSECS_CALC;
windSpeed = "";
windSpeed = String(windSpeed + String(speed / 10) + String('.') + String(speed % 10));
numRevsAnemometer = 0;
}
Finally, calculating wind direction is done by reading the voltage off of the divider and finding values in the tables that we established earlier.
void calcDirection() {
Serial.print("Wind direction: ");
int sensorValue = analogRead(PIN_VANE);
int dirRes = (sensorValue * (5.0 / 1023.0)) * 100;
Serial.println(dirRes);
int x;
for (x = 0; x < NUM_OF_DIRS; x++) {
if (directions[x] == dirRes) {
break;
}
}
windDir = strVals[x];
Serial.println(windDir);
}
While the weather meter itself is more than good enough for the purposes of this project, we can always add more. The Elegoo starter kit happened to come with a DHT11 temperature and humidity sensor, so I figured I'd incorporate that into the project to track more environmental information. The component only requires a signal wire, which I placed into pin 5 on the Arduino, a power line at 3.3v or 5.0v, and a ground wire.
I was able to find the DHT11 library online, so I added that to my Arduino environment, and then incorporated it into my project. You can do this, too, by adding the following lines to the top of your class
#include <dht.h>
#define PIN_DHT11 5
dht DHT;
To retrieve the temperature and humidity, you can create a function like this
void calcTemperature() {
DHT.read11(PIN_DHT11);
}
Now that we have our Arduino recording and saving environmental data, we need to do something with it. The next sections will cover how we will connect our Arduino to the Internet using a Helium long range wireless module, and then funnel that information into Firebase via Google Cloud IoT Core. Once the data is stored in Firebase, we will run a cron job to start a Firebase Function that will check to see if the garden should be watered, then start the sprinkler system via a call to the Rachio API.
Helium HardwareThe Helium long range wireless setup consists of two devices: a device module, known as an Atom, that sits on the Arduino via a shield
and a network endpoint that physically connects to a regular network, known as an Element.
You can attach the Atom shield to your Arduino Uno and use the pins on the top just like you would on an Uno without the shield. You will need to adjust the jumpers on the top so that pin 8 is used for data out, and pin 9 is used for data in. The Element can be plugged into your network via an ethernet cable (assuming you have the ethernet version of the element - there's also a more expensive cellular connection version).
Rachio Setup
After your Arduino hardware is set up, the rest of this project is actually easier to build if we move backwards from the Rachio controller to the Arduino weather monitor. The first step is setting up the controller to your system. This is pretty straight forward - each zone is controlled by a single wire, so you just need to plug those wires into the Rachio controller based on their zone number.
Once the controller is hooked up, you will need to open their app and add information about your zones, then set up a schedule that will be triggered by your own monitoring service.
Make sure you untick Enabled, otherwise the sprinklers will run on their own system rather than yours.
Backend Setup - Firebase and Cron JobNext it's time to set up the backend. This project uses Firebase and Google Cloud IoT Core to communicate between the Arduino and the Rachio, as well as store data. You will first need to go to the Firebase console and create a new project.
After your project is created, you will need to upgrade the project to the pay-as-you-go Blaze plan, as there must be a credit card on file in order to access external APIs via a Firebase Function.
In order to trigger our function every 24 hours, I'm using Functions-Cron library from Google. Rather than rehashing all of the instructions for this library, you can find the directions for setting up a cron job for 24 hours in the README file linked here.
One thing to note is that you will need to install some dependencies before you can upload the pre-defined Firebase function after cloning the project. You can find those commands here:
npm install firebase-functions
npm install firebase-admin
After your dependencies are installed, you can push up the sample function like so
After your sample has uploaded, you can go into the appengine directory and open cron.yaml, which will look like this:
cron:
- description: Push a "tick" onto pubsub every hour
url: /publish/hourly-tick
schedule: every 1 hours
- description: Push a "tick" onto pubsub every day
url: /publish/daily-tick
schedule: every 24 hours
- description: Push a "tick" onto pubsub every week
url: /publish/weekly-tick
schedule: every saturday 00:00
Since the only cron job we will need is daily-tick
, we can delete the hourly-tick
and weekly-tick
items.
cron:
- description: Push a "tick" onto pubsub every day
url: /publish/daily-tick
schedule: every 24 hours
Next you will want to update the index.js file in the functions directory of the cloned project. The sample supports the hourly-tick
published subject, but we can change it to support the daily-tick
published subject.
var functions = require('firebase-functions');
exports.daily_job =
functions.pubsub.topic('daily-tick').onPublish((event) => {
console.log("This job is run every day!")
});
We'll come back to update our Firebase functions after we set up Google IoT Core and the Helium console.
Backend Setup - IoT CoreGoogle IoT Core is what will link the Arduino device to Firebase. You will need to enable IoT Core by going to the Google Cloud console and searching for IoT Core in the bar at the top of the page. Once you find the API, you will need to click on the blue enable button.
This will bring up a second dialog that allows you to create a registered device.
The next screen will require that you enter information for your weather station, and create a new pub/sub topic. Here you can see that the registry ID I'm using is weather-station
, and the telemetry topic is weather-data-upload
.
After creating the new registration, you will need to go back into the Google Cloud console and create a new service account. This account will be used by the Helium network to send data to your Google service.
Click on the Create Service Account link at the top of the Service accounts page, enter a name for the new account, give it a project role of Cloud IoT Admin and Cloud IoT Editor (for whatever reason, Admin doesn't include all of the permissions of editor - this was a recent change, because Google ��) and opt to furnish a new JSON private key. Side note/update: another recent change is the following screen - it'll look a little different, using two different screens. It'll be similar enough that you can figure it out though :)
After you click on the blue save button, a JSON file will save to your computer.
Backend Setup - HeliumAt this point we're almost done with the initial setup of our backend. This step will go through the Helium Dashboard and tie your project up to the rest of the infrastructure we have just put together.
From the Dashboard, after logging in, select the Atom section and click on the blue Add New button. You can add information for your Atom there to activate it.
You can do the same thing for your Helium Element.
After your devices are registered, you will need to create a new Helium channel. You can select the channel section on the left of the dashboard website, and then click on Google Cloud IoT Core under the Create New Channel header.
On the next page, you will need to enter information from your Google Cloud project: region, registry name and the private JSON key that is located in the file that was downloaded when creating the registry in the Google Cloud console. After you've entered your information, you can click Next to finish setting up your new Helium channel.
Congratulations, the services setup part of this project is done, and we can get into the actual code again :) Next we will need to add the Helium Arduino library to our Arduino library collection. After the Helium source is placed in your library directory, it's time to add Helium to your Arduino sketch. Start by importing the necessary Helium classes at the top of the sketch, as well as references to the classes that will be used for communication.
#include "Arduino.h"
#include "Board.h"
#include "Helium.h"
#include "HeliumUtil.h"
We will then declare the channel name that we're using and some Helium objects
#define CHANNEL_NAME "Sprinklers"
Helium helium(&atom_serial);
Channel channel(&helium);
To wrap up connecting to the channel, add the following three lines to the setup function:
helium.begin(HELIUM_BAUD_RATE);
helium_connect(&helium);
channel_create(&channel, CHANNEL_NAME);
Next, let's update our loop()
method to calculate all of our values from the sensors and upload the data. You'll notice that wind speed is being calculated every 5 seconds, whereas everything else is being calculated every 30 minutes. I did this because I wanted to get the wind speed closer to the upload time, not the average speed over 30 minutes. You can play with these numbers however you see fit, however :)
void loop() {
time = millis();
if (time >= nextCalcSpeed) {
calcWindSpeed();
nextCalcSpeed = time + MSECS_CALC;
}
if (time >= nextCalcUpload) {
calcDirection();
calcWater();
calcTemperature();
nextCalcUpload = time + MSECS_UPLOAD;
String formattedString = "{\"temp\":" + String(DHT.temperature) + ",\"dir\":" + windDir + ",\"speed\":" + windSpeed + ",\"water\":" + totalWater + "}";
Serial.println(formattedString);
char data[50];
formattedString.toCharArray(data, 100);
int8_t result;
channel.send(data, strlen(data), &result);
}
}
If we run this code now, we should see a nice blip in the Helium channel dashboard showing our data having been uploaded to Google Cloud.
Earlier in this project we set up Firebase and a cron job that runs every 24 hours, but we didn't actually do anything with them. Let's fix that. The first thing we will want to do is add a method that listens for new uploaded data, and then stores it in a Firebase database. When data is received, we will store the wind speed, direction and temperature with a time stamp. We will also append the new water gain (if any) to the daily value.
const functions = require('firebase-functions');
var admin = require('firebase-admin');
const googleapis = require('googleapis');
const fs = require('fs');
const API_SCOPES = 'https://www.googleapis.com/auth/cloud-platform';
const API_VERSION = 'v1';
const DISCOVERY_API = 'https://cloudiot.googleapis.com/$discovery/rest';
const SERVICE_NAME = 'cloudiot';
const DISCOVERY_URL = `${DISCOVERY_API}?version=${API_VERSION}`;
const projectId = "";
const cloudRegion = "us-central1";
const registryId = "";
admin.initializeApp();
var db = admin.database();
exports.receiveTelemetry = functions.pubsub
.topic('weather-data-upload')
.onPublish((data) => {
const attributes = data.attributes;
const message = data.json;
const envdata = {
temp: message.temp,
dir: message.dir,
speed: message.speed,
water: message.water
};
return Promise.all([
updateCurrentData(envdata),
updateWater(envdata)
]);
});
function updateCurrentData(data) {
var d = new Date();
var timeInMillis = d.getTime();
var ref = db.ref(`/envlog/${timeInMillis}`);
return ref.set({
temp: data.temp,
dir: data.dir,
speed: data.speed
});
}
function updateWater(data) {
return db.ref('/').once('water').then(function(snapshot) {
var currentWaterLevel = (snapshot.val() && snapshot.val().today) || 0.0;
var newlevel = currentWaterLevel + data.water;
var ref = db.ref(`/water/today`);
return ref.set({ today: newlevel });
})
}
When the 24 hour cron job runs, we will take the water level value at that time and store it as a previous day's value. This is also where we will decide if watering should happen. If the rain level over the last two days is greater than 1", we'll start the sprinklers.
exports.daily_job =
functions.pubsub.topic('daily-tick').onPublish((event) => {
return db.ref('/').once('water').then(function(snapshot) {
var currentWaterLevel = (snapshot.val() && snapshot.val().today) || 0.0;
var yesterdaysWaterLevel = (snapshot.val() && snapshot.val().yesterday) || 0.0;
var shouldStartSprinkler = (currentWaterLevel + yesterdaysWaterLevel) > 1.0
if( shouldStartSprinkler ) {
startSprinklers();
}
return db.ref('/water').set({
yesterday: currentWaterLevel,
today: 0.0
});
})
});
Starting the sprinklers is where things get a bit more interesting. The first thing you will need to do is get the API key for the Rachio. You can find it in the Account Settings section of the Rachio login page under the GET API KEY link.
Next, open a terminal and issue this command, replacing xxxx-xx in the sample with your personal API key.
curl -v https://api.rach.io/1/public/person/info -H "Authorization: Bearer xxxx-xxx"
This will return a response from the Rachio API that contains your user ID at the bottom of the response. You will use this ID with your API key to retrieve information about your controllers, and the schedule that you will start from Firebase.
curl -X GET -H "Content-Type: application/json" -H "Authorization: Bearer YOUR-API-KEY-HERE" https://api.rach.io/1/public/person/YOUR-PERSON-ID-HERE
This is going to come back as a giant JSON block, which you'll want to copy into a JSON formatter from your terminal in order to read it. If you look through this data, you'll find the scheduleRules item, which contains the schedule's id value, as seen in this snippet.
"timeZone":"America/Denver",
"latitude":39.9194443,
"longitude":-105.0566776,
"name":"Rachio-Home",
"scheduleRules":[
{
"id":"xxxxxxxxxx",
"zones":[
{
"zoneId":"xxxxxxxx",
"duration":1620,
"sortOrder":5
},
At this point you have all of the data that you need for starting a sprinkler schedule. The reason we're using a schedule rather than starting each individual zone is because the schedule will start each zone sequentially on its own, keeping our own code simple. We will use the request NPM module for making the network call. It is important to remember that Firebase requires some form of paid account (even pay-as-you-go) in order to send external network requests, such as to the Rachio API.
var networkrequest = require('request');
var schedule1 = {
url: 'https://api.rach.io/1/public/schedulerule/start',
headers: {
'Authorization': 'Bearer api-key-here'
},
method: 'PUT',
body: '{ "id" : "schedule-id-here"}'
};
function startSprinklers() {
console.log("trying to send request");
networkrequest(schedule1, callback);
}
function callback(error, response, body) {
if (!error && response.statusCode == 200) {
console.log("sprinklers on")
}
}
When the startSprinklers()
method is called, a network request will be sent to the Rachio API to start your schedule. You should see a notification on your phone when this starts, as well as see the sprinklers start.
As with any project, there's parts that I'd want to improve. For starters, I'd want the cron job to trigger at a specific time - preferably in the middle of the night. I'd also like to improve the 'should I water?' logic, and set up a custom schedule through Firebase scheduling and running each zone individually. I wouldn't mind updating the sensors, as well, to include soil moisture and humidity, allowing me to check against more data before turning on the sprinkler system. Power is the other tricky part here. Currently I'm just running everything off of a rechargeable battery pack, though I'd love to switch to a system that uses solar panels to recharge the pack. Finally, now that I have the other portions of this project built, I'd want to create my own hardware controller, rather than relying on the Rachio at all. This could be a set of relays controlled via IoT Core. That's a whole other additional project for another day, though :)
ConclusionAt this point we should have a weather station that stores data and starts the smart sprinkler system. While this project is a little more complicated than it really needs to be, it gives a good overview of using multiple hardware components, using IoT Core and Firebase Functions, and interfacing with the Rachio through Firebase. Using this knowledge, you can create and improve your own projects using similar technologies :)
Comments