Monday, May 3, 2010

Arduino Email Manager - Part 3 - Arduino Code

Now we come to the last part of the Email Manager - the Arduino firmware. Most of it is reasonably straightforward, but there are a few things to note.

To drive the LCD I used the LCD4Bit_mod library recommended to go with the DFRobot LCD shield (available from here). In general this seems to work fine, but it doesn't seems consistent about one line flowing over to the next which is why I have "lcd.cursorTo" lines to put the cursor on the 1st or 2nd line.

The buttons actually are run over Analog pin 0 and the "get_key" function determines the analog value returned and lines it up with the appropriate key number. The trickiest thing for me was figuring the various modes for the buttons in different states - especially the Up/Down buttons that can also be used to delete messages, which is why there are a lot of "if-this-key-and-that-mode-do-this" statements. Likely they could be cleaned up further!

Finding the from/subject info in the data stream proved to be harder than I would have thought as explained in the first post so I ended up using the TextFinder library available from here.  This allows you to search in the data stream (either serial or Ethernet) for keywords or values. Come to think of it, I could probably have used this to do the whole email parsing job and then dispensed with the PHP script, but I noticed when I ran the "getValue" feature it temporarily blacked out the LCD screen - maybe a short processor lock up? Anyway, if anyone can think of a way to use TexFinder to eliminate the PHP script, let me know!

So, here is the Arduino code (with lots of comments) in its glory:

/*
*  Arduino POP Mail Manager
* Uses the DFRobot LCD Shield, Arduino Ethernet Sheild
* and an Arduino Duemilanove to build a simple system for seeing
* how many POP eamils you have and deleting ones you don't want. Separate
* PHP script must be used with it. Complete description at:
* http://opensourceprojects-torchris.blogspot.com/
*
* Uncomment the //Serial lines for troubleshooting/debug info.
*
* This code is in the public domain. Please provide credit if it is used
* in another project.
* 
* written by Chris Armour, Arpil 30th, 2010
*
*/

//=====================Libraries=============/
#include <Ethernet.h>
#include <LCD4Bit_mod.h> 
#include <TextFinder.h>

//===============Set up variables =============/

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; //MAC address for Arduino
byte ip[] = { 192,168,0,34 }; // IP address you wish to assign to Arduino
byte server[] = { 192, 168, 0, 171 }; // IP address of your PHP server
int ServerPort = 12345;
int  adc_key_val[5] ={30, 150, 360, 535, 760 };
int NUM_KEYS = 5;
int adc_key_in;
int key=-1;
int oldkey=-1;
char NumRecd[4];
char SubjRecd[32];
boolean ConnectedState = false;
int MsgNumber = 1;
char TotalMsgNumChar[4] = "   ";
int MsgTotalNum = 1;
char TotalMsgCharAr[2];
boolean DelMode = false;
unsigned long ButtonMillis = 0;
unsigned long CurrentMillis = 0;
unsigned long interval = 15000;

//===============Setup instances===============/

LCD4Bit_mod lcd = LCD4Bit_mod(2); 
Client client(server, ServerPort); 
TextFinder finder( client);

//=================Setup========================/

void setup() { 
  Ethernet.begin(mac, ip);
  lcd.init();
  lcd.clear();
  lcd.cursorTo(1,0);
  lcd.printIn("POP Mail Started");
  lcd.cursorTo(2,0);
  lcd.printIn("Press S to conct");
  //Serial.begin(9600);
  DelMode = false;
  ConnectedState = false;
}

//=================Main program loop ==================/

void loop() {

  CurrentMillis = millis(); 
  
  if (((CurrentMillis - ButtonMillis) > interval) && (ConnectedState == true)){
    //If connected to the PHP script, then every 15 seconds display the number of emails.
    ButtonMillis = CurrentMillis;
    //Serial.println("15 Seconds have passed!");
    GetTotalMsgs(); //Get total message count
    delay(100);
    PrintTotalMsgs(); //Display total messages
  }
  
  if (((CurrentMillis - ButtonMillis) > interval) && (ConnectedState != true)){
    //If not connected, then just display the not connected error.
    ButtonMillis = CurrentMillis;
    //Serial.println("15 Seconds have passed!");
    NotConnectedError();
  } 
  

//The following items are from the original LCD example and they read the value of the key pressed. 
adc_key_in = analogRead(0);    // read the value from the sensor 

key = get_key(adc_key_in); // convert into key press
   
if (key != oldkey) // if keypress is detected
    {
    delay(70);        // wait for debounce time
    adc_key_in = analogRead(0);    // read the value from the sensor 
    key = get_key(adc_key_in);                // convert into key press
    //Serial.println(key);
  
    if (key != oldkey)               
    {           
      oldkey = key;
    }
  }
  
  if (key == 4) {
    //If the Select button is pushed, run the routine to connect to the PHP server.
    ButtonMillis = millis();
       if (client.connect()) {
          char c = client.read();
        if (c == 'C') {
          //Serial.println("connected");
          lcd.clear();
          lcd.printIn("Connected");
          ConnectedState = true; //Sets ConnectedState to true
          delay(200);
        }
         } else {
           //If can't connect, print an error.
          //Serial.println("connection failed");
          ConnectedState = false; // Set connected state to false
          lcd.clear();
          lcd.printIn("Connection Fail");
          lcd.cursorTo(2, 0);  //line=2, x=0 
          lcd.printIn("Check PHP script");

      }
  }
  
  if ((key == 3) && (ConnectedState == true)) {
    //If Right button pressed and it's connected to the PHP script, then dispaly the toal messages.
          ButtonMillis = millis(); //these detect when the button was pushed
          GetTotalMsgs();
          delay(100);
          PrintTotalMsgs();
         }
        
if ((key == 3) && (ConnectedState == false)){
   //Serial.println("Not Connected!!");
   //If ConnectedState is false, print error message.
    NotConnectedError();
    } 


if ((key == 2) && (ConnectedState == false)){
   //Serial.println("Not Connected!!");
    NotConnectedError();
    } 

if ((key == 2)&& (DelMode != true) && (ConnectedState == true)) {
  //If it is connected AND not in delete mode, then display email info
  ButtonMillis = millis();
  GetTotalMsgs();
  delay(200);
  //Serial.println("DN button push detected");
  MsgNumber++; //Increments the messages.
   if (MsgNumber >= MsgTotalNum){ //if we reach the max message number, then go to message #1
   MsgNumber = 1;
    }  
   CallMsg();
   delay(150);
   //Serial.print("Total Messages = ");
   //Serial.println(MsgTotalNum);

}
         
if ((key == 2) && (DelMode == true)&& (ConnectedState == true)){
  //If it is connected, but in Delete mode, then this is the confirm delete button.
  ButtonMillis = millis();
  //Serial.println("DN button push detected");
  //Serial.print("Deleting message number =");
  //Serial.println(MsgNumber);
  DelMode = !DelMode;
  DelMsg();
  lcd.clear();
  lcd.printIn("Message Deleted");
  MsgNumber--;
  delay(250);
  CallMsg();
}

if ((key == 1) && (ConnectedState == false)){
   //Serial.println("Not Connected!!");
    NotConnectedError();
    } 

if ((key == 1)  && (DelMode != true) && (ConnectedState == true)) {
  //If we are connected AND not in delete mode, then disply email info
  ButtonMillis = millis();
  GetTotalMsgs();
  delay(200);
  //Serial.println("UP button push detected");
  MsgNumber--; //decrements the message number
  if (MsgNumber <= 1){ //If we decrement down to 1, then go to the highest message number
     MsgNumber = MsgTotalNum;
   }
   //Serial.print("Total Messages = ");
   //Serial.println(MsgTotalNum);
   delay(150);
   CallMsg();
    }

if ((key == 1) && (DelMode == true)&& (ConnectedState == true)){
  //If we're in delete mode, then this cancels the deletion.
    ButtonMillis = millis();
  //Serial.println("UP button push detected");
  //Serial.print("Cancelling deletion");
  //Serial.println(MsgNumber);
   //Serial.println("DelMode is set to false");
  DelMode = false;
  delay(200);
  lcd.clear();
  lcd.printIn("Delete Cancelled");
  CallMsg();
  }

if ((key == 0) && (ConnectedState == false)){
  //Serial.println("Not Connected!!");
    NotConnectedError();
    } 

if ((key == 0) && (ConnectedState == true)) {
//Finally, if we are connected then this is the delete button.
  DelMode = true; //triggers delete mode
  //Serial.println("DelMode is set to True");
  //prints instructions for deleting or cancelling.
  lcd.clear();
  lcd.cursorTo(1,0);
  lcd.printIn("Delete Message?");
  lcd.cursorTo(2,0);
  lcd.printIn("UP=N  /  DN=Y");
  }
}

//======================Functions=====================//

// Convert ADC value to key number
int get_key(unsigned int input)
{
    int k; 
    for (k = 0; k < NUM_KEYS; k++)
    {
        if (input < adc_key_val[k])
        {
          return k;
        }
    }   
    if (k >= NUM_KEYS)
        k = -1;     // No valid key pressed  
    return k;
}

//Deletes a message
void DelMsg(){
     //Serial.print("Deleting MsgNumber = ");
     //Serial.println(MsgNumber);
     client.print ("D.");
     client.println(MsgNumber);
     lcd.printIn("Message Deleted");
     delay(150); 
}

//Sends the command to the PHP script to get the from/subject info for the current message number
void CallMsg(){
     //Serial.print("MsgNumber = ");
     //Serial.println(MsgNumber);
     client.print ("S.");
     client.println(MsgNumber);
     delay(150); 
     GetFrmSubj();
}

void GetFrmSubj(){
  //parses out the from/subject info from the PHP return info
     if(finder.find("MSG") == true ){
       //Uses the TextFinder library to locate the from/subject info.
           for (int z = 0; z <= 32; z++) {
             delay(20);
             char c = client.read(); 
             if ((c > 31) && (c < 128)){
             SubjRecd[z] = c;
             }
           }
           Print2LCD();
           client.flush();
     }
     else if (finder.find("MSG") != true) {
       //If the from/subj can't be retreived, then display an error.
       //Usually a transient authentication error on the PHP side.
       //Serial.println("Didn't get it!");
       lcd.clear();
       lcd.printIn("Error getting Msg");  
       client.flush();
     }
}
   
void Print2LCD(){
  //prints the from/subject info to the LCD
  lcd.clear();
  lcd.cursorTo(1,0);
  for (int y = 0; y <= 15; y++) {
    lcd.print(SubjRecd[y]);
    //Serial.print(SubjRecd[y]);
    }
  lcd.cursorTo(2,0);
  //Serial.println();
  for (int z = 16; z <= 32; z++){
    lcd.print(SubjRecd[z]);
    //Serial.print(SubjRecd[z]);
                    }
  client.flush();
}

void GetTotalMsgs () {
  //Gets the total message number, which is stored as both an array & an integer
   client.println("N");
   delay(150);
   if(finder.find("Total Number of Messages:") == true )  {   
     for (int w = 0; w <= 2; w++) {
        delay(20);
        char c = client.read(); 
        if ((c > 31) && (c < 128)){
        TotalMsgCharAr[w] = c;
       }
  }
  MsgTotalNum = atoi(TotalMsgCharAr); //Use ASCII to Integer to convert the array.
  }
}

void NotConnectedError(){
  //Simple error message
  lcd.clear();
  lcd.cursorTo(1,0);
  lcd.printIn("Not Connected"); 
  lcd.cursorTo(2, 0);  //line=2, x=0 
  lcd.printIn("Press S to conct");
}

void PrintTotalMsgs(){
  //This takes the total message array & prints it to the LCD.
  //Note that is will max out at 99 messages
    lcd.clear();
    lcd.printIn("Number of emails"); 
    lcd.cursorTo(2, 0);  //line=2, x=0 
    //Serial.print("Total messages = ");
    //Serial.println(MsgTotalNum);
       for (int d = 0; d <= 1; d++){
         if ((TotalMsgCharAr[d] >= 48) && (TotalMsgCharAr[d] <= 57)){
         lcd.print(TotalMsgCharAr[d]);
         }
      }
}

So what?


Well, why bother doing all this since I can, of course, much more easily delete spam from my Inbox with a regular email client or my iPhone? Well, as with my previous Arduino/POP3 interfacing project, I like to have a running visual indication of my email load, and, of course, the stock answer is "Why not? It's my time to waste!". Actually, email is quite lightweight an ubiquitious and so could be easily used for remote control. We web-based control would be more elegant, but not always accessible via a remote device for whatever reason.

About 10 years ago I build a device that interfaced with the PC parallel port and then wrote a Visual Basic program that received email and interpreted the subject line to do basic remote control via a couple of solid state relays. It could turn on or off simple AC devices with email and worked like a charm. With an Arduino, you could have a light sensor that then could tell if the light is, in fact, on and send an SMTP message (via the PHP script) to confirm that the action had been taken.

Next steps...


Next up, I should get this properly working with Gmail - possibly using the PHP classes available for IMAP rather than POP3.I also hope to get one of those new-fangled Wi-fi WiShield things and try to take this wireless!

Another future project may be to use the Twilio web-based API for voice-enabling applications to interface to an Arduino (check it out here). I have played with Twilio a bit and using a PHP pre-processor should make it quite easy to interface to an Arduino. This would allow a simple IVR-type remote control & monitoring - "Press 1 to turn on your lights, Press 2 to hear the temperature." I will try and not make it five months between posts!

3 comments:

  1. Awesome post, would love to see your Twilio Arduino implementation! Let us know if we can help :)

    -jeff
    Twilio.com

    ReplyDelete
  2. Thanks! It took me five months to get this done, so don't hold your breath! :-)

    I do see some good PHP examples on the Twilio website so I will start playing with those when I get a chance. Using a button off an Arduino to build a "click-to-call" button is an interesting idea.

    ReplyDelete
  3. OK, Jeff - Check out my latest post!

    ReplyDelete