Dry-O-Matic

From Matt Bilsky

Overview

I created the Dry-O-Matic to fix a common problem when doing laundry: not knowing when the dryer is finished. In my house the dryer takes significantly longer than the washer to run a regular cycle. Since it is located in the basement it is difficult to hear if the dryer is running even on the first floor (I live in the attic). The Dry-O-Matic eliminates the need to continuously monitor the dryer for the cycle to end by notifying the selected user via text message or email when the clothes are dry.

Finished Product

Dryer Top.jpeg

Dryer Accelerometer Box.jpeg

Dryer Antenna.jpeg

Dryer guts.jpeg

Demonstration Video

Internals

The Brain

The heart of the device is an Arduino Duemilanove I had laying around. It handles all of the input/output to the LEDs and user selector along with reading the Accelerometer over I2C. A software serial port allows for communication between the Arduino and the Fonera 2100 Wi-Fi interface.

On the Duemilanove Analog ports 4 and 5 are the SDA and SCL lines. Analog 0 is used to read the potentiometer. Digital pins 2-6 are used to control the user LEDs while pins 10 and 11 are the software serial TX/RX lines.

For the final version I used a MakerShield to securely attach the wires to the Arduino. A SainSmart DS1307 AT24C32 RTC (Real Time Clock) module was also added to prevent issues with time keeping when the device is plugged in for

Dryer Arduino.jpeg

The Arduino code is posted below (Note: I removed the web address of the php script for security reasons)

// 8-21-13
// Dry-O-Matic Software
// Matt Bilsky
// matt@mattcomp.com
// http://MattBilsky.com

// Accelerometer code from:
// Example which uses the MMA_7455 library
// Moritz Kemper, IAD Physical Computing Lab
// moritz.kemper@zhdk.ch
// ZHdK, 03/04/2012
// Released under Creative Commons Licence

// RTC Module code from:
// Steve Spence
// http://arduinotronics.blogspot.com/2010/10/ds1307-real-time-clock-working.html


#include <Wire.h> //Include the Wire library
#include <MMA_7455.h> //Include the MMA_7455 library
#include <SoftwareSerial.h>
#include "Statistic.h"


SoftwareSerial mySerial(10, 11); // RX, TX

MMA_7455 mySensor = MMA_7455(); //Make an instance of MMA_7455

//turn on/off serial debug statements
boolean debug = false;

char xVal, yVal, zVal; //Variables for the values from the sensor

//store the currently selected user
char* user = "0";

//store the state of the program
int mode = 0;

//store last time check
long lastTime = 0;

//store where the pot was when started
int initVal = -1;

//store the last time the pot changed for user select delay
int lastChange = 0;

//store the temp user to prevent misfires
char* userT = "0";

//store where the pot was the last time through the selection loop
int lastVal = -1;

boolean ledState = true;

//counter for averaging scheme
int n = 1;

//assume the dryer is off to start
boolean dryerState = false;

//track the last state of the dryer since we only want to send a message if the dryer went from on to off
boolean lastDryerState = false;

//Statistics library object
Statistic stats;


///////////////////////////////////////////////////////////
// Code for reading/writing to the Real Time Clock

#define DS1307_I2C_ADDRESS 0x68

// Convert normal decimal numbers to binary coded decimal
byte decToBcd(byte val)
{
return ( (val/10*16) + (val%10) );
}
// Convert binary coded decimal to normal decimal numbers
byte bcdToDec(byte val)
{
return ( (val/16*10) + (val%16) );
}
// 1) Sets the date and time on the ds1307
// 2) Starts the clock
// 3) Sets hour mode to 24 hour clock
// Assumes you're passing in valid numbers
void setDateDs1307(byte second, // 0-59
byte minute, // 0-59
byte hour, // 1-23
byte dayOfWeek, // 1-7
byte dayOfMonth, // 1-28/29/30/31
byte month, // 1-12
byte year) // 0-99
{
Wire.beginTransmission(DS1307_I2C_ADDRESS);
Wire.write(0);
Wire.write(decToBcd(second)); // 0 to bit 7 starts the clock
Wire.write(decToBcd(minute));
Wire.write(decToBcd(hour));
Wire.write(decToBcd(dayOfWeek));
Wire.write(decToBcd(dayOfMonth));
Wire.write(decToBcd(month));
Wire.write(decToBcd(year));
Wire.write(0x10); // writes 0x10 (hex) 00010000 (binary) to control register - turns on square wave
Wire.endTransmission();
}
// Gets the date and time from the ds1307
void getDateDs1307(byte *second,
byte *minute,
byte *hour,
byte *dayOfWeek,
byte *dayOfMonth,
byte *month,
byte *year)
{
// Reset the register pointer
Wire.beginTransmission(DS1307_I2C_ADDRESS);
Wire.write(0);
Wire.endTransmission();
Wire.requestFrom(DS1307_I2C_ADDRESS, 7);
// A few of these need masks because certain bits are control bits
*second = bcdToDec(Wire.read() & 0x7f);
*minute = bcdToDec(Wire.read());
*hour = bcdToDec(Wire.read() & 0x3f); // Need to change this if 12 hour am/pm
*dayOfWeek = bcdToDec(Wire.read());
*dayOfMonth = bcdToDec(Wire.read());
*month = bcdToDec(Wire.read());
*year = bcdToDec(Wire.read());
}




void setup()
{
  if(debug) Serial.begin(9600);
  
  // Start the Fon serial 
  mySerial.begin(9600);
  
  // Set the sensitivity you want to use
  // 2 = 2g, 4 = 4g, 8 = 8g
  mySensor.initSensitivity(2);
  // Calibrate the Offset, that values corespond in 
  // flat position to: xVal = -30, yVal = -20, zVal = +20
  // !!!Activate this after having the first values read out!!!
  // Y and Z are not calibrated since we are only looking at x
  mySensor.calibrateOffset(1, -29, 74);
  
  stats.clear();
  
  
  //LED pins
  pinMode(2,OUTPUT);
  pinMode(3,OUTPUT);
  pinMode(4,OUTPUT);
  pinMode(5,OUTPUT);
  pinMode(6,OUTPUT);
  
  //RTC setup (only do once to set the time for the module, it will remember after since it has a backup battery)
  byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
  
  // Change these values to what you want to set your clock to.
  // You probably only want to set your clock once and then remove
  // the setDateDs1307 call.
  second = 0;
  minute = 20;
  hour = 10;
  dayOfWeek = 4;
  dayOfMonth = 21;
  month = 8;
  year = 13;

  //setDateDs1307(second, minute, hour, dayOfWeek, dayOfMonth, month, year);

}

void loop()
{
 //check the time from the real time clock
  byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
  getDateDs1307(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year);

  int Sec = int(second);
  int Min = int(minute);
  int Hour = int(hour);
  
  //convert the time into secons
  int time = Sec + 60*Min + 3600*Hour;
  
  //read the accelerometers
  xVal = mySensor.readAxis('x'); //Read out the 'x' Axis
  yVal = mySensor.readAxis('y'); //Read out the 'y' Axis
  zVal = mySensor.readAxis('z'); //Read out the 'z' Axis
  
  //calculate if the dryer is on
  //increment the number of samples
   n++;
   
 stats.add(double(zVal));
  
  //after 600 samples (~60 seconds) see if acceleation is higher than stationary
  if(n == 600){
    
    
    //threshold is set to 1.5
    if(stats.pop_stdev() < 2.5) dryerState = false;
    else dryerState = true;
    
    if(debug) Serial.println(stats.pop_stdev());
    
    
    n =1;
    stats.clear();
    
  }
  
  

  
  
  switch(mode){
  
    //Wait 30 seconds in case fon is warming up from a cold boot
  case 0:
  {
    if((millis()) > 30000){
       mySerial.println("\n");
       mode = 1;
       //blink 3 times to say delay is over
    digitalWrite(2,HIGH);
    digitalWrite(3,HIGH);
    digitalWrite(4,HIGH);
    digitalWrite(5,HIGH);
    digitalWrite(6,HIGH);
    delay(200);
    digitalWrite(2,LOW);
    digitalWrite(3,LOW);
    digitalWrite(4,LOW);
    digitalWrite(5,LOW);
    digitalWrite(6,LOW);
    delay(200);
    digitalWrite(2,HIGH);
    digitalWrite(3,HIGH);
    digitalWrite(4,HIGH);
    digitalWrite(5,HIGH);
    digitalWrite(6,HIGH);
    delay(200);
    digitalWrite(2,LOW);
    digitalWrite(3,LOW);
    digitalWrite(4,LOW);
    digitalWrite(5,LOW);
    digitalWrite(6,LOW);
    delay(200);
    digitalWrite(2,HIGH);
    digitalWrite(3,HIGH);
    digitalWrite(4,HIGH);
    digitalWrite(5,HIGH);
    digitalWrite(6,HIGH);
    delay(200);
    digitalWrite(2,LOW);
    digitalWrite(3,LOW);
    digitalWrite(4,LOW);
    digitalWrite(5,LOW);
    digitalWrite(6,LOW);
    }
    
    
  }
    break;
    
    //reset everything and read the pot
    case 1:
    {
     initVal = analogRead(0);
     digitalWrite(2,LOW);
    digitalWrite(3,LOW);
    digitalWrite(4,LOW);
    digitalWrite(5,LOW);
    digitalWrite(6,LOW);
    user = "0";
    mode = 2;
    }
    break;
    
    //if the pot has been moved from initial position begin selecting the user
    case 2:
    {
    int vala = analogRead(0);
    
    if (vala > initVal + 10 || vala < initVal - 10){
      lastChange = time;
      mode = 3;
     }
    }
     break;
    
    //if the pot has moved remember the time and select the user being selected
    case 3:
    {
    
    int val = analogRead(0);
    if(val > lastVal + 5 || val < lastVal - 5 ){
      lastChange = time;
    }
    
    lastVal = val;
  if(val == 0){
    userT = "1";
    digitalWrite(2,HIGH);
    digitalWrite(3,LOW);
    digitalWrite(4,LOW);
    digitalWrite(5,LOW);
    digitalWrite(6,LOW);
}
 else if(val > 0 && val <111){
    userT = "2";
    digitalWrite(3,HIGH);
    digitalWrite(2,LOW);
    digitalWrite(4,LOW);
    digitalWrite(5,LOW);
    digitalWrite(6,LOW);
}
else if(val > 110 && val <601){
    userT = "3";
    digitalWrite(4,HIGH);
    digitalWrite(2,LOW);
    digitalWrite(3,LOW);
    digitalWrite(5,LOW);
    digitalWrite(6,LOW);
}
else if(val > 600 && val <1015){
    userT = "4";
    digitalWrite(5,HIGH);
    digitalWrite(2,LOW);
    digitalWrite(4,LOW);
    digitalWrite(3,LOW);
    digitalWrite(6,LOW);
}
else{
  userT = "5";
    digitalWrite(6,HIGH);
    digitalWrite(2,LOW);
    digitalWrite(4,LOW);
    digitalWrite(3,LOW);
    digitalWrite(5,LOW);
}

//if the same user has been selected for 5+ seconds select the user and start the dry cycle
if((time - lastChange) > 5){
  user = userT;
  mode = 4;
}
    }
break;

//blink the led
case 4:
{

  int usernum = atoi(user) + 1;

  //Serial.println(usernum);
digitalWrite(usernum,ledState);
ledState = !ledState;
mode = 5;
    }
break;

//blink the led and wait for the dryer to go from on to off (we don't care if it goes from off to on)
//when dryer stops send email to the selected user using the serial line and the php script
//wget -s tells it to overwrite the file

case 5:
{
  int usernum1 = atoi(user) + 1;
  digitalWrite(usernum1,ledState);
ledState = !ledState;
  if (dryerState != lastDryerState){
    if(dryerState == false){
      mySerial.print("\n");
      mySerial.print("wget -s http://YOURSITE.com/YOURSCRIPT.php?user=");
mySerial.print(user);
      mySerial.println("\n");
      mode = 1;
    }
  }
}
break;




}
//note if the dryer was on or off the last time the software looped around
lastDryerState = dryerState;
//if(debug) Serial.println(mode);

//serial delay to prevent overload
delay(100);
}

Wi-Fi

Arduino specific Wi-Fi shields are generally quite expensive. Back around 2006 when the Fon company was just starting out I ordered the free Fon 2100 wireless access point. It sat around in its box up until 3 years ago when I needed a way to give wireless communication to an Arduino for the original thermal chamber. Following the online guides I installed OpenWRT (since its serial console natively runs at a BAUD rate of 9600). I had the Fon device sitting around unused so it seemed like a good idea to use it on this project. The Arduino code delays 30 seconds after firing up to give OpenWRT enough time to boot then sends a '\n' over the serial link to activate the serial console. To communicate with the web the Arduino sends a wget command over serial to the Fon which calls the php script emailing/texting the users.

Dryer fon.jpeg

The yellow wire is the TX pin (which goes to the RX on the Arduino) and orange wire is the RX pin. It is also important to ensure that the ground on the Fon and Arduino are connected (the red wire).

Accelerometer

I tried to find the cheapest accelerometer breakout board including shipping which led me to the SainSmart MMA7455 on Amazon (hooray Amazon Prime!). Moritz Kemper wrote a great Arduino library for the MMA7455 available here. In the software I used a scheme that averaged 100 samples to come up with an acceleration value. Testing with this method allowed me to come up with a threshold acceleration for the dryer on versus off. An old Sparkfun box serves as an enclosure for the accelerometer and a strong magnet to hold it tight against the top of the dryer.

Dryer accel.jpeg

User Interface

The user interface was designed to be as simple as possible. The blue LED is tied directly to the 5 volt supply and lets the user know that the system is powered on. Turning the selector (a simple 10K potentiometer) 'wakes' the system. Orange LEDs illuminate to show which user is currently selected. Selecting a user for 5 seconds locks in the choice and tells the system who is doing laundry and to start monitoring the dryer to see if it has gone from on to off. The LED of the user to be notify blinks continuously while the system is working. When the dryer stops the system stops blinking the LED indicating that the message is sent and it is back in 'sleep' mode. I use quotes for the mode since the device does not actually power down.

The wiring of the user selection LEDs:

Dryer LEDs.jpeg


Selection potentiometer (the shiny thing) mounted on another old Sparkfun box as a riser:

Dryer pot.jpeg

Server PHP Script

A simple PHP script was used to handle sending emails. Texts can be sent to users by utilizing each cell phone carriers email to text address such as 1234567890@vtext.com for Verizon users. The script has 755 permissions and is fed a user number when called which tells it who to email.

<?php
	//Start session
	session_start();
	
	
	
	//Array to store validation errors
	$errmsg_arr = array();
	
	//Validation error flag
	$errflag = false;
	
	
	
	
	//Function to sanitize values received from the form. Prevents SQL injection
	
	
	//Sanitize the POST values
	$user = $_GET['user'];
	

	//Input Validations
	if($user == '') {
		$errmsg_arr[] = 'Login ID missing';
		$errflag = true;
	}
	

	//If there are input validations, redirect back to the login form
	if($errflag) {
		$_SESSION['ERRMSG_ARR'] = $errmsg_arr;
		session_write_close();
		echo "fail";
		exit();
	}
	
	if ($user == '1'){
	$to = 'ADDRESS 1';
	}
	elseif ($user == '2'){
	$to = 'ADDRESS 2';
	}
	else {$to = 'FAIL ADDRESS';}
	$subject = 'Dryer';
	$body = 'Clothes are dry!';
	;
				
 if (mail($to, $subject, $body)) {
   echo("<p>Message successfully sent!</p>");
  } else {
   echo("<p>Message delivery failed...</p>");
  }

		
		
?>