Dry-O-Matic
From Matt Bilsky
Contents
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
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
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.
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.
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:
Selection potentiometer (the shiny thing) mounted on another old Sparkfun box as a riser:
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>"); } ?>