Thursday, December 12, 2013

Control the Raspberry PI through email, in Java

The Problem

You'll find many articles showing how to use a web server like Apache to control your Raspberry PI over the Internet.
What they don't say - or don't say loud enough - is that to do so, your web server needs to be visible to (and from) the outside world.
Otherwise, you cannot access it outside of your home or ad-hoc network.
What those articles present is a nice way to:
  • Know what the conditions are on the Raspberry PI
  • Ask remotely to the Raspberry PI to take some action
For example
You are at work, the Raspberry PI is at home.
Your Raspberry PI has a temperature sensor connected to it, and it is also connected to some heater switch.
  • When the temperature in the home drops below a given threshold, it sends you a message (through whatever channel, WebSocket or so)
  • On reception of the message, you - wherever you are - decide to ask the Raspberry PI to turn the heater on
To do so, you need to be on the same network as the Raspberry PI, or the network the Raspberry PI is on needs to be visible from your location.
This does not happen if you don't have a registered IP address for your Raspberry PI.
It kind of kills the story...

A Solution

A way to address this problem is to use emails.
This will not implement real-time messaging - like WebSocket - but this will allow a form of communication, only relying on email service - like Yahoo! or Google. Those providers offer free email accounts, which is all you would need.

Required Components

A way to address this problem is to All you need on the client side (ie you, at work) is a browser, from which you can send and receive emails.
On the server side (ie the Raspberry PI, at home) you will need: The json parser is used to parse the content of the email messages. We choosed this technology in this example; others can be used as well, like XML, plain text messages, etc. Your call.

The Java code

All the sources are available in an archive you can get at the bottom of this document.
email.properties
All properties concerning the emails accounts are externalized in this properties file.
Lines 1 to 5 concern the origins, and the destinations of the emails going back and forth, as well as the subjects used for the emails (events, acknowledgements, and requests).
Notice the URL at the top of the file (line 7), it could help you to find the servers you are interested in.
pi4j.gpio.RaspberryPIEventListener.java
This is the interface used for the callback invoked when an event is triggered on the Reaspberry Pi's side. Namely, when the button will be pressed.
      

     1  package pi4j.gpio;
     2  
     3  import com.pi4j.io.gpio.event.GpioPinDigitalStateChangeEvent;
     4  
     5  public interface RaspberryPIEventListener
     6  {
     7    public void manageEvent(GpioPinDigitalStateChangeEvent event);
     8  }
      
      
pi4j.email.EmailReceiver.java
This is the utility class used to receive emails. Its behavior heavily depends on the values set in email.properties.
Do check out the method named setProps() (line 119). It might need to be tweaked to fit your email environment.
      

     1  package pi4j.email;
     2  
     3  import java.io.BufferedReader;
     4  import java.io.File;
     5  import java.io.FileInputStream;
     6  import java.io.FileOutputStream;
     7  import java.io.IOException;
     8  import java.io.InputStream;
     9  import java.io.InputStreamReader;
    10  import java.io.OutputStream;
    11  
    12  import java.util.ArrayList;
    13  import java.util.List;
    14  import java.util.Properties;
    15  import java.util.Set;
    16  
    17  import javax.mail.Address;
    18  import javax.mail.Flags;
    19  import javax.mail.Folder;
    20  import javax.mail.Message;
    21  import javax.mail.Multipart;
    22  import javax.mail.Part;
    23  import javax.mail.PasswordAuthentication;
    24  import javax.mail.Session;
    25  import javax.mail.Store;
    26  import javax.mail.internet.InternetAddress;
    27  import javax.mail.search.AndTerm;
    28  import javax.mail.search.FlagTerm;
    29  import javax.mail.search.FromStringTerm;
    30  import javax.mail.search.OrTerm;
    31  import javax.mail.search.SearchTerm;
    32  import javax.mail.search.SubjectTerm;
    33  
    34  public class EmailReceiver
    35  {
    36    private static String protocol;
    37    private static int outgoingPort;
    38    private static int incomingPort;
    39    private static String username;
    40    private static String password;
    41    private static String outgoing;
    42    private static String incoming;
    43    private static String replyto;
    44    private static boolean smtpauth;
    45    
    46    private static String sendEmailsTo;
    47    private static String acceptEmailsFrom;
    48    private static String acceptSubject;
    49    private static String ackSubject;
    50    
    51    private static boolean verbose = "true".equals(System.getProperty("verbose", "false"));
    52  
    53    private EmailSender emailSender = null; // For Ack
    54    private String provider = null;
    55    
    56    public EmailReceiver(String provider) throws RuntimeException
    57    {
    58      this.provider = provider;
    59      EmailReceiver.protocol = "";
    60      EmailReceiver.outgoingPort = 0;
    61      EmailReceiver.incomingPort = 0;
    62      EmailReceiver.username = "";
    63      EmailReceiver.password = "";
    64      EmailReceiver.outgoing = "";
    65      EmailReceiver.incoming = "";
    66      EmailReceiver.replyto = "";
    67      EmailReceiver.smtpauth = false;
    68      
    69      EmailReceiver.sendEmailsTo = "";
    70      EmailReceiver.acceptEmailsFrom = "";
    71      EmailReceiver.acceptSubject = "";
    72      EmailReceiver.ackSubject = "";
    73  
    74      Properties props = new Properties();
    75      String propFile = "email.properties";
    76      try
    77      {
    78        FileInputStream fis = new FileInputStream(propFile);
    79        props.load(fis);
    80      }
    81      catch (Exception e)
    82      {
    83        System.out.println("email.properies file problem...");
    84        throw new RuntimeException("File not found:email.properies");
    85      }
    86      EmailReceiver.sendEmailsTo     = props.getProperty("pi.send.emails.to");
    87      EmailReceiver.acceptEmailsFrom = props.getProperty("pi.accept.emails.from");
    88      EmailReceiver.acceptSubject    = props.getProperty("pi.email.subject");
    89      EmailReceiver.ackSubject       = props.getProperty("pi.ack.subject");
    90      
    91      EmailReceiver.protocol     = props.getProperty("pi." + (provider != null ? (provider + ".") : "") + "mail.protocol");
    92      EmailReceiver.outgoingPort = Integer.parseInt(props.getProperty("pi." + (provider != null ? (provider + ".") : "") + "outgoing.server.port", "0"));
    93      EmailReceiver.incomingPort = Integer.parseInt(props.getProperty("pi." + (provider != null ? (provider + ".") : "") + "incoming.server.port", "0"));
    94      EmailReceiver.username     = props.getProperty("pi." + (provider != null ? (provider + ".") : "") + "mail.username",   "");
    95      EmailReceiver.password     = props.getProperty("pi." + (provider != null ? (provider + ".") : "") + "mail.password",   "");
    96      EmailReceiver.outgoing     = props.getProperty("pi." + (provider != null ? (provider + ".") : "") + "outgoing.server", "");
    97      EmailReceiver.incoming     = props.getProperty("pi." + (provider != null ? (provider + ".") : "") + "incoming.server", "");
    98      EmailReceiver.replyto      = props.getProperty("pi." + (provider != null ? (provider + ".") : "") + "mail.replyto",    "");
    99      EmailReceiver.smtpauth     = "true".equals(props.getProperty("pi." + (provider != null ? (provider + ".") : "") + "mail.smtpauth", "false"));
   100      
   101      if (verbose)
   102      {
   103        System.out.println("Protocol:" + EmailReceiver.protocol);
   104        System.out.println("Usr/pswd:" + EmailReceiver.username + "/" + EmailReceiver.password);
   105      }
   106    }
   107  
   108    private static SearchTerm[] buildSearchTerm(String str)
   109    {
   110      String[] sa = str.split(",");
   111      List<SearchTerm> lst = new ArrayList<SearchTerm>();
   112      for (String s : sa)
   113        lst.add(new FromStringTerm(s.trim()));
   114      SearchTerm[] sta = new SearchTerm[lst.size()];
   115      sta = lst.toArray(sta);
   116      return sta;
   117    }
   118    
   119    private Properties setProps()
   120    {
   121      Properties props = new Properties();
   122      props.put("mail.debug", verbose?"true":"false");
   123      
   124      // TASK smtp should be irrelevant for a receiver
   125      props.put("mail.smtp.host", EmailReceiver.outgoing);
   126      props.put("mail.smtp.port", Integer.toString(EmailReceiver.outgoingPort));
   127  
   128      props.put("mail.smtp.auth", "true");
   129      props.put("mail.smtp.starttls.enable", "true"); //  See http://www.oracle.com/technetwork/java/faq-135477.html#yahoomail
   130    //  props.put("mail.smtp.starttls.required", "true");
   131      props.put("mail.smtp.ssl.enable", "true");
   132  
   133      if ("pop3".equals(EmailReceiver.protocol))
   134      {
   135        props.setProperty("mail.pop3.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
   136        props.setProperty("mail.pop3.socketFactory.fallback", "false");
   137        props.setProperty("mail.pop3.port", Integer.toString(EmailReceiver.incomingPort));
   138        props.setProperty("mail.pop3.socketFactory.port", Integer.toString(EmailReceiver.incomingPort));
   139      }            
   140  
   141      if ("imap".equals(protocol))
   142      {
   143        props.setProperty("mail.imap.starttls.enable", "false");
   144        // Use SSL
   145        props.setProperty("mail.imap.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
   146        props.setProperty("mail.imap.socketFactory.fallback", "false");
   147    
   148        props.setProperty("mail.imap.port", Integer.toString(EmailReceiver.incomingPort));
   149        props.setProperty("mail.imap.socketFactory.port", Integer.toString(EmailReceiver.incomingPort));
   150    
   151        props.setProperty("mail.imaps.class", "com.sun.mail.imap.IMAPSSLStore");
   152      }
   153      return props;
   154    }
   155  
   156    public boolean isAuthRequired()
   157    {
   158      return EmailReceiver.smtpauth;
   159    }
   160  
   161    public String getUserName()
   162    {
   163      return EmailReceiver.username;
   164    }
   165  
   166    public String getPassword()
   167    {
   168      return EmailReceiver.password;
   169    }
   170  
   171    public String getReplyTo()
   172    {
   173      return EmailReceiver.replyto;
   174    }
   175  
   176    public String getIncomingServer()
   177    {
   178      return EmailReceiver.incoming;
   179    }
   180  
   181    public String getOutgoingServer()
   182    {
   183      return EmailReceiver.outgoing;
   184    }
   185  
   186    public List<String> receive()
   187      throws Exception
   188    {
   189      return receive(null);
   190    }
   191    
   192    public List<String> receive(String dir)
   193      throws Exception
   194    {
   195      if (verbose) System.out.println("Receiving...");
   196      List<String> messList = new ArrayList<String>();
   197      Store store = null;
   198      Folder folder = null;
   199      try
   200      {
   201    //  Properties props = System.getProperties();
   202        Properties props = setProps();
   203        
   204        if (verbose)
   205        {
   206          Set<Object> keys = props.keySet();
   207          for (Object o : keys)
   208            System.out.println(o.toString() + ":" + props.get(o).toString());
   209        }
   210        if (verbose) System.out.println("Getting session...");
   211  //    Session session = Session.getInstance(props, null);
   212        Session session = Session.getInstance(props,
   213                          new javax.mail.Authenticator() 
   214                          {
   215                            protected PasswordAuthentication getPasswordAuthentication() 
   216                            {
   217                              return new PasswordAuthentication(username, password);
   218                            }
   219                          });    
   220        session.setDebug(verbose);
   221        if (verbose) System.out.println("Session established.");
   222        store = session.getStore(EmailReceiver.protocol);
   223        if (EmailReceiver.incomingPort == 0)
   224          store.connect(EmailReceiver.incoming, EmailReceiver.username, EmailReceiver.password);
   225        else
   226          store.connect(EmailReceiver.incoming, EmailReceiver.incomingPort, EmailReceiver.username, EmailReceiver.password);
   227        if (verbose) System.out.println("Connected to store");        
   228        folder = store.getDefaultFolder();
   229        if (folder == null)
   230          throw new RuntimeException("No default folder");
   231  
   232        folder = store.getFolder("INBOX");
   233        if (folder == null)
   234          throw new RuntimeException("No INBOX");
   235  
   236        folder.open(Folder.READ_WRITE);
   237        if (verbose) System.out.println("Connected... filtering, please wait.");
   238        SearchTerm st = new AndTerm(new SearchTerm[] { new OrTerm(buildSearchTerm(sendEmailsTo)), 
   239                                                       new SubjectTerm(acceptSubject),
   240                                                       new FlagTerm(new Flags(Flags.Flag.SEEN), false) });
   241     // st = new SubjectTerm("PI Request");
   242        Message msgs[] = folder.search(st);
   243  //    Message msgs[] = folder.getMessages();
   244  
   245        if (verbose) System.out.println("Search completed, " + msgs.length + " message(s).");
   246        for (int msgNum=0; msgNum<msgs.length; msgNum++)
   247        {
   248          try
   249          {
   250            Message mess = msgs[msgNum];
   251            Address from[] = mess.getFrom();
   252            String sender = "";
   253            try
   254            {
   255              sender = from[0].toString();
   256            }
   257            catch(Exception exception) 
   258            {
   259              exception.printStackTrace();
   260            }
   261  //        System.out.println("Message from [" + sender + "], subject [" + subject + "], content [" + mess.getContent().toString().trim() + "]");
   262            
   263            if (true)
   264            {
   265              if (!mess.isSet(javax.mail.Flags.Flag.SEEN) && 
   266                  !mess.isSet(javax.mail.Flags.Flag.DELETED))
   267              {
   268                String txtMess = printMessage(mess, dir);
   269                messList.add(txtMess);
   270                mess.setFlag(javax.mail.Flags.Flag.SEEN, true);
   271                mess.setFlag(javax.mail.Flags.Flag.DELETED, true);
   272                // Send an ack - by email.
   273                if (this.emailSender == null)
   274                  this.emailSender = new EmailSender(this.provider);
   275                this.emailSender.send(new String[] { sender }, 
   276                                      ackSubject, 
   277                                      "Your request [" + txtMess.trim() + "] is being taken care of.");
   278                if (verbose) System.out.println("Sent an ack to " + sender);
   279              } 
   280              else
   281              {
   282                if (verbose) System.out.println("Old message in your inbox..., received " + mess.getReceivedDate().toString());
   283              }
   284            }
   285          }
   286          catch(Exception ex)
   287          {
   288  //        System.err.println(ex.getMessage());
   289            ex.printStackTrace();
   290          }
   291        }
   292      }
   293      catch(Exception ex)
   294      {
   295        throw ex;
   296      }
   297      finally
   298      {
   299        try
   300        {
   301          if (folder != null)
   302            folder.close(true);
   303          if (store != null)
   304            store.close();
   305        }
   306        catch(Exception ex2)
   307        {
   308          System.err.println("Finally ...");
   309          ex2.printStackTrace();
   310        }
   311      }
   312      return messList;
   313    }
   314  
   315    public static String printMessage(Message message, String dir)
   316    {
   317      String ret = "";
   318      try
   319      {
   320        String from = ((InternetAddress)message.getFrom()[0]).getPersonal();
   321        if(from == null)
   322          from = ((InternetAddress)message.getFrom()[0]).getAddress();
   323        if (verbose) System.out.println("From: " + from);
   324        String subject = message.getSubject();
   325        if (verbose) System.out.println("Subject: " + subject);
   326        Part messagePart = message;
   327        Object content = messagePart.getContent();
   328        if (content instanceof Multipart)
   329        {
   330  //      messagePart = ((Multipart)content).getBodyPart(0);
   331          int nbParts = ((Multipart)content).getCount();
   332          if (verbose) System.out.println("[ Multipart Message ], " + nbParts + " part(s).");
   333          for (int i=0; i<nbParts; i++)
   334          {
   335            messagePart = ((Multipart)content).getBodyPart(i);
   336            if (messagePart.getContentType().toUpperCase().startsWith("APPLICATION/OCTET-STREAM"))
   337            {
   338              if (verbose) System.out.println(messagePart.getContentType() + ":" + messagePart.getFileName());
   339              InputStream is = messagePart.getInputStream();
   340              String newFileName = "";
   341              if (dir != null)
   342                newFileName = dir + File.separator;
   343              newFileName += messagePart.getFileName();
   344              FileOutputStream fos = new FileOutputStream(newFileName);
   345              ret = messagePart.getFileName();
   346              if (verbose) System.out.println("Downloading " + messagePart.getFileName() + "...");
   347              copy(is, fos);
   348              if (verbose) System.out.println("...done.");
   349            } 
   350            else // text/plain, text/html
   351            {
   352              if (verbose) System.out.println("-- Part #" + i + " --, " + messagePart.getContentType().replace('\n', ' ').replace('\r', ' ').replace("\b", "").trim());
   353              InputStream is = messagePart.getInputStream();
   354              BufferedReader br = new BufferedReader(new InputStreamReader(is));
   355              String line = "";
   356              while (line != null)
   357              {
   358                line = br.readLine();
   359                if (line != null)
   360                {
   361                  if (verbose) System.out.println("[" + line + "]");
   362                  if (messagePart.getContentType().toUpperCase().startsWith("TEXT/PLAIN"))
   363                    ret += line;
   364                }
   365              }
   366              br.close();
   367              if (verbose) System.out.println("-------------------");
   368            }
   369          }
   370        }
   371        else
   372        {
   373  //      System.out.println("  .Message is a " + content.getClass().getName());
   374  //      System.out.println("Content:");
   375  //      System.out.println(content.toString());
   376          ret = content.toString();
   377        }
   378        if (verbose) System.out.println("-----------------------------");
   379      }
   380      catch(Exception ex)
   381      {
   382        ex.printStackTrace();
   383      }
   384      return ret;
   385    }
   386  
   387    private static void copy(InputStream in, OutputStream out)
   388      throws IOException
   389    {
   390      synchronized(in)
   391      {
   392        synchronized(out)
   393        {
   394          byte buffer[] = new byte[256];
   395          while (true)
   396          {
   397            int bytesRead = in.read(buffer);
   398            if(bytesRead == -1)
   399              break;
   400            out.write(buffer, 0, bytesRead);
   401          }
   402        }
   403      }
   404    }
   405  }
      
        
pi4j.email.EmailSender.java
Same as the previous, but for outgoing emails. Notice that the two can be different. You might very well send emails using Yahoo! and receive others using Google.
This way, the Raspberry PI and you would have your own email addresses.
As before, check out the method named setProps() (line 164). It might need to be tweaked to fit your email environment.
      

     1  package pi4j.email;
     2  
     3  import com.sun.mail.smtp.SMTPTransport;
     4  
     5  import java.io.FileInputStream;
     6  
     7  import java.util.Properties;
     8  
     9  import javax.mail.Message;
    10  import javax.mail.MessagingException;
    11  import javax.mail.PasswordAuthentication;
    12  import javax.mail.Session;
    13  import javax.mail.Transport;
    14  import javax.mail.internet.AddressException;
    15  import javax.mail.internet.InternetAddress;
    16  import javax.mail.internet.MimeMessage;
    17  
    18  public class EmailSender
    19  {
    20    private static String protocol;
    21    private static int outgoingPort;
    22    private static int incomingPort;
    23    private static String username;
    24    private static String password;
    25    private static String outgoing;
    26    private static String incoming;
    27    private static String replyto;
    28    private static boolean smtpauth;
    29    
    30    private static String sendEmailsTo;
    31    private static String eventSubject;
    32    
    33    private static boolean verbose = "true".equals(System.getProperty("verbose", "false"));
    34  
    35    public EmailSender(String provider) throws RuntimeException
    36    {
    37      EmailSender.protocol = "";
    38      EmailSender.outgoingPort = 0;
    39      EmailSender.incomingPort = 0;
    40      EmailSender.username = "";
    41      EmailSender.password = "";
    42      EmailSender.outgoing = "";
    43      EmailSender.incoming = "";
    44      EmailSender.replyto = "";
    45      EmailSender.smtpauth = false;
    46      EmailSender.sendEmailsTo = "";
    47      EmailSender.eventSubject = "";
    48  
    49      Properties props = new Properties();
    50      String propFile = "email.properties";
    51      try
    52      {
    53        FileInputStream fis = new FileInputStream(propFile);
    54        props.load(fis);
    55      }
    56      catch (Exception e)
    57      {
    58        System.out.println("email.properies file problem...");
    59        throw new RuntimeException("File not found:email.properies");
    60      }
    61      EmailSender.sendEmailsTo = props.getProperty("pi.send.emails.to");
    62      EmailSender.eventSubject = props.getProperty("pi.event.subject");
    63      
    64      EmailSender.protocol     = props.getProperty("pi." + (provider != null ? (provider + ".") : "") + "mail.protocol");
    65      EmailSender.outgoingPort = Integer.parseInt(props.getProperty("pi." + (provider != null ? (provider + ".") : "") + "outgoing.server.port", "0"));
    66      EmailSender.incomingPort = Integer.parseInt(props.getProperty("pi." + (provider != null ? (provider + ".") : "") + "incoming.server.port", "0"));
    67      EmailSender.username     = props.getProperty("pi." + (provider != null ? (provider + ".") : "") + "mail.username",   "");
    68      EmailSender.password     = props.getProperty("pi." + (provider != null ? (provider + ".") : "") + "mail.password",   "");
    69      EmailSender.outgoing     = props.getProperty("pi." + (provider != null ? (provider + ".") : "") + "outgoing.server", "");
    70      EmailSender.incoming     = props.getProperty("pi." + (provider != null ? (provider + ".") : "") + "incoming.server", "");
    71      EmailSender.replyto      = props.getProperty("pi." + (provider != null ? (provider + ".") : "") + "mail.replyto",    "");
    72      EmailSender.smtpauth     = "true".equals(props.getProperty("pi." + (provider != null ? (provider + ".") : "") + "mail.smtpauth", "false"));
    73      
    74      if (verbose)
    75      {
    76        System.out.println("-------------------------------------");
    77        System.out.println("Protocol       : " + EmailSender.protocol);
    78        System.out.println("Usr/pswd       : " + EmailSender.username + "/" + EmailSender.password);
    79        System.out.println("Incoming server: " + EmailSender.incoming + ":" + EmailSender.incomingPort);
    80        System.out.println("Outgoing server: " + EmailSender.outgoing + ":" + EmailSender.outgoingPort);      
    81        System.out.println("replyto        : " + EmailSender.replyto);
    82        System.out.println("SMTPAuth       : " + EmailSender.smtpauth);
    83        System.out.println("-------------------------------------");
    84      }
    85    }
    86  
    87    public boolean isAuthRequired()
    88    {
    89      return EmailSender.smtpauth;
    90    }
    91  
    92    public String getUserName()
    93    {
    94      return EmailSender.username;
    95    }
    96  
    97    public String getPassword()
    98    {
    99      return EmailSender.password;
   100    }
   101  
   102    public String getReplyTo()
   103    {
   104      return EmailSender.replyto;
   105    }
   106  
   107    public String getIncomingServer()
   108    {
   109      return EmailSender.incoming;
   110    }
   111  
   112    public String getOutgoingServer()
   113    {
   114      return EmailSender.outgoing;
   115    }
   116    
   117    public String getEmailDest()
   118    {
   119      return EmailSender.sendEmailsTo;
   120    }
   121  
   122    public String getEventSubject()
   123    {
   124      return EmailSender.eventSubject;
   125    }
   126  
   127    public void send(String[] dest, 
   128                     String subject, 
   129                     String content)
   130      throws MessagingException, AddressException
   131    {
   132      Properties props = setProps();
   133      
   134  //  Session session = Session.getDefaultInstance(props, auth);
   135      Session session = Session.getInstance(props,
   136                                            new javax.mail.Authenticator() 
   137                                            {
   138                                              protected PasswordAuthentication getPasswordAuthentication() 
   139                                              {
   140                                                return new PasswordAuthentication(username, password);
   141                                              }
   142                                            });    
   143      session.setDebug(verbose);
   144      Transport tr = session.getTransport("smtp");
   145      if (!(tr instanceof SMTPTransport))
   146        System.out.println("This is NOT an SMTPTransport:[" + tr.getClass().getName() + "]");
   147  
   148      Message msg = new MimeMessage(session);
   149      msg.setFrom(new InternetAddress(EmailSender.replyto));
   150      if (dest == null || dest.length == 0)
   151        throw new RuntimeException("Need at least one recipient.");
   152      msg.setRecipient(javax.mail.Message.RecipientType.TO, new InternetAddress(dest[0]));
   153      for (int i=1; i<dest.length; i++)
   154        msg.addRecipient(javax.mail.Message.RecipientType.CC, new InternetAddress(dest[i]));
   155      msg.setSubject(subject);
   156      msg.setText(content != null ? content : "");
   157      msg.setContent(content, "text/plain");
   158      msg.saveChanges();
   159      if (verbose) System.out.println("sending:[" + content + "], " + Integer.toString(content.length()) + " characters");
   160  
   161      Transport.send(msg);
   162    }
   163  
   164    private Properties setProps()
   165    {
   166      Properties props = new Properties();
   167      props.put("mail.debug", verbose?"true":"false");
   168      props.put("mail.smtp.host", EmailSender.outgoing);
   169      props.put("mail.smtp.port", Integer.toString(EmailSender.outgoingPort));
   170  
   171      props.put("mail.smtp.auth", "true");
   172      props.put("mail.smtp.starttls.enable", "true"); //  See http://www.oracle.com/technetwork/java/faq-135477.html#yahoomail
   173  //  props.put("mail.smtp.starttls.required", "true");
   174      props.put("mail.smtp.ssl.enable", "true");
   175      return props;
   176    }
   177  }
      
      
pi4j.gpio.GPIOController.java
Interaction with the GPIO on the Raspberry PI.
Notice the pins we use:
  • Yellow led on pin #01 (line 23)
  • Green led on pin #04 (line 24)
  • Button on pin #02 (line 25)
We will need to make sure the wiring matches this setup.
      

     1  package pi4j.gpio;
     2  
     3  import com.pi4j.io.gpio.GpioController;
     4  import com.pi4j.io.gpio.GpioFactory;
     5  import com.pi4j.io.gpio.GpioPinDigitalInput;
     6  import com.pi4j.io.gpio.PinPullResistance;
     7  import com.pi4j.io.gpio.RaspiPin;
     8  import com.pi4j.io.gpio.event.GpioPinDigitalStateChangeEvent;
     9  import com.pi4j.io.gpio.event.GpioPinListenerDigital;
    10  
    11  public class GPIOController
    12  {
    13    private GpioController gpio = null;
    14    private OneLed yellowLed = null;
    15    private OneLed greenLed  = null;
    16    private GpioPinDigitalInput button = null;
    17    private RaspberryPIEventListener caller = null;
    18    
    19    public GPIOController(RaspberryPIEventListener listener)
    20    {
    21      this.caller = listener;
    22      this.gpio = GpioFactory.getInstance();
    23      this.yellowLed = new OneLed(this.gpio, RaspiPin.GPIO_01, "yellow");
    24      this.greenLed  = new OneLed(this.gpio, RaspiPin.GPIO_04, "green");    
    25      this.button = this.gpio.provisionDigitalInputPin(RaspiPin.GPIO_02, PinPullResistance.PULL_DOWN);
    26      this.button.addListener(new GpioPinListenerDigital() 
    27      {
    28        @Override
    29        public void handleGpioPinDigitalStateChangeEvent(GpioPinDigitalStateChangeEvent event) 
    30        {
    31          caller.manageEvent(event);
    32        }
    33      });
    34    }
    35      
    36    public void shutdown()
    37    {
    38      this.gpio.shutdown();
    39    }
    40    
    41    public void switchYellow(boolean on)
    42    {
    43      if (on)
    44        yellowLed.on();
    45      else
    46        yellowLed.off();
    47    }
    48    
    49    public void switchGreen(boolean on)
    50    {
    51      if (on)
    52        greenLed.on();
    53      else
    54        greenLed.off();
    55    }
    56  }
      
      
pi4j.gpio.OneLed.java
Utility class for led management.
      

     1  package pi4j.gpio;
     2  
     3  
     4  import com.pi4j.io.gpio.GpioController;
     5  import com.pi4j.io.gpio.GpioPinDigitalOutput;
     6  import com.pi4j.io.gpio.Pin;
     7  import com.pi4j.io.gpio.PinState;
     8  
     9  public class OneLed
    10  {
    11    private GpioPinDigitalOutput led = null;
    12    private String name;
    13    
    14    public OneLed(GpioController gpio, Pin pin, String name)
    15    {
    16      this.name = name;
    17      led = gpio.provisionDigitalOutputPin(pin, "Led", PinState.LOW);
    18    }
    19    
    20    public void on()
    21    {
    22      if ("true".equals(System.getProperty("verbose", "false")))
    23        System.out.println(this.name + " is on.");
    24      led.high();
    25    }
    26    
    27    public void off()
    28    {
    29      if ("true".equals(System.getProperty("verbose", "false")))
    30        System.out.println(this.name + " is off.");
    31      led.low();
    32    }
    33  }
      
      
pi4j.email.PIControllerMain.java
This is the main, the one to start from the Raspberry PI (see below)
      

     1  package pi4j.email;
     2  
     3  import com.pi4j.io.gpio.event.GpioPinDigitalStateChangeEvent;
     4  
     5  import java.text.SimpleDateFormat;
     6  
     7  import java.util.Date;
     8  import java.util.List;
     9  
    10  import org.json.JSONObject;
    11  
    12  import pi4j.gpio.GPIOController;
    13  import pi4j.gpio.RaspberryPIEventListener;
    14  
    15  public class PIControllerMain implements RaspberryPIEventListener
    16  {
    17    private final static SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MMM-dd HH:mm:ss");
    18    private static boolean verbose = "true".equals(System.getProperty("verbose", "false"));
    19  
    20    private static String providerSend    = "google";
    21    private static String providerReceive = "google";
    22    
    23    EmailSender sender = null;
    24  
    25    /**
    26     * Invoked like:
    27     *   java pi4j.email.PIControllerMain [-verbose] -send:google -receive:yahoo -help
    28     *   
    29     * This will send emails using google, and receive using yahoo.
    30     * Do check the file email.properties for the different values associated with email servers.
    31     * 
    32     * @param args See above
    33     */
    34    public static void main(String[] args)  
    35    {
    36      for (int i=0; i<args.length; i++)
    37      {
    38        if ("-verbose".equals(args[i]))
    39        {
    40          verbose = true;
    41          System.setProperty("verbose", "true");
    42        }
    43        else if (args[i].startsWith("-send:"))
    44          providerSend = args[i].substring("-send:".length());      
    45        else if (args[i].startsWith("-receive:"))
    46          providerReceive =args[i].substring("-receive:".length());  
    47        else if ("-help".equals(args[i]))
    48        {
    49          System.out.println("Usage:");
    50          System.out.println("  java pi4j.email.PIControllerMain -verbose -send:google -receive:yahoo -help");
    51          System.exit(0);
    52        }
    53      }
    54      
    55      PIControllerMain lmc = new PIControllerMain();
    56      GPIOController piController = new GPIOController(lmc);
    57      EmailReceiver receiver = new EmailReceiver(providerReceive); // For Google, pop must be explicitely enabled at the account level
    58      try
    59      {
    60        System.out.println("Waiting for instructions.");
    61        boolean keepLooping = true;
    62        while (keepLooping)
    63        {
    64          List<String> received = receiver.receive();
    65          if (verbose || received.size() > 0)
    66          System.out.println(SDF.format(new Date())  + " - Retrieved " + received.size() + " message(s).");
    67          for (String s : received)
    68          {
    69    //      System.out.println(s);
    70            String operation = "";
    71            try
    72            {
    73              JSONObject json = new JSONObject(s);
    74              operation = json.getString("operation");
    75            }
    76            catch (Exception ex)
    77            {
    78              System.err.println(ex.getMessage());
    79              System.err.println("Message is [" + s + "]");
    80            }
    81            if ("exit".equals(operation))
    82            {
    83              keepLooping = false;
    84              System.out.println("Will exit next batch.");
    85          //  break;
    86            }
    87            else
    88            {
    89              if ("turn-green-on".equals(operation))
    90              {
    91                System.out.println("Turning green on");
    92                piController.switchGreen(true);
    93              }
    94              else if ("turn-green-off".equals(operation))
    95              {
    96                System.out.println("Turning green off");
    97                piController.switchGreen(false);
    98              }
    99              else if ("turn-yellow-on".equals(operation))
   100              {
   101                System.out.println("Turning yellow on");
   102                piController.switchYellow(true);
   103              }
   104              else if ("turn-yellow-off".equals(operation))
   105              {
   106                System.out.println("Turning yellow off");
   107                piController.switchYellow(false);
   108              }
   109              try { Thread.sleep(1000L); } catch (InterruptedException ie) { ie.printStackTrace(); }
   110            }
   111          }
   112        }
   113        piController.shutdown();
   114        System.out.println("Done.");
   115        System.exit(0);
   116      }
   117      catch (Exception ex)
   118      {
   119        ex.printStackTrace();
   120      }
   121    }
   122  
   123    public void manageEvent(GpioPinDigitalStateChangeEvent event)
   124    {
   125      if (sender == null)
   126        sender = new EmailSender(providerSend);
   127      try
   128      {
   129        String mess = "{ pin: '" + event.getPin() + "', state:'" + event.getState() + "' }";
   130        System.out.println("Sending:" + mess);
   131        sender.send(sender.getEmailDest().split(","), 
   132                    sender.getEventSubject(), 
   133                    mess);
   134      }
   135      catch (Exception ex)
   136      {
   137        ex.printStackTrace();
   138      }
   139    }
   140  }
      
      

Wiring

This diagram shows the name of the pins you want to use on the GPIO Connector of the Raspberry PI.

Raspberry PI P1 Connector map

As we said before, we need to use the GPIO pins 1, 4, and 2.
Attention!:
GPIO pin 1 is the pin #12
GPIO pin 4 is the pin #16
GPIO pin 2 is the pin #13
The two leds need a 220 Ohms resistor inline between their anode (+) and the GPIO pin they're connected on.
Their cathode (-) is connected on the Ground (pin #6)

Wiring

Demonstration

The Raspberry PI is connected to the home wireless network with the small wireless dongle you can see on the pictures below.
An ethernet cable would have done the same job.
Also notice that there is no keyboard, mouse, nor screen connected to the device.
We used ssh to connect to a Linux session, from which we started the Java program, in background.

Click to enlarge
The Raspberry PI, connected to a breadboard.

At this point, the Raspberry PI is up and running, the java program has been started:
    
 Prompt> sudo ./run -send:google -receive:google [&] 
    
Hint: there is an help option.

 Prompt> ./run -help
 Usage:
   java pi4j.email.PIControllerMain -verbose -send:google -receive:yahoo -help
 Prompt> 
    
The run script looks like this:

 #!/bin/bash
 CP=./classes
 CP=$CP:/home/pi/pi4j/pi4j-distribution/target/distro-contents/lib/pi4j-core.jar
 CP=$CP:./lib/javax.mail_1.1.0.0_1-4-4.jar
 CP=$CP:./lib/json.jar
 #
 java -classpath $CP pi4j.email.LedControllerMain $*
    
Notice the different components on the breadboard:
  • A green led
  • A yellow led
  • A push button

Click to enlarge
Event on the Raspberry PI side. Button pushed.

Some event happens on the Raspberry PI's side. Someone pushed pushed - and the released - the button.
Two emails have been sent:
    
 from   : olivier.lediouris@gmail.com
 subject: PI Event
 content: { pin: '"GPIO 2" <GPIO 2>', state:'HIGH' }
    
    
 from   : olivier.lediouris@gmail.com
 subject: PI Event
 content: { pin: '"GPIO 2" <GPIO 2>', state:'LOW' }
    
Now, you send the following email (in plain text), from your laptop, desktop, smart phone, tablet, what not:

 to     : olivier.lediouris@gmail.com
 subject: PI Request
 content: { operation: "turn-green-on" }
    

Click to enlarge
Green led is on.

The email is received by the Raspberry PI, the green led is turned on.
And you receive an acknowledgement, by email:

 from   : olivier.lediouris@gmail.com
 subject: PI Robot Ack
 content: Your request [{operation:"turn-green-on"}] is being taken care of.
    

Now you send the following email:

 to     : olivier.lediouris@gmail.com
 subject: PI Request
 content: { operation: "turn-yellow-on" }
    

Click to enlarge
Yellow led is on.

The email is received by the Raspberry PI, the yellow led is turned on.
And you receive an acknowledgement, by email:

 from   : olivier.lediouris@gmail.com
 subject: PI Robot Ack
 content: Your request [{operation:"turn-yellow-on"}] is being taken care of.
    

Now you send the following emails:

 to     : olivier.lediouris@gmail.com
 subject: PI Request
 content: { operation: "turn-yellow-off" }
    

 to     : olivier.lediouris@gmail.com
 subject: PI Request
 content: { operation: "turn-green-off" }
    

 to     : olivier.lediouris@gmail.com
 subject: PI Request
 content: { operation: "exit" }
    

Click to enlarge
Everything off, program terminated.

You receive all the acknowledgements, as usual.
All leds are turned off, and the program exits.
Done! Back to base.

Resources

1 comment: