In this part of the series on “do it yourself” smart homes we head over to the shower where we’ll be installing an infrared sensor to monitor when we get in and out of the shower. This can provide us with a lot of useful information including an estimation of our water usage and trending to let us know our average shower time.
Getting started…
If you haven’t followed along with part 1, please start there. This tutorial is intended to expand upon the last one and won’t explain as much information. Additionally, we’ll be using the same arduino to run our infrared sensor and the sonar range finder so we’ll need to alter that code. Addtitionally, I’ve made some improvements to the backend and the arduino script.
Materials…
- Arduino
- Server/domain to call with a get request
- IR sensor from radio shack
- sonar range finder from radio shack
- New twitter account with API credentials for your shower
Coding the Arduino…
It’s probably good to start off with a simple example to get the new IR sensor working, once we can get that working well port that to the original arduino code that we already have set up to run our sonar range finder. Here’s a simple example from ladyada.net which explains (in great detail) how your IR sensor works.
/*
* PIR sensor tester
*/
int ledPin = 13; // choose the pin for the LED
int inputPin = 2; // choose the input pin (for PIR sensor)
int pirState = LOW; // we start, assuming no motion detected
int val = 0; // variable for reading the pin status
void setup() {
pinMode(ledPin, OUTPUT); // declare LED as output
pinMode(inputPin, INPUT); // declare sensor as input
Serial.begin(9600);
}
void loop(){
val = digitalRead(inputPin); // read input value
if (val == HIGH) { // check if the input is HIGH
digitalWrite(ledPin, HIGH); // turn LED ON
if (pirState == LOW) {
// we have just turned on
Serial.println("Motion detected!");
// We only want to print on the output change, not state
pirState = HIGH;
}
} else {
digitalWrite(ledPin, LOW); // turn LED OFF
if (pirState == HIGH){
// we have just turned of
Serial.println("Motion ended!");
// We only want to print on the output change, not state
pirState = LOW;
}
}
}
Once we have our IR sensor connected and we’re able to get a solid serial output. Let’s modify this script to show when we get in and out of the shower instead of just detecting motion. We’ll set up a flag to alternate motion events in order to flag our initial event as the “getting in” event and our second motion event as our “getting out” motion.
/*
* PIR sensor tester
*/
int ledPin = 13; // choose the pin for the LED
int inputPin = 2; // choose the input pin (for PIR sensor)
int pirState = LOW; // we start, assuming no motion detected
int val = 0; // variable for reading the pin status
int flag = 0;
void setup() {
pinMode(ledPin, OUTPUT); // declare LED as output
pinMode(inputPin, INPUT); // declare sensor as input
Serial.begin(9600);
}
void loop(){
val = digitalRead(inputPin); // read input value
if (val == HIGH) { // check if the input is HIGH
digitalWrite(ledPin, HIGH); // turn LED ON
if (pirState == LOW) {
// we have just turned on
Serial.println("Motion detected!");
if(flag == 0) {
flag = 1;
Serial.println("in the shower");
} else if(flag == 1) {
flag = 0;
Serial.println("out of the shower");
}
// We only want to print on the output change, not state
pirState = HIGH;
}
} else {
digitalWrite(ledPin, LOW); // turn LED OFF
if (pirState == HIGH){
// we have just turned of
Serial.println("Motion ended!");
// We only want to print on the output change, not state
pirState = LOW;
}
}
}
Once we get that working it’s time to modify our original code, last time we made a script to run the sonar sensor, we’ll modify this script. The following the newest modified version.
/*
updated as of 4/30/11 - included shower script
Web client
This sketch connects to a website (http://www.google.com)
using an Arduino Wiznet Ethernet shield.
Circuit:
* Ethernet shield attached to pins 10, 11, 12, 13
created 18 Dec 2009
by David A. Mellis
*/
#include <SPI.h>
#include <Ethernet.h>
// Enter a MAC address and IP address for your controller below.
// The IP address will be dependent on your local network:
byte mac[] = { 0x90, 0xA2, 0xDA, 0x00, 0x38, 0x04 };
byte ip[] = { 192,168,1,177 };
//byte server[] = { 173,194,33,104 }; // Google
byte server[] = { 67,222,18,93 }; // iwearshorts
// Initialize the Ethernet client library
// with the IP address and port of the server
// that you want to connect to (port 80 is default for HTTP):
Client client(server, 80);
long inches = 11;
// shower
int inputPin = 2; // choose the input pin (for PIR sensor)
int pirState = LOW; // we start, assuming no motion detected
int val = 0; // variable for reading the pin status
int flag = 0;
void setup() {
// reset pin
pinMode(9, OUTPUT); // resest
pinMode(7, OUTPUT); // ping pin
// start the Ethernet connection:
Ethernet.begin(mac, ip);
// start the serial library:
Serial.begin(9600);
// shower
pinMode(inputPin, INPUT); // declare sensor as input
}
void loop()
{
val = digitalRead(inputPin); // read input value
if (val == HIGH) { // check if the input is HIGH
// digitalWrite(ledPin, HIGH); // turn LED ON
if (pirState == LOW) {
// we have just turned on
Serial.println("Motion detected!");
if(flag == 0) {
flag = 1;
Serial.println("in the shower");
// we want to notify the server of a shower update
callServer("1", "infrared");
client.flush();
client.stop();
delay(5000);
} else if(flag == 1) {
flag = 0;
Serial.println("out of the shower");
// we want to notify the server of a shower update
callServer("0", "infrared");
client.flush();
client.stop();
delay(5000);
}
// We only want to print on the output change, not state
pirState = HIGH;
}
} else {
// digitalWrite(ledPin, LOW); // turn LED OFF
if (pirState == HIGH){
// we have just turned of
Serial.println("Motion ended!");
// We only want to print on the output change, not state
pirState = LOW;
}
}
// if no motion, start monitoring the bathroom
Serial.println("pinging...");
// send a sonar ping
pingSonar();
}
void pingSonar() {
// establish variables for duration of the ping,
// and the distance result in inches and centimeters:
long duration, inches, cm;
const int pingPin = 7;
// The PING))) is triggered by a HIGH pulse of 2 or more microseconds.
// Give a short LOW pulse beforehand to ensure a clean HIGH pulse:
pinMode(pingPin, OUTPUT);
digitalWrite(pingPin, LOW);
delayMicroseconds(2);
digitalWrite(pingPin, HIGH);
delayMicroseconds(5);
digitalWrite(pingPin, LOW);
// The same pin is used to read the signal from the PING))): a HIGH
// pulse whose duration is the time (in microseconds) from the sending
// of the ping to the reception of its echo off of an object.
pinMode(pingPin, INPUT);
duration = pulseIn(pingPin, HIGH);
// convert the time into a distance
inches = microsecondsToInches(duration);
String inchesStr = inches;
Serial.println("ping returned: "+ inchesStr +"...");
// show LED colors
if(inches < 35 && inches > 0) {
// then the person is sitting
callServer(inchesStr, "sonar");
responseLoop();
} else {
// do nothing...
}
Serial.println();
delay(3000);
}
void responseLoop () {
for(int i = 0; i < 1200; i++) {
// delay the timer
// BEGIN IF STATEMENTS FROM LOOP
// if there are incoming bytes available
// from the server, read them and print them:
if (client.available()) {
char c = client.read();
Serial.print(c);
}
delay(1);
} // end for loop
// if the server's disconnected, stop the client:
if (!client.connected()) {
Serial.println();
Serial.println("disconnecting.");
client.stop();
}
delay(560000);
// END IF STATEMENTS FROM LOOP
}
void callServer (String inchesStr, String type) {
String str = "https://iwearshorts.com/bathroom/process.php?measurement="+ inchesStr +"&type=" + type;
// give the Ethernet shield a second to initialize:
delay(1000);
Serial.println("connecting...");
// if you get a connection, report back via serial:
if (client.connect()) {
Serial.println("connected");
Serial.println("Calling: "+ str);
// Make a HTTP request:
client.println("GET "+ str +" HTTP/1.0");
client.println();
} else {
// kf you didn't get a connection to the server:
Serial.println("connection failed: " + str);
}
}
long microsecondsToInches(long microseconds)
{
// According to Parallax's datasheet for the PING))), there are
// 73.746 microseconds per inch (i.e. sound travels at 1130 feet per
// second). This gives the distance travelled by the ping, outbound
// and return, so we divide by 2 to get the distance of the obstacle.
// See: http://www.parallax.com/dl/docs/prod/acc/28015-PING-v1.3.pdf
return microseconds / 74 / 2;
}
long microsecondsToCentimeters(long microseconds)
{
// The speed of sound is 340 m/s or 29 microseconds per centimeter.
// The ping travels out and back, so to find the distance of the
// object we take half of the distance travelled.
return microseconds / 29 / 2;
}
No that we have our newly modified script running both our sonar range finder and our IR sensor its time to modify the backend to post some pictures of showers on twitter and update our database with our usage data. I have not included the calculations for water usage yet, we’ll do that in part 3 of this tutorial.
The backend…
Now lets open process.php in our backend and modify the script to process IR data as well. We’ve set up our arduino to send a data “type” in the GET string so we’ll just have to decide whether we’re receiving data from a “sonar” event or an “infrared” event. I’ve also set up a new twitter account specifically for my shower so I have an API key and authentication for that account. Based on the data “type” we’ll either upload a picture of a toilet or a shower. Then we’ll post it to twitter and input data to our database. Below is the modified process.php file.
<?php
if(isset($_GET['measurement']) && isset($_GET['type'])) {
require_once('lib/db.php');
require_once('lib/TwitPic.php');
$inches = intval($_GET['measurement']);
$type = $_GET['type'];
if($type == "sonar") {
if($inches < 16) {
$activity = "sitting";
$searchStr = "sitting+on+toilet";
} else {
$activity = "standing";
$searchStr = "standing+at+urinal";
}
/*
GET CONTENTS OF TOILETS DIRECTORY
*/
if ($handle = opendir('./toilets')) {
echo "Directory handle: $handlen";
echo "Files:n";
$images = array();
/* This is the correct way to loop over the directory. */
while (false !== ($file = readdir($handle))) {
//echo "$filen";
$images[] = $file;
}
closedir($handle);
}
// select a random image
$count = count($images);
for($i = 0; $i < $count; $i++) {
$imageNum = mt_rand(1, $count);
$selectedImageSrc = $images[$imageNum];
}
echo 'toilets/'.$selectedImageSrc;
/*
NEW TWITPIC API
* All of this info is needed only for API calls that
* require authentication. However, if the API call doesn't
* require authentication and you provide the information anyways,
* it won't make a difference.
*/
$api_key = "YOUR_API_KEY"; // your TwitPic API key (http://dev.twitpic.com/apps)
$consumer_key = "TWITTER_CONSUMER_KEY"; // your Twitter application consumer key
$consumer_secret = "TWITTER_CONSUMER_SECRET"; // your Twitter application consumer secret
$oauth_token = "TWITTER_OAUTH_TOKEN"; // the user's OAuth token (retrieved after they log into Twitter and auth there)
$oauth_secret = "TWITTER_OAUTH_SECRET"; // the user's OAuth secret (also from Twitter login)
/*
* The TwitPic() constructor can be left blank if only
* doing non-auth'd API calls
*/
$twitpic = new TwitPic($api_key, $consumer_key, $consumer_secret, $oauth_token, $oauth_secret);
try {
/*
* Uploads an image to TwitPic AND posts a tweet
* to Twitter.
*
* NOTE: this still uses v2 of the TwitPic API. This means that the code makes 2 separate
* requests: one to TwitPic for the image, and one to Twitter for the tweet. Because of this,
* understand this call may take a bit longer than simply uploading the image.
*/
$resp = $twitpic->uploadAndPost(array('media'=>'toilets/'.$selectedImageSrc, 'message'=>"Someone is ".$activity." at Mike's toilet right now!"));
print_r($resp);
} catch(TwitPicAPIException $e) {
echo $e->getMessage();
}
//$connection->post('statuses/update', array('status' => "Guess the activity! Someone is using Mike's toilet "));
// set the values to variables
$measurement = $_GET['measurement'];
$type = $_GET['type'];
// store the value in a database
$db = new DBconnect();
$result = $db->query("INSERT INTO readings (measurement, sensor_type) VALUES ('$measurement', '$type')");
if($result) {
echo "successful.";
} else {
echo "unsuccessful input.";
}
} elseif ($type == "infrared") {
if($inches == 1) {
if ($handle = opendir('./showers')) {
echo "Directory handle: $handlen";
echo "Files:n";
$images = array();
/* This is the correct way to loop over the directory. */
while (false !== ($file = readdir($handle))) {
//echo "$filen";
$images[] = $file;
}
closedir($handle);
}
// select a random image
$count = count($images);
for($i = 0; $i < $count; $i++) {
$imageNum = mt_rand(1, $count);
$selectedImageSrc = $images[$imageNum];
}
echo 'showers/'.$selectedImageSrc;
/*
NEW TWITPIC API
* All of this info is needed only for API calls that
* require authentication. However, if the API call doesn't
* require authentication and you provide the information anyways,
* it won't make a difference.
*/
$api_key = "TWITPIC_API_KEY"; // your TwitPic API key (http://dev.twitpic.com/apps)
$consumer_key = "TWITTER_CONSUMER_KEY"; // your Twitter application consumer key
$consumer_secret = "TWITTER_CONSUMER_SECRET"; // your Twitter application consumer secret
$oauth_token = "TWITTER_OAUTH_TOKEN"; // the user's OAuth token (retrieved after they log into Twitter and auth there)
$oauth_secret = "TWITTER_OAUTH_SECRET"; // the user's OAuth secret (also from Twitter login)
/*
* The TwitPic() constructor can be left blank if only
* doing non-auth'd API calls
*/
$twitpic = new TwitPic($api_key, $consumer_key, $consumer_secret, $oauth_token, $oauth_secret);
try {
/*
* Uploads an image to TwitPic AND posts a tweet
* to Twitter.
*
* NOTE: this still uses v2 of the TwitPic API. This means that the code makes 2 separate
* requests: one to TwitPic for the image, and one to Twitter for the tweet. Because of this,
* understand this call may take a bit longer than simply uploading the image.
*/
/* if($inches == 1) { */
$resp = $twitpic->uploadAndPost(array('media'=>'showers/'.$selectedImageSrc, 'message'=>"Someone is using Mike's shower right now!"));
/* } */
/* $resp = $twitpic->uploadAndPost(array('media'=>'showers/'.$selectedImageSrc, 'message'=>"Someone is using Mike's shower right now!")); */
print_r($resp);
} catch(TwitPicAPIException $e) {
echo $e->getMessage();
}
} // end if inches == 1
//$connection->post('statuses/update', array('status' => "Guess the activity! Someone is using Mike's toilet "));
// set the values to variables
/* $measurement = $_GET['measurement']; */
/* $type = $_GET['type']; */
// store the value in a database
$db = new DBconnect();
if($inches == 1) {
$measurement = "in";
} elseif($inches == 0) {
$measurement = "out";
} else {
$measurement = "Unkown shower variable";
}
$result = $db->query("INSERT INTO readings (measurement, sensor_type) VALUES ('$measurement', '$type')");
if($result) {
echo "successful." . $result;
} else {
echo "unsuccessful input." . $result;
}
} else {
echo "type not recognized";
}
} else {
echo "get request with measurement not present";
}
?>
Now that we have a PHP script that can handle our data, we just need to test and make sure it works!
Summary…
We updated our arduino script to run both a sonar range finder and IR sensor. We then told it to call our server in the event of an interation with one of these sensors. We send a get request with our data to our server where a script (called process.php) takes our data and decides what to do with it. It then uses the twitpic API to upload a new photo of either a toilet or a shower (depending on the event) and update our database with the new interaction.
Feel free to download the source and play around with it! You can also follow my shower @MikesShower and my toilet @miketoilet!