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
- Software that connects to an imap email service and queries for a list of unread messages.
- An Arduino Deicimila board that connects to the host computer via USB and waits for messages from the software. It will then send the message using an RF transmitter.
- An Arduino Lilypad hooked up to an RF receiver to read messages over the air and report status.
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.
Construction
Below is a list of the parts used for this project. All of these can be purchased online at SparkFun
Parts
- Arduino Diecimila – $35
- LilyPad Arduino Mainlioard – $22
- LilyPad Tri-Color LED – $8
- LilyPad Power Supply – $15
- Conductive Thread – $20
- 434MHz RF tx/rx pair – $10
- 2 Tennis Balls – $2
Total cost ~ $100
Other parts and equipment
- Soldering Iron
- Multimeter for testing
- Assortment of sowing needles
- Mini USB cable
- Regular USB cable
- Razor blade / Scissors
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.

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.
![]() |
![]() |
![]() |
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.
![]() |
![]() |
![]() |
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.
![]() |
![]() |
![]() |
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.
![]() |
![]() |
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
- Power on test – cycles through the led states
- No heartbeat from the Arduino – cycles all three leds
- No communication from the perl script – pulses the red LED
- Communication from the perl script – pulses the blue LED
- New Mail – pulses the green led once per msg and then pulses the blue LED.
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;
}
}
Filed under: Uncategorized | Leave a Comment











