Monday, May 3, 2010

Arduino Email Manager - Part 2 - The Preprocessor

I freely admit that I am not the greatest programmer in the world. Never having been formally trained, my programs tend to be rather loopy and don't include all the error checking they should. This PHP script that I have written is a case in point. It will work with my POP email provider (Rogers in Canada), but it may NOT work with your provider! It seems like there are minor variations in the way some POP servers will respond to the standard commands and these can throw off the script slightly. The best bet is to log into your POP server via telnet and carefully watch the messages back and forth.

Also note that this will support SSL (if you have it set up with your PHP installation) just by putting "ssl:/" in front of the host name and using whatever port number has been assigned - usually 995.You can manually log into an SSL-enabled account using the OpenSSL client available from OpenSSL.org instead of telnet.

At this point, the script does not support Gmail, so don't write to me about that! I have been trying to get it to work and you can definitely log in, but it seems to give inconsistent results and the mail total includes both the inbox and sent folder somehow (which doesn't make sense to me). If anyone out there knows the trick with Gmail, please let me know. I see that there are some PHP classes available for accessing Gmail via IMAP and I will investigate these and post revised code when I can.

So, how does this script work? The script needs to run on a server with the current version of PHP running. This could be a Mac or a Linux/Windows box or it could be running on a remote webserver - assuming you have all the networking and firewall issues dealt with. There is a new project called Yaler that may help with the networking issues if your script is remotely hosted - check it out here. You need to start the script before pressing the "Connect" (or Select) button on the Arduino.

When running, the Preprocessor is a "host" for the requests from the Arduino, but is in turn acting as a "client" to the POP server. It maintains two separate PHP sockets - one to the Arduino and one to the POP server.



The script implements a very simple protocol with the Arduino:

Arduino Sends...
Preprocessor returns...
Arduino connects to IP/portthe ASCII "C"
NCurrent number of emails on the POP server.
S.x (where x is an integer)The from/subject information for email number "x" prefixed with MSG.
D.x (where x is an integer)Deletes email number "x".
EKills the socket and ends session - currently not implemented.

The script also provides the following error messages back (although I am not currently using these on the Arduino):

  • X.1 - Could not open socket to POP server
  • X.2 - Error from server
  • X.3 - Authentication failure
  • X.4 - Bad connection

I tend to prefer one letter protocols with the Arduino since it simplifies the text handling on the Arduino side.

And, without further ado, here is the code...

<?php

/*
*  PHP Pre-processor script for Arduino POP Mail Manager
*
* this is the pre-processor script for use with the Arduino email
* manager described on my blog. Complete description at:
* http://opensourceprojects-torchris.blogspot.com/
*
* This script can be used with SSL enabled POP services by putting
* "ssl:/" infront of the host name. It does NOT currently work with
* GMail POP service.
*
* This code is in the public domain. Please provide credit if it is used
* in another project.
*
* written by Chris Armour, Arpil 30th, 2010
*
*/

//=============================IP, Port, User Info=================//
//Modify to suit your network.

define("HOST_POP", "your.popserver..com");
define("PORT_POP", "110");
define("USER_POP", "user.name");
define("PASS_POP", "YourPassword");
//Server settings
$host_ard = "192.168.0.171"; //This is the IP of the server running the script.
$port_ard = "12345"; //Port number being used by the Arduino


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

//GetMailCount grabs the number of emails in the mailbox using the STAT command
function GetMailCount()
{
$fp_pop = fsockopen (HOST_POP, PORT_POP, $errno, $errstr);
    // if a handle is not returned
    ob_implicit_flush();
    if (!$fp_pop)
        {
        echo("Error: could not open socket connection\n");
        return("X.1");
        }
    else
        {
        // get the welcome message
        $welcome = fgets ($fp_pop, 150);
        // check for success code
        if (substr($welcome, 0, 3) == "+OK")
        {
        // send username and read response
    fputs ($fp_pop, "user " . USER_POP . "\n");
    //fgets($fp_pop, 50);
    // send password and read response
    fputs ($fp_pop, "pass " . PASS_POP . "\n");
    $ack = fgets($fp_pop, 50);
    // check for success code
    if (substr($ack, 0, 3) == "+OK")
        {
        // send status request and read response
        fputs ($fp_pop, "STAT\n");
        $status = fgets($fp_pop, 50);
    if (substr($status, 0, 3) == "+OK")
        {
        // shut down connection
        fputs ($fp_pop, "QUIT\n");
        fclose ($fp_pop);
        }
// error getting status
    else  {
        echo ("Error - server said: $status");
        return("X.2");
        }
        }
    // auth failure
    else  {
        echo ("Error - server said: $ack");
        return("X.3");
        }
        }
        // bad welcome message
    else {
        echo ("Error - bad connection string\n");
        return("X.4");
        }
// get status string
// split by spaces
$arr = explode(" ", $status);
// the second element contains the total number of messages
echo $arr[3] . " messages in mailbox\n";
$GotMail = $arr[3];
return $GotMail;
}
}

//GetFromSubj grabs the content of the email MailNumber then passes this back to the main loop for extraction of the From & subject
//You may need to check the output from your POP3 server using telnet to check on the responses. This seems to vary from POP server to POP
//server.
function GetFromSubj($MailNumber){
$fp_pop = fsockopen (HOST_POP, PORT_POP, $errno, $errstr);
    // if a handle is not returned
    ob_implicit_flush();
    if (!$fp_pop)
        {
        echo("Error: could not open socket connection\n");
        return("X.1");
        }
    else
        {
        // get the welcome message
        $welcome = fgets ($fp_pop, 150);
        // check for success code
        if (substr($welcome, 0, 3) == "+OK")
        {
        // send username and read response
    fputs ($fp_pop, "user " . USER_POP . "\n");
    fgets($fp_pop);
    // send password and read response
    fputs ($fp_pop, "pass " . PASS_POP . "\n");
   $ack = fgets($fp_pop);
    // check for success code
    if (substr($ack, 0, 3) == "+OK")
       {
        // send request for email content & read response

        fputs ($fp_pop, "retr " . $MailNumber . "\n");
        $EmailContent = fread($fp_pop, 4096);
//        echo $EmailContent;
    if (substr($EmailContent, 0, 3) == "+OK")
        {
        // shut down connection
        return $EmailContent;
        fputs ($fp_pop, "QUIT\n");
        fclose ($fp_pop);
        }
// error getting status
    else
        {
        echo ("Error - Server said: $EmailContent");
        return("X.2");
        }
        }
    // auth failure
    else
        {
        echo ("Error - Server said: $ack");
        return("X.3");
        }
        }
        // bad welcome message
    else
        {
        echo ("Error - Bad connection string\n");
        return("X.4");
        }
}
}

//MsgDelete delets message # MailNumber using the dele command
function MsgDelete($MailNumber){

$fp_pop = fsockopen (HOST_POP, PORT_POP, $errno, $errstr);
    // if a handle is not returned
    ob_implicit_flush();
    if (!$fp_pop)
        {
        echo("Error: could not open socket connection\n");
        return("X.1");
        }
    else
        {
        // get the welcome message
        $welcome = fgets ($fp_pop, 150);
        // check for success code
        if (substr($welcome, 0, 3) == "+OK")
        {
        // send username and read response
    fputs ($fp_pop, "user " . USER_POP . "\n");
    fgets($fp_pop);
    // send password and read response
    fputs ($fp_pop, "pass " . PASS_POP . "\n");
   $ack = fgets($fp_pop);
    // check for success code
    if (substr($ack, 0, 3) == "+OK")
       {
        // send delete command

        fputs ($fp_pop, "dele " . $MailNumber . "\n");
        $DeleteAck = fgets($fp_pop);
//        echo $EmailContent;
    if (substr($DeleteAck, 0, 3) == "+OK")
        {
        // shut down connection
        echo ("Message " . $MailNumber . " deleted. \n");
        fputs ($fp_pop, "QUIT\n");
        fclose ($fp_pop);
        }
// error getting status
   else
        {
        echo ("Error - Server said: $EmailContent");
        return ("X.2");
        }
        }
    // auth failure
    else
        {
        echo ("Error - Server said: $ack");
        return("X.3");
        }
        }
        // bad welcome message
    else
        {
        echo ("Error - Bad connection string\n");
        return("X.4");
        }
        }
}

//=====================Create Socket to listen for Arduino======================//

ob_implicit_flush();

//Open socket to listen for Arduino
$socket_ard = socket_create(AF_INET, SOCK_STREAM, 0) or die ("Could not bind to socket\n");
$result_ard = socket_bind($socket_ard, $host_ard, $port_ard) or die("Could not bind to socket\n");

// start listening for connections
$result_ard = socket_listen($socket_ard, 3) or die("Could not set up socket listener\n");
// accept incoming connections
// spawn another socket to handle communication
$spawn = socket_accept($socket_ard) or die("Could not accept incoming connection\n");
// read client input
socket_write($spawn,"C\n");
echo "Device connected.\n";

//=====================Main Socket loop======================//
do {

$input = socket_read($spawn, 2048, PHP_NORMAL_READ) or die("Could not read input\n");

echo $input;

if (trim($input[0]) == "N" || trim($input[0]) == "S" || trim($input[0]) == "D" || trim($input[0]) == "E" || trim($input[0]) == "\r\n") {
//Only do anything if N, S, D or E received.

    if (trim($input) == "N"){
        // This gets the mailcount using the GetMailCount function.
        $MailCount = GetMailCount();
        socket_write($spawn, "Total Number of Messages:" . $MailCount . "@");
        }

    if (trim($input[0]) == "S") {
        //This gets the from/subject info is an S.num is received.
       $MsgNum = explode (".",$input);
       //Use explode to break the info from the Arduino into an array.
        $MsgNumber = $MsgNum[1];
        //Get the Message number from the array.
        echo ("Value of MsgNumber= " . $MsgNumber . "\n");
        $MsgFromSubj = GetFromSubj($MsgNumber);
        //run the function to get the info from the POP server
            if ($MsgFromSubj[0] == "X")
            {
            //If there's an error, write back the server error text.
                socket_write($spawn,$MsgFromSubj);
            }
        else
            {
            $MsgFromLocn = strpos($MsgFromSubj, "From: ");
            $MsgFromName = substr($MsgFromSubj, ($MsgFromLocn + 6), 14);
            print ( "MSG". "F:" . $MsgFromName );
            $MsgSubjLocn = strpos($MsgFromSubj, "Subject: ");
            $MsgSubj = substr($MsgFromSubj, ($MsgSubjLocn + 9), 14);
            echo ("S:" . $MsgSubj );
            usleep(20000);
            socket_write ($spawn,  "MSG" . "F:" . $MsgFromName . "S:" . $MsgSubj . "\n");
            }
        }

    if (trim($input[0]) == "D") {
    //This sends the command to delete an email.
       $MsgNum = explode (".",$input);
        $MsgNumber = $MsgNum[1];
        $MsgDelRet = MsgDelete($MsgNumber);
        if ($MsgDelRet[0] == "X")
            {
                socket_write($spawn,$MsgDelRet ."\n");
            }
        else
            {
        socket_write($spawn, "D." . $MsgNum[1]);
        }
    }

    if (trim($input) == "E"){
  //This isn't actually currently used by the Arduino.
        socket_shutdown($spawn, 2);
        usleep(1000);
        socket_close($spawn);
        socket_shutdown($socket_ard, 2);
        usleep(1000);
        socket_close($socket_ard);
        echo "Sockets terminated\n";
 //       break;
        }
}

} while(true);

?>

Note that this code does not provide good handling of disconnect/reconnect nor can it handle multiple connections simultaneously. In other words, if the Arduino is reset, then the script needs to be restarted. Also, if multiple connection requests are received, it will just die. If anyone can improve on this, please make the suggestions.

Next up will be the Arduino code!

No comments:

Post a Comment