Go Down

Topic: wifi enabled alarm clock (Read 4246 times) previous topic - next topic

Imahilus

Nov 09, 2010, 03:00 am Last Edit: Nov 09, 2010, 03:40 pm by Imahilus Reason: 1
Hey,

second arduino project finished.
A photograph of the assembly:
http://www.frozenchaos.net/Arduino/arduino_alarm.jpg

So I wanted to make an alarm clock since my current one is being 'fidgety' (It takes some fidgeting when putting the alarm on, to get it to detect that its supposed to be on.
While I don't have a problem remembering to set my alarm (and even turning it on).. it is sometimes a concern, so que.. wifi capabilities. Whats nicer than being able to set your wakeup times for the upcomming week? So you wake up, ready to go to school / work.. you arrive there.. no teacher / closed door. Ok.. so you sit down behind a school computer / go home. You check your e-mail... great, at ### in the morning, they sent you an e-mail moving the appointment.
Excuse #2 to put wifi on the alarm clock. Now something akin to bld's wifi tank is also something I'd like to do at some point, but for now a simple alarm clock will have to do!

So thats my criteria:
- update its alarm time from the server
- use the server to get the number of unread e-mails from my inbox
- synchronise the time of the clock with the server
- provide an annoying noise to wake me up
- snooze functionality
- IR remote (flailing my arm or foot around trying to hit that blasted button just isn't comfy when my room is freezing cold!) to turn off the alarm / hit the snooze button.

That explained, time to provide the goods!

Items used:
3 resistors (2x 1k and I believe 10k (red red brown gold))
1 diode
1 LED
1 buzzer (I used a sonitron 6MA-21L, but any one that works at 5V will probably do)
1 IR receiver (I used a TSOP853 with breakout from sparkfun, there are cheaper ones out there)
1 4x20 LCD that works with the LiquidCrystal display, forgot what the common interface is called..
1 Asynclabs Wishield 2.0 (without antenna)
1 PC with the following software: apache, php5, php-imap (a php library). This PC has been given the static address of 192.168.1.200 in my network.

Wiring:
Since I don't have a diagram.. I'll have to explain the wiring (some vcc / ground references have been omitted, see the items instruction on the playground for details)
digital6 -> IR receiver signal pin
digital8 -> buzzer + (the - side with the 10k? resistor to ground)
digital9 -> LED + (the - side with a 1k resistor to ground)
analog0 (digital14) -> LCD signal line 0
analog1 (digital15) -> LCD signal line 1
analog2 (digital16) -> LCD signal line 2
analog3 (digital17) -> LCD signal line 3
analog4 (digital18) -> LCD enable
analog5 (digital19) -> LCD reset
wishield 2.0 - interrupt jumper to d2, LED jumper removed and the remaining jumper on d7.
The thing missing is the contrast wiring of the LCD, I used a static ... whatyamacallit (sorry, I'm a coder, not an electrical engineer) with a diode and a resistor.

Will put the code of the server side parts and arduino in following posts (not enough space left in this one).
The libraries used can be found here
The date and beeper libraries I wrote myself, they're free to use in whatever way you want. The IRRecv library is a modified version of the IR remote library that can be found on the playground, I don't claim ownership of this nor credit. Thanks for doing the hard work for me, I just wanted something slightly different, and have changed the library to fit accordingly.
The WiServer library can be found on http://www.asynclabs.com, and the WString library also on the playground.

Imahilus

#1
Nov 09, 2010, 03:02 am Last Edit: Nov 09, 2010, 03:11 am by Imahilus Reason: 1
The arduino code:
Code: [Select]
#define IR_SONY
#include <IRrecv.h>
#include <LiquidCrystal.h>
#include <Beeper.h>
#include <Date.h>
#include <WiServer.h>
#include <WString.h>

//wifi stuff
unsigned char   local_ip[]                    = {192,168,1,201}; //IP address of WiShield
uint8           server_ip[]                   = {192,168,1,200}; //destination IP
unsigned char   gateway_ip[]                  = {192,168,1,1};   //router or gateway IP address
unsigned char   subnet_mask[]                 = {255,255,255,0}; //subnet mask for the local network
const prog_char ssid[] PROGMEM                = {"ssid"};   //the ssid of the network to connect to, max 32 bytes
unsigned char   security_type                 = 3;               //the security type: 0 - open; 1 - WEP; 2 - WPA; 3 - WPA2
const prog_char security_passphrase[] PROGMEM = {"pass"};    //wpa/wpa2 passphrase
unsigned char   wireless_mode                 = 1;               //use infrastructure based wifi, use 2 to use ad-hoc
GETrequest      getAlarmUpdate                = GETrequest(server_ip, 80, "192.168.1.200", "/arduino.php");
GETrequest      getTimeSynchronisation        = GETrequest(server_ip, 80, "192.168.1.200", "/arduino.php?sync=1");
prog_uchar      wep_keys[0];
unsigned char   ssid_len;
unsigned char   security_passphrase_len;

//initialize the library objects
LiquidCrystal lcd    = LiquidCrystal(19, 18, 17, 16, 15, 14);
IRrecv        ir     = IRrecv(6);
Beeper        bp     = Beeper(8, 9);
Date          date   = Date();

//constants
const unsigned int  RemoteBounceInterval   = 200;      //200 millisecond debounce time for remote control signals, accept only one button press each 200 milliseconds
const unsigned long ServerUpdateInterval   = 60000;    //check the server for an update each 60k milliseconds, in other words.. every minute
const unsigned long ServerTimeSyncInterval = 43200000; //synchronise the time with the server every 12 hours (12*60*60 = 43200), this interval is in milliseconds

//variables used in the application
boolean       alarmSet                 = false;               //whether the alarm is currently ringing (true) or not (false)
unsigned int  alarmMoment[]            = {61, 0, 0, 1, 1, 0}; //the next timestamp when the alarm should ring, 61 seconds cannot be achieved.. thus it won't ring
unsigned long remotePreviousTime       = 0;                   //the previous time when a IR remote command has been processed
unsigned long updateDatePreviousTime   = 0;                   //the previous time when the date has been updated, includes an update of the LCD
unsigned long serverUpdatePreviousTime = 0;                   //the last time an update from the server was requested
unsigned long previousTimeSyncTime     = 0;                   //the last time a time synchronisation took place
boolean       allowServerUpdates       = true;
byte          pastHeaders              = 0;                   //the state machine variable to check whether the HTTP headers have been ignored yet, at 4 the headers are finished
byte          emailCount               = 0;                   //the number of unread emails in the inbox
String        contents(30);                                   //the contents of the HTTP request, minus the headers


void setup()
{
   lcd.begin(20, 4);
   lcd.clear();
   Serial.begin(9600);
   
   lcd.setCursor(0, 0);
   lcd.print("Connecting to wifi..");
   WiServer.init(NULL);
   getAlarmUpdate.setReturnFunc(parseServerData);
   getTimeSynchronisation.setReturnFunc(parseServerData);
   getTimeSynchronisation.submit();
   lcd.clear();
   
   ir.enableIRIn();
   bp.singleBeep();
}

void loop()
{
   unsigned long time = millis();
   
   //execute time based functions
   updateDate(time);
   bp.beep(time);
   
   //check input objects
   remote(time);
   
   //see if the next alarm moment has been, if it hasn't allready been triggered
   if((!alarmSet)&&(date.hasBeen(alarmMoment[0], alarmMoment[1], alarmMoment[2], alarmMoment[3], alarmMoment[4], alarmMoment[5])))
   {
       allowServerUpdates = false;
       alarmSet           = true;
       bp.doubleBeep(true);
   }
 
   //run objects, let them work out their time delayed actions
   serverUpdates(time);
   WiServer.server_task();
   
   delay(1);
}

void remote(unsigned long time)
{
   int key = ir.decode();
   if((time-remotePreviousTime) >= RemoteBounceInterval)
   {
       if(key >= 0)
       {
           remotePreviousTime = time;
           if(bp.isBeeping())
           {
               //if the alarm is going off, stop it
               bp.stopBeep();
               if(key != 2704)
               {
                   alarmMoment[0] = date.getSecond();
                   alarmMoment[1] = date.getMinute();
                   alarmMoment[2] = date.getHour();
                   alarmMoment[3] = date.getDay();
                   alarmMoment[4] = date.getMonth();
                   alarmMoment[5] = date.getYear();
                   date.addToDateTime(540, alarmMoment[0], alarmMoment[1], alarmMoment[2], alarmMoment[3], alarmMoment[4], alarmMoment[5]); //9 minute snooze
                   alarmSet = false; //reset the alarm
               }
               else
               {
                   allowServerUpdates = true;
               }
           }
           else
           {
               Serial.println(key, DEC);
           }
       }
   }
}

void updateDate(unsigned long time)
{
   if(time-updateDatePreviousTime >= 1000)
   {
       //previousTime = time;
       updateDatePreviousTime += 1000; //keep the discrepency so it can be corrected
       date.tick();
   
       lcd.setCursor(0, 0);
       char number = date.getHour();
       if(number < 10) { lcd.print(' '); }
       lcd.print(number, DEC);
       lcd.print(':');
       number = date.getMinute();
       if(number < 10) { lcd.print('0'); }
       lcd.print(number, DEC);
       lcd.print(':');
       number = date.getSecond();
       if(number < 10) { lcd.print('0'); }
       lcd.print(number, DEC);
       if(date.getMonth() < 10) { lcd.print(' '); }
       lcd.print("  ");
       number = date.getDay();
       if(number < 10) { lcd.print(' '); }
       lcd.print(number, DEC);
       lcd.print('/');
       lcd.print(date.getMonth(), DEC);
       lcd.print("/2");
       number = date.getYear();
       if(number < 100) { lcd.print('0'); }
       if(number < 10 ) { lcd.print('0'); } //since it is currently 2010, this will never happen.. but it defaults to 0, so keep it in here
       lcd.print(number, DEC);

       lcd.setCursor(0, 1);
       number = alarmMoment[2];
       if(number < 10) { lcd.print(' '); }
       lcd.print(number, DEC);
       lcd.print(':');
       number = alarmMoment[1];
       if(number < 10) { lcd.print('0'); }
       lcd.print(number, DEC);
       lcd.print(':');
       number = alarmMoment[0];
       if(number < 10) { lcd.print('0'); }
       lcd.print(number, DEC);
       if(alarmMoment[4] < 10) { lcd.print(' '); }
       lcd.print("  ");
       number = alarmMoment[3];
       if(number < 10) { lcd.print(' '); }
       lcd.print(number, DEC);
       lcd.print('/');
       lcd.print(alarmMoment[4], DEC);
       lcd.print("/2");
       number = alarmMoment[5];
       if(number < 100) { lcd.print('0'); }
       if(number < 10 ) { lcd.print('0'); } //since it is currently 2010, this will never happen.. but it defaults to 0, so keep it in here
       lcd.print(number, DEC);
       
       lcd.setCursor(0, 2);
       lcd.print(emailCount, DEC);
       lcd.print(" unread e-mails");
   }
}

void serverUpdates(unsigned long time)
{
   if((allowServerUpdates)&&((time-previousTimeSyncTime) >= ServerTimeSyncInterval))
   {
       //do a time synchronisation with the server
       serverUpdatePreviousTime = time;
       previousTimeSyncTime     = time;
       
       getTimeSynchronisation.submit();
   }
   else
   if((time-serverUpdatePreviousTime) >= ServerUpdateInterval)
   {
       //get an update from the server
       serverUpdatePreviousTime = time;
       getAlarmUpdate.submit();
   }
}

void parseServerData(char* data, int len)
{
   char current;
   while(len-- > 0)
   {
       current = *(data++);
       if((current == 13)||(current == 10))
       {
           pastHeaders += 1;
       }
       else
       if(pastHeaders >= 3)
       {
           //reset the printing if its the semicolon terminator
           if(current == ';')
           {
               pastHeaders = 0;
               processServerUpdate();
               contents    = "";
           }
           else
           {
               contents.append(current);
               Serial.print(current);
           }
       }
       else
       {
           pastHeaders = 0;
           contents    = "";
       }
   }
}


Due to post size constraints, the final function has been cut-off, it is listed in the next post.
Also, my apologies for awkward alignments, as this was written mainly in the arduino IDE.. some actual tab characters made it in. The alignments should be fixed if you view it with a tab size of 2.

Imahilus

#2
Nov 09, 2010, 03:07 am Last Edit: Nov 09, 2010, 03:12 am by Imahilus Reason: 1
The final arduino code function:
Code: [Select]

void processServerUpdate()
{
   //timestamps for the alarm are 13 numbers, following the following notation:
   //ssmmhhDDMMYYY - s = second, m = minute, h = hour, D = day, M = month, Y = year (omit the leading 2 of 2000)
   byte length = strlen(contents);
   if(length == 14)
   {
       //enough characters to contain the alarm update part, also includes the e-mail count update
       if((allowServerUpdates)&&(!((contents[0] == '6')&&(contents[1] == '1'))))
       {
           alarmMoment[0] = ((contents[0]-48)*10)+(contents[1]-48);
           alarmMoment[1] = ((contents[2]-48)*10)+(contents[3]-48);
           alarmMoment[2] = ((contents[4]-48)*10)+(contents[5]-48);
           alarmMoment[3] = ((contents[6]-48)*10)+(contents[7]-48);
           alarmMoment[4] = ((contents[8]-48)*10)+(contents[9]-48);
           alarmMoment[5] = ((contents[10]-48)*100)+((contents[11]-48)*10)+(contents[12]-48);
           bp.stopBeep();
           alarmSet = false;
       }
       
       emailCount = contents[13]-48;
       if((emailCount > 0)&&(!bp.isBeeping()))
       {
           bp.singleBeep();
       }
   }
   else
   if((allowServerUpdates)&&(length == 27))
   {
       //enough characters to include a time synchronisation aswell
       alarmMoment[0] = ((contents[0]-48)*10)+(contents[1]-48);
       alarmMoment[1] = ((contents[2]-48)*10)+(contents[3]-48);
       alarmMoment[2] = ((contents[4]-48)*10)+(contents[5]-48);
       alarmMoment[3] = ((contents[6]-48)*10)+(contents[7]-48);
       alarmMoment[4] = ((contents[8]-48)*10)+(contents[9]-48);
       alarmMoment[5] = ((contents[10]-48)*100)+((contents[11]-48)*10)+(contents[12]-48);
       date.setTime( ((contents[13]-48)*10)+(contents[14]-48), ((contents[15]-48)*10)+(contents[16]-48), ((contents[17]-48)*10)+(contents[18]-48) );
       date.setDate( ((contents[19]-48)*10)+(contents[20]-48), ((contents[21]-48)*10)+(contents[22]-48), ((contents[23]-48)*100)+((contents[24]-48)*10)+(contents[25]-48) );
       
       emailCount = contents[26]-48;
       if((emailCount > 0)&&(!bp.isBeeping()))
       {
           bp.singleBeep();
       }
   }
}


The servers arduino php script:
Code: [Select]
<?
//timestamps for the alarm are 13 numbers, following the following notation:
//ssmmhhDDMMYYY - s = second, m = minute, h = hour, D = day, M = month, Y = year (omit the leading 2 of 2000)


mysql_connect('localhost', 'username', 'password');
mysql_selectdb('arduino_alarm');

//get the list of alarm times, well.. the soonest one
$time = time();
$sql  = mysql_query('SELECT * FROM alarm_schedule WHERE timestamp > '.$time.' ORDER BY timestamp ASC LIMIT 1;');



//print the soonest alarm time
if(mysql_num_rows($sql) > 0)
{
   //the first 13 characters represent the next alarm event
   $row  = mysql_fetch_assoc($sql);
   echo date("siHdm", $row['timestamp']).substr(date('Y', $row['timestamp']), 1);
}
else
{
   //no alarm event available, provide a timestamp that won't trigger the alarm
   echo '6100000101000';
}



//print the datestamp for synchronisation purposes
if($_GET['sync'] == 1)
{
   //the second 13 characters represent a time synchronisation
   echo date("siHdm", $time).substr(date('Y', $time), 1);
}



//print the amount of new e-mails in the mailbox
$imap = imap_open('{mailbox:port}INBOX', 'emailaddress', 'password');
if($imap)
{
   $status = imap_status($imap, "{mailbox:port}INBOX", SA_UNSEEN);
   echo $status->unseen;
   imap_close($imap);
}
else
{
   echo '0';
}



//always finish up with a semicolon as terminator character
echo ';';

mysql_close();
?>


The alarm setting page (also on the server):
Code: [Select]
<html>
<head>
<title>Arduino Alarm Calander</title>
</head>
<body>
<h1 style="margin-left: 20px;">Arduino Alarm Calender</h1>
<?
function checkPost($name) { if((isset($_POST[$name]))&&(is_numeric($_POST[$name]))&&($_POST[$name] >= 0)) { return true; } return false; } //function to check a post variable for common requirements

//connect to the database
mysql_connect('localhost', 'username', 'password');
mysql_selectdb('arduino_alarm');
$time = time();



//new item, deleted item, or edited item
if((checkPost('year'))&&(checkPost('month'))&&(checkPost('day'))&&(checkPost('hour'))&&(checkPost('minute')))
{
   //new or edited item
   $timestamp   = mktime($_POST['hour'], $_POST['minute'], 0, $_POST['month'], $_POST['day'], $_POST['year']);
   $description = addslashes($_POST['description']);
   if(checkPost('id'))
   {
       //edited item
       mysql_query('UPDATE alarm_schedule SET timestamp = \''.$timestamp.'\', description = \''.$description.'\' WHERE id = \''.$_POST['id'].'\';');
   }
   else
   {
       //new item
       mysql_query('INSERT INTO alarm_schedule (timestamp, description) VALUES (\''.$timestamp.'\', \''.$description.'\');');
   }
}
else
if(checkPost('id'))
{
   //delete this item
   mysql_query('DELETE FROM alarm_schedule WHERE id = \''.$_POST['id'].'\';');
}



//get all the items
$sql  = mysql_query('SELECT * FROM alarm_schedule WHERE timestamp > \''.$time.'\' ORDER BY timestamp ASC;');
$rows = array();
while($row = mysql_fetch_assoc($sql)) { $rows[] = $row; }
$time += 21*3600; //time plus almost one day, this makes it so that way early in the morning, the alarm is easily set for the comming day
echo '<div style="border: 1px solid black; padding: 8px; margin-bottom: 40px; margin-left: 20px; width: 600px; -moz-border-radius:6px 6px 6px 6px;">';
echo '<form action="" method="post" style="display: inline;">';
echo '<input type="text" name="hour" size="2" maxlength="2" />:<input type="text" name="minute" size="2" maxlength="2" />';
echo ' - ';
echo '<input type="text" name="day" value="'.date('j', $time).'" size="2" maxlength="2" />/<input type="text" name="month" value="'.date('n', $time).'" size="2" maxlength="2" />/<input type="text" name="year" value="'.date('Y', $time).'" size="4" maxlength="4" />';
echo ' - ';
echo '<input type="text" name="description" size="20" maxlength="50" />';
echo ' <input type="submit" value="Add" />';
echo '</form>';
echo '</div>';

foreach($rows as $row)
{
   echo '<div style="border: 1px solid black; padding: 8px; margin-bottom: 20px; margin-left: 20px; width: 600px; -moz-border-radius:6px 6px 6px 6px;">';
   echo '<form action="" method="post" style="display: inline;"><input type="hidden" name="id" value="'.$row['id'].'" />';
   echo '<input type="text" name="hour" value="'.date('G', $row['timestamp']).'" size="2" maxlength="2" />:<input type="text" name="minute" value="'.date('i', $row['timestamp']).'" size="2" maxlength="2" />';
   echo ' - ';
   echo '<input type="text" name="day" value="'.date('j', $row['timestamp']).'" size="2" maxlength="2" />/<input type="text" name="month" value="'.date('n', $row['timestamp']).'" size="2" maxlength="2" />/<input type="text" name="year" value="'.date('Y', $row['timestamp']).'" size="4" maxlength="4" />';
   echo ' - ';
   echo '<input type="text" name="description" size="20" maxlength="50" value="'.stripslashes($row['description']).'" />';
   echo ' <input type="submit" value="Update" />';
   echo '</form>';
   echo '<form action="" method="post" style="display: inline;"><input type="hidden" name="id" value="'.$row['id'].'" /><input type="submit" value="Delete" /></form>';
   echo '</div>';
}


mysql_close();
?>
</body>
</html>


And finally, the missing piece of the puzzle, a mysql database structure dump:
Code: [Select]
DROP TABLE IF EXISTS `alarm_schedule`;
CREATE TABLE IF NOT EXISTS `alarm_schedule` (
 `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
 `timestamp` bigint(20) NOT NULL,
 `description` varchar(50) NOT NULL,
 PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=2 ;

Jonathan Oxer

That functionality sounds really interesting. It's almost like a Chumby but done with Arduino, and there are lots of possibilities for things you could display on it.

I'd love to see a picture of the end result.
--
Jon
Security Sensor Shield, coming soon: www.freetronics.com/secsense

Imahilus

More of an intermediate result, I just have to make up my mind as to what to do with the project now.
In anycase, I took a picture, it can be seen here: http://www.frozenchaos.net/Arduino/arduino_alarm.jpg

I'm contemplating whether to make my own PCB for the entire thing, to keep it as arduino+shield or making my own PCB for just the arduino and extra parts to put the wifi shield on... Feel free to add your own comments on which you'd go for.

samuelsun

Hi i am new here and glad to join this forum community .. This is really charming and awesome . I see this structure and also function.. So great....

Go Up