projects | about

Wireless email notifications using a pair of Arduino boards

lilyball

The goal of this project was to build a wireless email notifier. It will work with any email provider that allows for imap retrieval. (gmail, etc)

The lilypad was designed as a platform for wearable electronics. The tennis ball works well here not only because the lilypad is round and fits well into the ball, but also the external components can be sown into the green fuzzy. Two wins!


   __________                    \ /    \ /   ------ 
  |  Check   |  USB    _________  |      |  /        \
  | for mail | ------ | Arduino |-        -| lilyball |
  |__________|         ---------            \        /
 /          /                                         
/__________/

There are three components in this architecture

This video shows it in action with 10 mails my in my inbox. When turned out the LEDs cycle through all the possible states. Following that it blinks the green LED once per message, followed by a blue pulse.

Get the Flash Player to see this player.

Construction

Below is a list of the parts used for this project. All of these can be purchased online at SparkFun

Parts

Total cost ~ $100

Other parts and equipment

Transmitter

For the time being the transmitter circuit is connected on a breadboard. It is simply an Arduino Deicimila connected to an rf link with a wire antenna. The arduino is connected to my linux box via USB. For this frequency the antenna length is about 20cm.

rflink

Lilypad Receiver

The Lilypad receiver is housed in a tennis ball. The arduino lilypad is perfect for this because it is round which makes it easy to mount. Cut the tennis ball about 2/3rds down from the top using a razor blade. The arduino is then threaded (using conductive thrad) for power, ground and for three pins which will connect to the three-segment RGB LED.

cut open the tennis ball thread the lilypad lilypad in tennis ball

After the lilypad is threaded (this will keep it secure in the ball) the next part is the rf receiver. The RF link is long and rectangular, it is mounted but cutting grooves in the rubber. Insetead of running thread for the connections I soldered wires directly to the lilypad. Three connections were necessary, power, ground and the data line attached to pin 5.

The last step is to attach the three segement RGB LED on the front. Be sure to attach it securely, the fuzzy stuff on the tennis ball makes that part easy.

rflink lilypad connections lilypad and rflink wired threaded rbg led

Power Supply

My power requirements were 5v for the rf receiver. (although the lilypad can run on 3v) I settled on using a second tennis ball attached by conductive thread for power and using a single AAA battery and the lilypower device. I also wanted an external switch on the ball to turn it off To make this happen I needed to solder wires on the SMD switch attached to the lilypower device (pictured below). This is not a very secure way to attach them but it works well enought. To help make it more secure i tied the wires down with nylon strink using the two extra ground terminals.

smd connections switch connected to lilypower lilyball switch

Disposing of the remaining tennis balls

Don't let your remaining tennis balls go to waste! Dispose of them properly with a generous amount of carb cleaner followed by an open flame. Have a good time kicking them around until the neighbors call the police.

tennis ball and carb cleaner tennis ball flame

Software

Overview

   __________                    \ /    \ /   ------ 
  |  Check   |  USB    _________  |      |  /        \
  | for mail | ------ | Arduino |-        -| lilyball |
  |__________|         ---------            \        /
 /          /          lilytx.cpp         lilyrx_led.cpp        
/__________/       (continually sends
imap_status.pl      the last msg received)

There are a lot of examples online for sending messags between arduinos using an rflink but mostly they seem limited to simple commands, button presses, etc. I wanted to create a generic framework for sending text messages from one arduino to the other. To do this I create the following message structure:

[ opcode (1byte) ][ legnth (1byte) ][ payload ][ checksum (1byte) ] 

This gives me the ability to send arbitrary messages between 1 and 255 bytes in length with an added checksum.

The Arduino acts as a broker between the commands sent over the usb and those sent over the air. One reason this is necessary is because it is important to keep the wireless line active, otherwise it will be filled with noise that may confuse the receiver. In the case where there isn't data being sent by the arduino, the checksum validation will do a decent job at preventing false positives.

There are five states for the lilyball receiver

Perl Script to Monitor a mailbox via IMAP

This script is set to run continually. There a few library choices in Perl for dealing with IMAP, I picked Mail::IMAPClient because it provided an easy method for getting the number of unseen messages. We also use IO::Socket::SSL since Mail::IMAPClient does not natively support SSL and we want to be secure!

#!/usr/bin/perl -w
#
# imap_status.pl
#
# John Jarvis 2008/11/8
# john@jarv.org
#
# Polls an IMAP folder and sends
# a message when there is new mail
# over a serial connections.
#

use strict;
use Device::SerialPort;
use Mail::IMAPClient;
use IO::Socket::SSL;

my $SERVER = "***********";
my $USER = "**********";
my $PASS = "**********";
my $MAILBOX = 'INBOX';
my $SERIAL = '/dev/ttyUSB0';


# constants

my $newmail   = 41;  # opcode for newmail msg
my $heartbeat = 42;  # opcode for heartbeat msg
my $hmsg      = "hi there"; # can be whatever you want

{ 
   
    #initialize serial
    my $port = Device::SerialPort->new($SERIAL);

    #initialize ssl connection
    my $s = IO::Socket::SSL->new(
            proto   =>      'tcp',
            PeerAddr  =>    "$SERVER",
            PeerPort  =>    '993',
    );
    die "Failed to init SSL" unless (defined $s);

    #intialize imap
    my $i = Mail::IMAPClient->new ( Socket => $s,);
    die "Cannot connect to IMAP Server" unless(defined $i);

    $i->State(Mail::IMAPClient::Connected);
    $i->User("$USER");
    $i->Password("$PASS");
    $i->login();

    if ($i->IsAuthenticated) {
            print "Connected to IMAP Server\n";
    } else {
            die "Cannot authenticate to IMAP server\n";
    }

    while (1)  # loop forever
    {
        # get the number of msgs that haven't been read
        my $unseen = $i->unseen_count($MAILBOX);

        my $data = ();

        if ($unseen > 0) {  # w00t we have unseen msgs in our mail
                # one letter 'N' per msg
                my $msg = 'N' x $unseen;
                $data = pack("CCa*",$newmail,length($msg),$msg);

        } else { # no mail :( send a heartbeat instead
                $data = pack("CCa*",$heartbeat,length($hmsg),$hmsg);
        }

        my $count_out = $port->write($data);
        warn "write failed\n"  unless($count_out);
        warn "write incomplete\n" if ($count_out != length($data));
        sleep(5);
   }
}

lilytx - listen for serial messages and transmit them over the air

/****
*
*  lilytx
*  John Jarvis - 2008/11/8
*  john@jarv.org
*  reads status messages from the serial line
*  and continually sends them over an rf link using
*  SoftwareSerial
*
*  data received needs to be packed in this format
*
*        [1byte opcode][1byte length][payload]
*
*  data is then sent over the air in the following format
*  
*       [1byte opcode>[1byte length][payload][1byte checksum]
*
*****/


#include <SoftwareSerial.h>
#include <string.h>

#define rxPin 2
#define txPin 3
#define ledPin 13
  
#define DISCONNECT_TICKS 200  // listen for serial data for 50 loop ticks
#define NOSERIAL_MSG "heya lilypad!"  // if nothing is sending us data over serial we send this
#define ARDUINO 40  // opcode for the arduino-arduino message
#define TXDELAY 5   // small delay so we don't overrun the receiver

// set up a new serial port
SoftwareSerial rfserial =  SoftwareSerial(rxPin, txPin);
byte pinState = 0;
int dis_cnt = 0;   // number of times we haven't seen serial data
char sendbuf[256] = { 0 }; // global send buffer
char opcode = 0;  // global opcode

void setup()  {
  // define pin modes for tx, rx, led pins:
  pinMode(rxPin, INPUT);
  pinMode(txPin, OUTPUT);
  pinMode(ledPin, OUTPUT);
  // set the data rate for the SoftwareSerial port
  rfserial.begin(2400); // intialize the rflink serial line
  Serial.begin(9600);  // for debug only
  Serial.println("Here we go");
  
  
}

// this will loop continuously and attempt to read data off of
// the serial line.  If data is present it is copied into a global
// buffer.  When data is received it is sent over the air and will
// continue to be sent over the air until there is either new serial
// data or we stop seeing data for set number of loop ticks.

void loop() {
    
    if (Serial.available() > 0) {
        dis_cnt = 0;   // reset the serial counter
        opcode = Serial.read();  // first byte is the opcde
        Serial.print("Opcode = ");
        Serial.println(opcode,DEC);

        stringread();  // read the msg
        Serial.print("Data = ");
        Serial.println(sendbuf);

     }
     
     
     if (dis_cnt > DISCONNECT_TICKS) {
         opcode = ARDUINO;
         strcpy(sendbuf, NOSERIAL_MSG);
         Serial.println(ARDUINO,DEC);
         Serial.println(NOSERIAL_MSG);
      } else {
        dis_cnt++;
     }  
      
     // until we see a disconnect or serial data this will just
     // send nulls.
     // when either serial data or a disconnect is seen we will
     // send the same thing until something changes.
    
     stringsend(opcode,sendbuf);
}

void stringsend( char opcode, char data[]) {

    int i;
    byte checksum = 0;
    byte length = strlen(data);
    rfserial.print(opcode, BYTE);
    delay(TXDELAY);
    rfserial.print(length,BYTE);
    delay(TXDELAY);
    for (i=0;i<strlen(data);i++) {
      rfserial.print(data[i], BYTE);
      delay(TXDELAY);
      checksum = checksum + data[i];
    }
    checksum = checksum % 256;
    rfserial.print(checksum, BYTE);
    delay(TXDELAY);

    

}

// copies a string into the global send buffer, assumes first byte is the length
void stringread () {  
  
  int i = 0;
  char length = 0;
  char buf[256] = { 0 }; 
  length = Serial.read();
  for (i=0;i<length;i++) {
      buf[i] = Serial.read();
    Serial.println(buf[i],BYTE);
  }
  strcpy(sendbuf, buf);

 
}

lilyrx_led - read status messages over the air and relay them to leds attached to three pins

/****
*
*  lilyrx_led
*  John Jarvis - 2008/11/8
*  john@jarv.org
*  reads status messages over the air via an 
*  rf link using SoftwareSerial relays the information
*  to an led.
*
*  data received over the air needs to be in this format
*  
*       [1byte opcode][1byte length][payload][1byte checksum]
*
*****/





#include <SoftwareSerial.h>

#define RXPIN     5
#define TXPIN     6
#define RPIN      3
#define BPIN     11
#define GPIN     13


#define LEDON   LOW
#define LEDOFF  HIGH

#define RTOGGLE  rstate = !rstate; digitalWrite(RPIN,rstate); delay(50)
#define GTOGGLE  gstate = !gstate; digitalWrite(GPIN,gstate); delay(50)
#define BTOGGLE  bstate = !bstate; digitalWrite(BPIN,bstate); delay(50)
#define RON      digitalWrite(RPIN, LEDON); rstate = LEDON;  delay(200)
#define GON      digitalWrite(GPIN, LEDON); gstate = LEDON;  delay(200)
#define BON      digitalWrite(BPIN, LEDON); bstate = LEDON;  delay(200)
#define ROFF     digitalWrite(RPIN, LEDOFF); rstate = LEDOFF;  delay(200)
#define GOFF     digitalWrite(GPIN, LEDOFF); gstate = LEDOFF;  delay(200)
#define BOFF     digitalWrite(BPIN, LEDOFF); bstate = LEDOFF;  delay(200)

// opcodes for RF LINK messages

#define HEARTBEAT  42
#define NEWMAIL    41
#define ARDUINO    40

#define DISCONNECT_TICKS 1000  // number of ticks before a com failure

// set up a new serial port (global)
SoftwareSerial rfserial =  SoftwareSerial(RXPIN, TXPIN);

// globals that keep track of our pin states, initialize them to off
byte rstate = LEDOFF;
byte gstate = LEDOFF;
byte bstate = LEDOFF;

int dis_cnt = 0;  // number of times we haven't seen any msg


void setup()  {
  
  int cnt = 0;

  
  // prepare serial
  pinMode(RXPIN, INPUT);
  pinMode(TXPIN, OUTPUT);
  rfserial.begin(2400);  // rflink serial line

  Serial.begin(9600);  // this is for debugging only 
  Serial.println("Here we go");
 
  // set pin states and cycle
  pinMode(RPIN, OUTPUT);
  pinMode(GPIN, OUTPUT);
  pinMode(BPIN, OUTPUT);
  
  // LED POST ;) *********************
  
  for (cnt=0; cnt<10; cnt++) {
       RTOGGLE;
       GTOGGLE;
       BTOGGLE;
       BTOGGLE;
       GTOGGLE;
       RTOGGLE;
  }
  
  bstate = togglefade(BPIN, bstate);
  bstate = togglefade(BPIN, bstate);
  rstate = togglefade(RPIN, rstate);
  rstate = togglefade(RPIN, rstate);
  GON; 
  GOFF; 
  GON;  
  GOFF; 
  
  //*********************************
}

void loop() {

  char opcode = 0;
  char dest[256] = { 0 };
  byte msg_cnt = 0;
  int cnt = 0;
  
  opcode = rfserial.read();
  if (opcode == HEARTBEAT ) {   // don't care about the payload

    if (stringread(dest) == 0) {
         Serial.println("HEARTBEAT ");
         dis_cnt = 0;
         bstate = togglefade(BPIN, bstate);
         bstate = togglefade(BPIN, bstate);

    } else {  
      //invalid checksum

    }
    
  } else if (opcode == NEWMAIL  ) { // payload contains 1 byte per msg

   
    
    if (stringread(dest) == 0) {
        Serial.println("NEWMAIL");
        dis_cnt = 0;
        // pulse GREEN led once per byte in the payload
        for (msg_cnt=0;msg_cnt<strlen(dest);msg_cnt++) {
            GON; 
            GOFF; 

        }
        bstate = togglefade(BPIN, bstate);
        bstate = togglefade(BPIN, bstate);
        
    } else {
           //invalid checksum
    }
    
    
      
  } else if ( opcode == ARDUINO  ) {  // don't care about the payload

   
    if (stringread(dest) == 0) {
       Serial.println("ARDUINO");
       dis_cnt = 0;
       RTOGGLE;
       RTOGGLE;
    } else {
           //invalid checksum
    }
    
  } else {

    if (dis_cnt > DISCONNECT_TICKS) {  // com failure!
            RTOGGLE;
            GTOGGLE;
            BTOGGLE;
            BTOGGLE;
            GTOGGLE;
            RTOGGLE;

    }
    else {
      dis_cnt++;
    }
  }
}

int stringread (char* dest) {
  
  int i = 0;
  char checksum_read = 0;
  char checksum_calc = 0;
  char length = 0;
  char buf[256] = { 0 };
  
  length = rfserial.read();
  Serial.println(length,DEC); 
  for (i=0;i<length;i++) {
      buf[i] = rfserial.read();
      checksum_calc = checksum_calc + buf[i];
  }
  checksum_calc = checksum_calc % 256;
  checksum_read = rfserial.read();
  Serial.print(buf); 
  Serial.println(); 
  Serial.println(checksum_read, DEC);
  if (checksum_calc == checksum_read) {
    strcpy(dest, buf);
    return 0;
  } else {
    return -1;
  }
}

int togglefade (int pin, byte state)  {
  
  int i = 0;
  if (state == LEDOFF) {  //fade in
      for (i=255; i>=0; i=i-1) {
        analogWrite(pin,i);
        delay(1);
      }
      return LEDON;
  } else {  // fade out
      for (i=0; i<=255; i=i+1) {
        analogWrite(pin,i);
        delay(1);
      }
      return LEDOFF;
  }
}



Copyright (c) jarv.org Verbatim copying and redistribution of this entire page are permitted provided this notice is preserved.
Validate (nerd)