Monday, October 28, 2013

RXTX on Mac with Oracle Java 7

I've been using RXTX on the mac for quite some time with the -d32 vm argument. This was necessary with 64-bit Java since the native libraries for RXTX are only compiled for 32-bit mode. Without -d32, you'd get the following error:

java.lang.UnsatisfiedLinkError: librxtxSerial.jnilib: no suitable image found

Apple had always bundled Java with OS X, but about 3 years ago they announced they would no longer do so. Now, to get Java 7 for the mac you need to get it from Oracle. Recently I tried running one of my RXTX apps with Oracle's Java 7 and I got the following error:

Error: This Java instance does not support a 32-bit JVM.

Please install the desired version.

It seems that Oracle no longer supports the -d32 vm argument. You can still run the Apple version of Java 6. The Apple bundled Java versions can be found in

/System/Library/Frameworks/JavaVM.framework/Versions/

For example:

$ /System/Library/Frameworks/JavaVM.framework/Versions/1.6/Home/bin/java -d32 -version
java version "1.6.0_37"
Java(TM) SE Runtime Environment (build 1.6.0_37-b06-434-11M3909)
Java HotSpot(TM) Client VM (build 20.12-b01-434, mixed mode)

So to run Oracle Java 7 we need a 64-bit version of RXTX. A few searches led me to Arduino article that mentions Robert Harder's blog post http://blog.iharder.net/2009/08/18/rxtx-java-6-and-librxtxserial-jnilib-on-intel-mac-os-x/ In this post Robert describes how he compiled RXTX for 64-bit Java (apparently not a trivial task). After downloading his librxtxSerial.jnilib it worked, the first time; the second time I got an exception:

“gnu.io.PortInUseException: Unknown Application”

This was super annoying since I didn't have any other Java processes running that had the port open. I read through the comments and found a solution:

sudo mkdir /var/lock

sudo chmod a+wrx /var/lock

Apparently RXTX needs this directory to manage locks. This was also mentioned on the Arduino article but I missed it the first time. It seems that the Arduino IDE must rely on Java 6 since the RXTX library that comes with the IDE is 32-bit. At some point I imagine (Java 8 perhaps), we'll see more interest in 64-bit RXTX. Please leave a comment if you have any insight on this problem and/or alternative solutions.

Sunday, June 9, 2013

XBee on the Raspberry Pi

When I first started writing xbee-api in 2007, there were no options for a cost effective, low power host computer with Java support. In 2009 I purchased a Sheevaplug ARM computer and that worked for a while but proved to be problematic: the power supply failed after a year (a known issue), and it experienced periodic boot errors. Last year I received a Raspberry Pi after a long wait, due demand far exceeding supply.


In order to run Java on the the Raspberry Pi it requires the soft-float version of Debian OS, available from the Pi website. Once you get your Pi up and running, you'll need to install the Oracle Java for ARM. I don't recommend openjdk as I found it to be much slower and buggier on ARM.

Update: Java 8 (early access) is available for Raspian hard float. Oracle's getting started guide

There are several versions of Java for ARM on the Oracle site. The version compatible with the Raspberry Pi is ARMv6/7 Linux - Headless EABI, VFP, SoftFP ABI, Little Endian. The current release as of this writing is ejre-7u21-fcs-b11-linux-arm-vfp-client_headless-04_apr_2013.tar.gz.

Move the gzip file to your Pi and unpack

gunzip ejre-7u10-fcs-b18-linux-arm-vfp-client_headless-28_nov_2012.gz 
tar xvf ejre-7u10-fcs-b18-linux-arm-vfp-client_headless-28_nov_2012

I installed it in /opt/java

sudo mv -v ejre1.7.0_10/ /opt/java/

Create the symbolic link

cd /opt/java/ejre1.7.0_10/
sudo update-alternatives --install "/usr/bin/java" "java" "/opt/java/ejre1.7.0_10/bin/java" 1
sudo update-alternatives --set java /opt/java/ejre1.7.0_10/bin/java

Open .bashrc and set the JAVA_HOME environment variable

export JAVA_HOME="/opt/java/ejre1.7.0_10"

Now you should be able to execute java

java -version

And see something like

java version "1.7.0_10"
Java(TM) SE Embedded Runtime Environment (build 1.7.0_10-b18, headless)
Java HotSpot(TM) Embedded Client VM (build 23.6-b04, mixed mode)

Although the Pi has a GPIO serial port that is well suited for XBee, since it runs at 3.3V, I'm using a usb-serial XBee Explorer since I already had this hardware available and the configuration is less complicated. These can be found relatively cheap from Chinese vendors on ebay, or from a local supplier if you need it quicker. I'm using the SparkFun USB Explorer.

Install RXTX to provide serial port access to Java

sudo apt-get install librxtx-java

Now when you plug your XBee into the Pi, via USB, it should appear as /dev/ttyUSB0 and can be opened with xbee-api

XBee xbee = new XBee()
xbee.open("/dev/ttyUSB0")

Now, to run your xbee-api application on the Pi, you need to provide a few key arguments to Java.

java -Djava.library.path=/usr/lib/jni/ -classpath ".:/usr/share/java/RXTXcomm.jar" com.fooYourApp

The -Djava.library.path argument tells Java where to find the native RXTX library (JNI).

 If you exported your application from Eclipse into an executable JAR, use "*:/usr/share/java/RXTXcomm.jar" for classpath. This tells Java to load all JAR files in the current directory, in addition to the RXTX JAR. Alternatively, if you copied your Eclipse folder to the Pi, the classfiles are in the bin directory, so use "bin:/usr/share/java/RXTXcomm.jar". Of course replace com.fooYourApp with the package + class name of the main class. It's a good idea to put the full Java command in a script (e.g. myapp.sh). Then make it executable with chmod u+x myapp.sh.

If you want your app to start every time the Pi boots, add the script to the do_start() function in /etc/init.d/rc.local

do_start() {
        if [ -x /etc/rc.local ]; then
                cd /home/pi/path-to-your-app && sudo -u pi nohup ./myapp.sh &

I've been running my Garage Door XBee application on the Pi for about 6 months now and it has been very reliable. The only problems I've encountered so far were related to a failing power supply. When the voltage drops below 4.75, mysterious things can start to happen. My power supply dropped to 4.69 and I noticed periodic crashes.

I'm running my Pi with ethernet connectivity. The Pi can use Wifi but due to the limited current supply of the USB port, it only works with a select number of Wifi chips. Perhaps later versions of the Pi will supply more power to the USB to overcome this limitation.

In a future post I'll show how to interface the Pi's GPIO Serial port directly with the XBee UART.

Saturday, October 1, 2011

XBee/Google Talk Garage Door

I had been thinking about doing a garage door controller project for years, but never found the time to work on it.  On occasion I have forgotten to close my garage door at night, and worry about items disappearing.  It wasn't until after hearing about an expensive bike that was stolen from a neighbor's garage that I decided to make it happen. Initially I just wanted a system to send notifications to my phone if the door was left open, but I decided door control would also be useful. I also considered having the system automatically close the door, but decided against this due to the risks (e.g. a bug in the sketch crunching my car).


I did some research and found several garage door projects, using a variety of technologies: WiFi, Arduino, XBee and providing varying capabilities: monitor only, control only and both.  I wanted to be able to control and monitor the door from my Android phone.  I also did not want to need to write a phone application to control the door.  I considered using WiFi.  This would have involved a web server on the Arduino for control but there's no good method to send events to the phone with this approach.  Another problem is accessing the web server from the internet would require configuring port forwarding from my router to the Arduino, which is not very secure.  Additonally, the Arduino Wifi hardware is quite expensive.  I settled on XBee and Google Talk.  Of course Google Talk is supported on both Android and IPhone, so both me and my wife (IPhone) could control and monitor the door.  The downside to this solution is it does require running a server to bridge communication with both Google Talk and the Arduino.  For this I'm using my Sheevaplug (plug computer), since it's very low power (~3W)

Controlling a garage door requires a relay. Fortunately there's a good reference circuit for relays on the Arduino site.  I wired the relay to the Arduino and uploaded a simple script to verify I could make it open and close it. At first I wasted a lot of time troubleshooting what I thought was a circuit issue but turned out to be a bad breadboard. Note: the relay has a NO (normally open) and NC (normally closed). The garage door should be wired to the NO pin.


I'm using magnetic sensors (reed switch) to provide door position.  These are the same sensors used in security systems and are very reliable.  The door sensors are not necessary but highly recommend as they can tell you if your door is open or closed and identify a door failure (e.g. door started closing but failed to fully close).  I use two door sensors in my solution, one for the open door position and one for the closed door position. It's possible to use just one for the closed door position and just assume the door is open if it gets tripped, but the problem with this approach is you won't know if the door failed to fully open.  The magnetic sensors are cheap, and there are more than enough I/O ports, so you might as well use two. 


I had to attach a 1x1 post to the joist to position the sensor at the open door position. The sensors close at around 1/2", so I connected them at around 1/4", allowing for sufficient door clearance.



The close-door contact was easily mounted on the wall. I used twist connectors to connect the sensor wire to the extension wire.





The door contacts should be wired to ground and one of the Arduino digital inputs. I'm using Arduino's built in pull up resistors to drive the pin HIGH when the circuit is open. When the magnetic reed switch closes, the pin will go LOW since it it grounded.  The sketch expects the close door sensor to be wired to digital pin 8 and the open door sensor to digital 9.

Now is a good time to mention that the software for this project was written for Series 2 XBees.  The good news however is that both XBee libraries: xbee-api (Java) and xbee-arduino also support Series 1 and the code modifications to support Series 1 are relatively minor.


XBee Configuration

Configure the Series 2 radios for API mode.  Refer to XBeeConfiguration  For the best security, I recommend configuring your XBees to use encryption




Software


You can download all the software for this project from my Google Code site


Arduino Sketch

The loop function looks for incoming XBee requests (close door, open door, door status), and detects changes in the closed and open door magnetic contacts.  When the sketch receives an XBee packet (initiated by a Google Talk message), it performs the action (e.g. open door, close door, or status) and returns a acknowledgment packet.  Similarly, when it detects a change in the door sensors (e.g. door opening or closing), whether initiated from Google Talk or the garage door button, it sends the corresponding event to Google Talk.


The sketch requires only one change: update with the XBee address of the remote XBee.  Find COORD_MSB_ADDRESS and COORD_LSB_ADDRESS and replace with 64-bit address of the remote XBee (the one connected to the Java app).  The sketch assumes pins 8, 9, and 10 are used for the close-door sensor, open-door sensor and relay.  Adjust if necessary.

The sketch requires the xbee-arduino library to communicate with the XBee radio.  The library must be installed in your Arduino's "libraries" folder.  The project page includes installation instructions and information on getting started. 







#define STATUS_RESPONSE_TIMEOUT 500
#define DEBUG 0

uint8_t openContact;
uint8_t closeContact;

// state variables
bool doorFailure = false;
bool closing;
bool opening;
bool relayActivated;

// last time a contact fired
long lastDoorActionTime;
// last time the relay was activated
long lastRelayActionTime;

const uint8_t closePin = 8;
const uint8_t openPin = 9;
const uint8_t relayPin = 10;

// TX Commands
const uint8_t DOOR_OPEN = 4;
const uint8_t DOOR_CLOSED = 5;
const uint8_t DOOR_OPENING = 6;
const uint8_t DOOR_CLOSING = 7;
const uint8_t DOOR_FAILURE = 8;
const uint8_t DOOR_ALREADY_OPEN = 9;
const uint8_t DOOR_ALREADY_CLOSED = 10;
const uint8_t CMD_ACK = 11;
const uint8_t STARTUP = 12;

// RX Commands
const uint8_t DOOR_STATE_REQUEST = 1;
const uint8_t OPEN_DOOR_REQUEST = 2;
const uint8_t CLOSE_DOOR_REQUEST = 3;
// TODO 
const uint8_t GET_DOOR_STATS = 4;

const int MAX_DOOR_OPENCLOSE_TIME = 20000;
const uint8_t debounceDelay = 50;

uint16_t sendErrors = 0;

// Define NewSoftSerial TX/RX pins
// Connect TX of usb-serial device to NSS RX
uint8_t ssRX = 6;
// Connect RX of usb-serial device to NSS TX
uint8_t ssTX = 7;
// Remember to connect all devices to a common Ground: XBee, Arduino and USB-Serial device
NewSoftSerial nss(ssRX, ssTX);

XBee xbee = XBee();
XBeeResponse response = XBeeResponse();

// one byte payload
uint8_t payload[] = { 0 };

// TODO replace with address of your coordinator (Connected to the Java app)
uint32_t COORD_MSB_ADDRESS = 0x0013a41c;
uint32_t COORD_LSB_ADDRESS = 0x403ef3b1;

// Coordinator/XMPP Gateway
XBeeAddress64 addr64 = XBeeAddress64(COORD_MSB_ADDRESS, COORD_LSB_ADDRESS);
ZBTxRequest tx = ZBTxRequest(addr64, payload, sizeof(payload));
ZBTxStatusResponse txStatus = ZBTxStatusResponse();
// create reusable response objects for responses we expect to handle 
ZBRxResponse rx = ZBRxResponse();

void setup() {  
  // start serial
  xbee.begin(9600);
  
  if (DEBUG) {
    // start soft serial
    nss.begin(9600);
    nss.println("Startup");
  }
  
  // turn on internal pull-ups for magnetic switches
  pinMode(openPin, INPUT);
  digitalWrite(openPin, HIGH);
  
  pinMode(closePin, INPUT);
  digitalWrite(closePin, HIGH);
  
  pinMode(relayPin, OUTPUT);
  digitalWrite(relayPin, LOW);
  
  openContact = digitalRead(openPin);
  closeContact = digitalRead(closePin);
  
  opening = false;
  closing = false;
  doorFailure = false;
  lastDoorActionTime = 0;
}

void activateDoor() {
  digitalWrite(relayPin, HIGH);
  delay(200);
  digitalWrite(relayPin, LOW);
  
  relayActivated = true;
}

bool isDoorOpen() {
  // door is open only if open contact is closed and closed contact is open
  // 0 == closed contact, 1 == open contact
  return (openContact == 0) && (closeContact == 1);
}

bool isDoorClosed() {
  return (openContact == 1) && (closeContact == 0);
}

void handleXBeeResponse() {

  if (xbee.getResponse().getApiId() == ZB_RX_RESPONSE) {
       
    // now fill our zb rx class
    xbee.getResponse().getZBRxResponse(rx);
            
    // Make sure this is coming from our XBee (note: this is weak security.. using XBee encryption is highly recommended) 
    if (!(rx.getRemoteAddress64().getMsb() == COORD_MSB_ADDRESS && rx.getRemoteAddress64().getLsb() == COORD_LSB_ADDRESS)) {
      if (DEBUG) nss.println("WARN: unknown source address");
      return;
    }
     
    if (rx.getData(0) == OPEN_DOOR_REQUEST) {
      // open door
      if (isDoorClosed()) {
        if (DEBUG) nss.println("Opening door");
        activateDoor();       
        // tell the sender the request was successful  
        sendDoorEvent(CMD_ACK);                
      } else {
        // closed contact = 1 (open)
        // tell sender door is already open
        if (DEBUG) nss.println("Door already open!");
        sendDoorEvent(DOOR_ALREADY_OPEN);        
      }
    } else if (rx.getData(0) == CLOSE_DOOR_REQUEST) {
      // close door
      if (isDoorOpen()) {
        if (DEBUG) nss.println("Closing door");
        activateDoor();
        // tell the sender the request was successful
        sendDoorEvent(CMD_ACK);
      } else {
        if (DEBUG) nss.println("Door already closed!");
        // tell sender the door is already closed
        sendDoorEvent(DOOR_ALREADY_CLOSED);
      }
    } else if (rx.getData(0) == DOOR_STATE_REQUEST) {      
      sendDoorEvent((openContact & 1) + ((closeContact << 1) & 2));
    } else {
      // unknown command // TODO log
      if (DEBUG) nss.print("Unknown RX:");
      if (DEBUG) nss.println(rx.getData(0));
    }
  } else {
    // unsupported api -- TODO handle
    if (DEBUG) nss.print("Unsupported RX packet:");
    if (DEBUG) nss.println(xbee.getResponse().getApiId(), HEX);
  }
}

void sendDoorEvent(uint8_t message) {
  payload[0] = message;
  
  switch (message) {
     case DOOR_OPEN:
     case DOOR_CLOSED:
      lastDoorActionTime = millis();
      break;
  }
  
  // TODO set frame id with millis & 256
  xbee.send(tx);
  
  // after sending a tx request, we expect a status response
  // wait up to half second for the status response
  if (xbee.readPacket(STATUS_RESPONSE_TIMEOUT)) {
    // got a response!

    // check if series 1 or series 2 tx status       
    if (xbee.getResponse().getApiId() == ZB_TX_STATUS_RESPONSE) {
      xbee.getResponse().getZBTxStatusResponse(txStatus);

      // get the delivery status, the fifth byte
      if (txStatus.isSuccess()) {
        // good
      } else {
        if (DEBUG) nss.print("sendDoorEvent no ACK:");  
        // TODO resend with same frame id
        sendErrors++;
      }
    }      
  } else if (xbee.getResponse().isError()) {
    if (DEBUG) nss.print("sendDoor TX error:");  
    if (DEBUG) nss.println(xbee.getResponse().getErrorCode());
  } else {
    if (DEBUG) nss.print("sendDoor TX timeout");  
    // local XBee did not provide a timely TX Status Response -- should not happen if radio is configured and wired correctly
    // did you switch the TX/RX jumpers back to XBee?
    // is your baud rate correct?
    // in API mode?
  } 
}

// detect pin state change with debounce
bool pinChange(int pin, int current) {
  if (digitalRead(pin) != current) {
    
    // debounce
    delay(debounceDelay);
    
    // if state still the same, send event
    if (digitalRead(pin) != current) {
      return true;
    } else {
      // ignore spurious event
      // TODO log
      return false;
    }
  }
  
  return false;
}


void loop() {    
  // reads a packet from Serial, if data is available; otherwise continues on
  xbee.readPacket();
  
  if (xbee.getResponse().isAvailable()) {
    // got something
    handleXBeeResponse();
  } else if (xbee.getResponse().isError()) {
    if (DEBUG) nss.print("RX packet loop() error:");
    if (DEBUG) nss.println(xbee.getResponse().getErrorCode(), DEC);
  }

  // detect if open-door contact just tripped
  if (pinChange(openPin, openContact)) {
    // open-door contact tripped -- toggle it
    openContact = !openContact;

    // Remember that then the circuit is closed when the contacts meet, which makes the wire go to 0V (logical false).  An open contact is 5V (logical true)
    if (openContact) {
      // Open contact is now OPEN -- door is closing
      if (DEBUG) nss.println("Door closing");
      closing = true;
      lastDoorActionTime = millis();
      sendDoorEvent(DOOR_CLOSING);
    } else {
      // Open contact is now CLOSED -- door has completed opening
      if (DEBUG) nss.println("Door finished opening");
      opening = false;
      doorFailure = false;
      lastDoorActionTime = 0;
      sendDoorEvent(DOOR_OPEN);      
    }  
  }

  // detect if closed-door contact just tripped
  if (pinChange(closePin, closeContact)) {
      // closed-door contact tripped -- toggle it
      closeContact = !closeContact;  
      
      if (closeContact) {
        // Close contact is now OPEN -- door is opening
        if (DEBUG) nss.println("Door opening");
 // door opening
 opening = true;
 lastDoorActionTime = millis();
        sendDoorEvent(DOOR_OPENING);
      } else {
        // Close contact is now CLOSED -- door has finished closing
        if (DEBUG) nss.println("Door finished closing");
 closing = false;
 doorFailure = false;
 lastDoorActionTime = 0;
 sendDoorEvent(DOOR_CLOSED);
      }
  }
  
  if ((opening || closing) && (millis() - lastDoorActionTime) > MAX_DOOR_OPENCLOSE_TIME) {
     // Problem: door started opening or closing but did not complete with the expected time
     doorFailure = false;
     if (DEBUG) nss.println("Door failure");
     sendDoorEvent(DOOR_FAILURE);
  }
}


The Java Part

Unless you have multiple Google accounts, you'll need to create a Google account for the garage door (e.g. mygaragedoor@gmail.com). If you reuse an existing account, remember that anyone that is in your roster list (friends), will be able to control your door, unless your remove them. For this reason it's better to create a separate account.

Just like the Arduino sketch, the Java code was also written for Series 2 XBees, but since the XBee library (xbee-api) supports Series 1, it could be easily modified to support Series 1.

There are a few changes that need to be made in the Java app. Open GarageDoor.java (I recommend using an IDE such as Eclipse or Netbeans) and make the following changes. Find garageDoorAddress and replace with the 64-bit address of the garage door XBee. Find xbee.open("yourcomport", 9600); and enter the com port of XBee connected to your computer (Coordinator).
In initGoogleTalk, add a roster friend for each Google account that should be allowed to control the Garage Door, for example:

xmppClient.addRosterFriend("yourpersonalgmailaccount@gmail.com");

The software will automatically subscribe to and accept messages from this Google account. Only Google accounts specified here will be able to control and receive messages from the garage door.

Find the following line:

xmppClient.connect(new GtalkConnector("mygaragedoor@gmail.com", "password"), new MessageListener() {

and replace with the email/password of the Google account that was created for the garage door.

Once all changes have been made, you can run the application from your IDE.  All required libraries are included in the software download, so your IDE should find them automatically.  If not, add all JAR file in the "lib" folder.


The Google Talk commands are simple for easy operation with a mobile phone.  The commands are o=Open Door, c=Close Door, and s=Door Status. If you send a command that is not understood, the menu is returned.  Disregard the x (Extended menu), which I never got around to implementing.




In addition to sending open/close door events via Google Talk, the Java application will send a reminder if the door is left open for 10 minutes.  This is easy to miss if I don't have my phone with me, so I plan to add a piezo buzzer and/or a light to indicate the open door position.  A buzzer could be easily added to the Arduino, but this could be annoying since there are times that I leave my door open intentionally.  I may use the XBee to ask for current time (eliminating the need for a real time clock) from the server and only chirp the buzzer if door is open after a specific time (e.g 10PM).  A simple LED would be more effective but it would need to be mounted inside, somewhere that I would be sure to see it at night (e.g. bedroom).  The downside of using a light is it would be out of range of the Arduino, so it would need to be wireless.  I already have a wireless messaging system is use (droplet), so I may just send door-left-open alerts to that system and call it a day.

Security

Google Talk authentication and communication occurs over TLS (transport layer security), so you can consider it to be quite secure, to the extent that you protect your credentials and choose a strong password. Additionally, only users that you specify can send messages to the garage door account, so you don't have to worry about a spammer playing with your door.  You could add additional security by requiring a pin number to be entered for each door control request.

The Arduino Sketch is configured to only accept XBee packets from your radio. It does this by checking the source address (64-bit serial high/low). This however is not good security as it could be defeated, but is somewhat safe in that Digi firmware will not let you spoof addresses. I strongly recommend using XBee's built-in support for encryption.

Case

I found the project case in my recycle bin -- a plastic pizza dough container from Whole Foods. You can buy cases at any hardware store but I didn't need a fancy case, so this worked out perfectly. You can see I'm lazy by using a breadboard, instead of soldering the components down on a protoboard. I recommend making mounting the Arduino such that you can access the jumpers and USB cable for updating the sketch.

Parts

  • Omron G5SB Relay http://www.sparkfun.com/products/10509 This relay is Arduino safe, in that the coil impedance is high enough that the Arduino can safely power it.
  • 2N2222A transistor http://search.digikey.com/scripts/DkSearch/dksus.dll?Detail&name=497-2598-ND Doesn't have to be this exact one
  • 1K Resistor I'm guessing you have one of these
  • 1N4004 Diode http://search.digikey.com/scripts/DkSearch/dksus.dll?Detail&name=1N4004FSCT-ND Can also be found at Radio Shacks, in the US
  • 2 Magnetic door contacts. I got mine on ebay: http://cgi.ebay.com/5-Set-Door-Window-Contact-Magnetic-Reed-Switch-Alarm-/110716359054?pt=LH_DefaultDomain_0&hash=item19c735918e#ht_2557wt_1114 Keep in mind that if you order from China, be prepared to wait 3 weeks, unless of course you live in China.
  • Low voltage wire to connect Arduino circuit to the garage door unit and magnetic sensors. Measure first to get an idea of how much you need. I bought a 65' spool of 20 gauge and probably only used 25'.
  • Wire connectors caps, similar to this http://www.homedepot.com/h_d1/N-5yc1v/R-100628936/h_d2/ProductDisplay?langId=-1&storeId=10051&catalogId=10053
  • Obviously you'll need two Series 2 XBees, one Arduino or clone (e.g. RBBB). A USB Explorer or equivalent for the PC side, and a XBee socket or XBee Shield to interface with the Arduino.
  • 9V Arduino power supply and possibly an extension cord.
  • Standoffs to mount Arduino to enclosure.
  • Various screws and washers to mount the enclosure to wall

Saturday, November 21, 2009

Chatduino: An AIM Client for Arduino/Wiznet 5100

This is an AIM instant messenger client for Arduino/Wiznet 5100, which allows you to communicate with your Arduino project from anywhere on the internet, in near real-time. You can communicate with your project through any AIM client or even your cell phone by using text messaging with Mobile AIM, and since communication is channeled though the AIM server, both the Arduino+Wiznet and chat client can exist behind firewalls.



At first I looked into creating an Arduino library for XMPP/Jabber. This would allow you to connect to Google Talk/App Engine or any other XMPP service. The problem which this approach is most XMPP services require TLS for security, and TLS isn't going to happen on an ATmega328 (thinly veiled challenge going out to anyone who can prove otherwise). I looked at a few other chat services and finally settled on AOL instant messenger (AIM) because it's very popular and the protocol (TOC) is easy to implement on the Arduino. Although the protocol is proprietary, it is well understood and there are many open source libraries/apps available.

Hardware

Arduino + Wiznet W5100 Ethernet chip. This comes packaged together nicely with the Arduino Ethernet Shield. See below for parts.

Installation and Setup

If you don't already have an AIM account, go to http://www.aim.com/ and register for a free screen name.

Download Chatduino and open in Arduino

The official Arduino ethernet library does not support DHCP. Because of this we need to specify the IP address of the Wiznet. For example:
static byte ip[] = { 192, 168, 1, 99 };
Choose an IP address that is not in use and one that is not used by DHCP. I chose 192.168.1.99 since my router (Linksys) uses 192.168.1.100 and up for DHCP.

The Arduino ethernet library also does not support DNS, so we need to use the IP address of the AIM server. I have provided the two IP addresses for the TOC domain (toc.oscar.aol.com) at this time. Either should work but these could change over time. If you are having connection problems you may need to do a domain lookup on toc.oscar.aol.com to get the new IP addresses.
//static byte server[] = { 64, 12, 202, 14 };
static byte server[] = { 64, 12, 202, 7 };
The default port of the AIM server is 5190. If you are having connectivity issues, it's possible that your network is blocking this port. Try using port 80 instead.

Update: Some users have suggested an alternate Ethernet library that supports both DNS and DHCP. You might want to give this a try.

Scroll down toward the bottom of the sketch and specify your screen name and password:
char screenName[] = "yourscreenanme";
char pass[] = "yourpassword";
Specify the length of the message array. You will still be able to receive messages that are larger but of course only "msgLen" of the message will be stored in the array. Remember that the ATmega has limited memory (1K), so you don't want to make the arrays excessively large.
const uint16_t msgLen = 50;
Specify the length of the "from" screen name. This value should be at least (+1) larger than the largest screen name you expect.
const uint16_t fromLen = 18;
Now add your code inside the if (readMessage(from, fromLen, msg, msgLen) == 0) { block. This statement evaluates to true whenever a message is received.

The from string contains the screen name that sent the message and the msg string contains the message. For example:
if (strcmp(from, "myscreenname") == 0) {
// a message from "myscreenname"
if (strcmp(msg, "get temp") == 0) {
// return analog reading of temp sensor
itoa(analogRead(0), msg, 10);
sendMessage(from, msg);
}
else if (strcmp(msg, "turn on led") == 0) {
// turn on led
digitalWrite(ledPin, HIGH);
sendMessage(from, "ok");
}
}
Because of the bi-directional nature of chat, it's also possible to send messages based on external events. Here's an example:
//Place outside of readMessage block
if (digitalRead(motionPin) == HIGH) {
sendMessage("anyscreenname", "motion detected!");
}
Now you should be able to upload your Sketch and it should sign-on to AIM.

I have provided a few functions that you may find useful. The processPinRequest function provides I/O pin control. For example ar5 returns an analogRead of pin 5, and dw4=1 performs a digitalWrite(4, HIGH). Analog write (PWM) and digital read are also supported. It's recommend that you restrict write operations to verified screen names. To accomplish this, you can set authUser to a specific screen name that is allowed to manipulate the I/O pins. By default all users are allowed to perform pin readings. Refer to the function comments for more information.

It is highly recommended to sign-off from AIM before uploading a new sketch or powering off the unit, or AIM gets confused and may not allow reconnects for a period of time. You can sign-off by sending a "signoff" message. I've encountered a few instances where the Sketch failed to connect to AIM after it was previously connected and signed off. Hitting the reset button a few times seems to fix this issue (wait at least 15 seconds between resets).

The Sketch will automatically attempt to reconnect if disconnected from AIM. You can send the "reconnects" message to get the number of reconnects. I've been running the service for over a week with zero reconnects.

Serial debug can be turned on by setting #define CHATDUINO_DEBUG to 1 at the top of the sketch.

Note: I am using the Wiznet module (WIZ812MJ) directly, without a shield. This means I need to explicitly reset the device on startup. This is done by connecting the Wiznet reset pin to 9 (use a resistor, 1K or so), and setting #define WIZNET812MJ (top of sketch) to 1. The Arduino Ethernet Shield will reset automatically and does not require this step.

Parts

Seeedstudio currently has the best price on the Arduino Ethernet Shield ($29), although they are out of stock at the time of this writing. This is actually a clone but is functionally equivalent to the official shield.

NKC Electronics sells the official Arduino Ethernet Shield for $40 or you can get their version for $32 (requires assembly).

Whatever you choose, make sure you get a Wiznet based device and not Microchip's ENC28J60.

If you're looking for the most cost effective solution (less then the cost of the ethernet shield alone), and you don't mind wiring it together, I recommend an Arduino clone, such as Modern Device's RBBB (~$12) and the WIZNET812MJ (~$21). This setup requires a breadboard/protoboard, 3.3V power, some female/male jumpers, and a USB-serial device to program the RBBB.

Considerations

Currently the sketch only processes incoming messages. Other commands: CONFIG2, UPDATE_BUDDY2, PING etc. are ignored. In a future release I would like to support buddy list updates, which would allow you to receive notifications when your friends signon/signoff.
Other improvements may include using EEPROM to save memory and not blocking on readMessage if the receive buffer is empty. Of course at this time it's just a sketch but if people find it useful, I may release it as an Arduino library.

Saturday, September 12, 2009

Droplet on the SheevaPlug



Droplet requires a Java service to listen for requests from remote Droplets and run background threads for push services. At first I used my notebook to run the service, but it wasn't very convenient since it tended to not stay put, or powered on for very long. Around this time I received a SheevaPlug, after a 1.5 month wait. The SheevaPlug is a low power, compact, ARM based Linux computer and is perfect for running the Droplet service. In this blog entry I describe how to get the Droplet service running on the SheevaPlug.

Setup

If you are using a FTDI usb-serial device to interface with XBee (e.g. XBee Explorer USB), you'll need to upgrade the kernel since the kernel shipped with the plug does not have usb-serial driver support. (Arduino uses the same FTDI usb-serial chip). Fortunately, for those of us not comfortable with compiling Linux, there are prebuilt kernels available with usb-serial support. Here's the wiki entry that describes the installation process.

The SheevaPlug only has 512MB of disk space, so you can quickly run out of disk space after installing a few packages (Java itself requires over 300MB). The solution to this problem is to install an SD card as your primary file system. This step is optional but strongly recommended for the reason that in addition to more disk space, the plug will also run quite a bit faster. Here's a guide that explains how to add an SD card.

Since this is an ARM computer and not x86, we can't simply install Sun's version of Java for Linux. Fortunately Sun open-sourced Java several years ago and the folks at Sun and RedHat have been hard at work on OpenJDK, rewriting the proprietary parts and porting it to additional architectures (ARM, MIPS, PowerPC etc).

Install OpenJDK

apt-get install openjdk-6-jdk
Install RXTX. RXTX provides serial port communications for Java applications

apt-get --no-install-recommends install librxtx-java
Now we are ready to install Droplet.

Make a folder for the application. I'm using "/root/apps/droplet". Zip up your Eclipse project, transfer it to the plug and unzip in the new folder.

Since we don't have Eclipse on the plug, we need a script to start the application. Create the following script and in a file called droplet.sh

#!/bin/bash

# application entry point
MAIN_CLASS=com.rapplogic.droplet.impl.DropletDemo

# specify path to the rxtx jar.  default is for librxtx-java
RXTX_JAR=/usr/share/java/RXTXcomm.jar

# location of rxtx modules when installed with apt-get
RXTX_LIB_PATH=/usr/lib

# FTDI should appear as /dev/ttyUSB0 if this is the only USB device
COM_PORT=/dev/ttyUSB0

# build classpath
JAR_LIST=`find lib -name '*.jar'`

# add all jar libraries to classpath
for file in $JAR_LIST
do
#echo "adding $file to classpath"
CLASSPATH=$CLASSPATH:$file
done

# append droplet compiled classes (bin) and rxtx jar to classpath
CLASSPATH=$CLASSPATH:bin:$RXTX_JAR

# start Java with a 16MB heap, using the Cacao JIT compiler
java -cacao -Xms16m -Xmx64m -classpath $CLASSPATH -Djava.library.path=$RXTX_LIB_PATH $MAIN_CLASS $COM_PORT 2> droplet.err 1> /dev/null
Make the script executable:

chmod u+x droplet.sh
Note: I'm running the application as root. This probably isn't a good practice but the plug is running inside my firewall, with no ports open to the public, so I think it's fine.

Since we installed RXTX with the lib-rxtx package, we want to remove the RXTX Java library from the project so we don't have multiple RXTX libraries in the classpath.

rm lib/RXTXcomm.jar
And for good measure remove the RXTX native Linux library.

rm librxtxSerial.so
You should now be able to to start Droplet:

./droplet.sh
Check the log if things aren't working as expected (log file is ./logs/droplet.log). Go ahead an kill the app (Ctrl-c).

We are not quite done yet. To have the application start whenever the SheevaPlug boots, we need to add a startup hook. But first we'll need to create another script to start the application in the background. Save this script as droplet-nohup.sh

#!/bin/bash

cd /root/apps/droplet/
nohup ./droplet &
Make it executable

chmod u+x droplet-nohup.sh
To add the startup hook, insert a call to the droplet-nohup.sh script at the end of the "do_start" function in /etc/init.d/bootmisc.sh

# startup droplet
sudo /root/apps/droplet/droplet-nohup.sh
Now reboot the plug and it should automatically start Droplet.

Performance

The performance of OpenJDK Java on the plug is not great but mostly sufficient. I had to rewrite the Twitter service, replacing XPath with StAX because the XPath implementation was consistently timing out. It was taking 20 seconds or more just to parse the friends's timeline! In comparison, the StAX implementation completes in about 5 seconds. In hindsight I could have used JSON, which is quite fast and is already used for Twitter search.

One possible explanation for the slow performance is the lack of Hotspot in OpenJDK. A significant portion of Hotspot was written in assembly, making it very difficult to port. The IcedTea project has been working to address this gap, first with Zero and now with Shark, which is based on LLVM. See this article for more information.

What Else?

I've been running Droplet on the SheevaPlug for the last couple weeks. Overall I've been quite pleased although there is one issue that has popped up a few times. For no apparent reason the Twitter service thread will block indefinitely on what appears to be the HTTP call. When this occurs the rest of the app continues to function but you won't see new Tweets. If you encounter this issue just restart the app.

You may need to adjust the timeout if you are getting "Application Timeout" messages. Usually this will only occur for the Twitter services. This is a two step process. First open the Arduino Sketch and adjust the following line:

#define APPLICATION_TIMEOUT 7500
Then open Droplet.java and adjust the timeout:

private long serviceTimeoutMillis = 6000. 
The Arduino timeout will always need to be about 1.5 seconds longer than the Java timeout to account for the packet roundtrip transmission time. Unless you have installed the Java compiler on the plug, you'll need to make this change in Eclipse, then copy the "bin" folder to the plug.

Now that you are using the only USB port, if you need additional ports you can add a USB hub. I've found that the Belkin F5U407 USB hub works great with the SheevaPlug. Keep in mind you should not be drawing much power from the USB hub, unless you are using a powered hub.

Conclusion

So now you have an always on Droplet service! Place your plug at the back of your desk, or anywhere you have an ethernet connection and forget about it. You should be able to place Droplet remotes anywhere, within range, and they will just work. Of course since the SheevaPlug is a general purpose Linux computer, you can use for other purposes, web server, file server etc.

Update: I installed a 90 day evaluation of Sun's Java SE for Embedded and the performance is now significantly improved. They have several different versions but the one that's compatible with the SheevaPlug is Java SE for Embedded 6.0 Update 10 ARMv5 Linux Early Access, EABI, glibc 2.5, Soft Float, Little Endian (Headless or Headful). Now I just need to figure out how to purchase the full version, as there is no hint on their site of how to do so.

Friday, August 7, 2009

Droplet

Several months ago I released XBeeArduinoService. Since then I have been working on the next version. It's now available and has a new name: Droplet.

What's New
  • Menu/Navigation: The first version relied on a dedicated button for each service. This worked but doesn't scale well since you will quickly run out of buttons. Now it uses a menu to display the available services. Along with the menu are four buttons for navigation: Next, Previous, Select, and Escape.


  • Pagination: Previously, each message was limited to 80 characters, which of course is not much. Now messages that exceed 80 characters are formatted into multiple pages. A multi-page message is indicated by an arrow "⇾" character in the bottom right corner. When this character is present, selecting the Next button loads the next page.

  • Push Services: these are services that run in the background and send messages (or droplets) to the remote. I have written push services for Twitter, Gmail and Google Calendar. Note: these are different from pull services, which are user initiated and are listed on the menu.
  • History: You can now access previous messages that were sent to the LCD. This is helpful if you clicked the Next button too quickly, or you want to see any messages that were sent while you were away (e.g. missed tweets, calendar events). Selecting the Previous button while any message is displayed will load the previous message.
  • New Services: Twitter, Twitter Search, Google Calendar, IMAP "Push" Email (e.g Gmail) and Top News.
  • Light and Sound: A red LED flashes when a Push Service sends a message (tweets/calendar/email) and optionally a buzzer can sound. The Google Calendar service sounds the buzzer if "#buzzer#" appears in a Google Calendar event description. It effectively becomes a Google alarm clock when using the buzzer option with Google Calendar.
  • Multiple Remotes: This version includes better support for multiple remotes. You can use the XBee broadcast address (0x00000000ffff) to send messages to all remotes or target one or more specific remotes.
A Demo

Parts

For this version I added the following parts:
Circuit

In an attempt to not repeat myself, in this entry I'm only going to cover what's new, so you'll want to familiarize yourself with the previous version.

Now to wire up the new parts. Connect the buzzer to the Arduino Analog 0 and the negative terminal to GND. Yeah, that's right, Arduino lets you repurpose the Analog pins as Digital outputs. Analog 0-5 can accessed as Digital pins 14-19. Pretty neat, huh? Here's some more info.

Wire the LED to the Arduino Digital 5 pin and GND, placing your resistor in series.

Wire each push button to GND and the following Arduino Pin:

Next: Digital 2
Previous: Analog 1 (We are also repurposing this pin to be Digital, same as the buzzer pin)
Select: Digital 3
Escape: Digital 4

In case you're wondering whether it's safe or even a good practice to connect a button without a pull-up/down resistor, keep reading. In the previous version I was using a pull up resistor to drive the pin to a known state while the button was in its normally-open position. It turns out there's an easier way to do this by using the Arduino's internal pull up resistors. The pull-up resistors are enabled by setting the pin mode to INPUT, then turning them on with a digital write HIGH. In this configuration, the pin will read HIGH until the button is depressed, when it will read LOW, and we don't need to mess with any resistors! Thanks to todbot for this technique.

XBee Configuration

The XBee configuration is identical to the previous version.

Software

Download the project software from Google Code. This download includes everything you need to get it up and running except for Java (requires Java 5) and Eclipse. Eclipse is recommended but isn't required and if you are experienced with Java you can use your IDE of choice or run from the command-line. Being Java, this software will run on a variety of platforms (Windows, Mac, Linux etc.). The only limiting factor is the RXTX serial library and I've included binaries for Windows, Mac and Linux (Arduino uses RXTX also).

As before, you need to install two Arduino libraries. XBee-Arduino for XBee communication and LCD4x20 to display text on the LCD. Unzip the downloaded file and you will find these files in the "Arduino" folder of the project:

LCD4x20-781.zip
xbee-arduino-0.1.2.zip

Unzip each file and install in your ARDUINO_HOME/hardware/libraries folder
(where ARDUINO_HOME is the location of Arduino on your system. On my machine it's /Users/andrew/Documents/devsoft/arduino-0016/hardware/libraries)

After installation, you should have XBee and LCD4x20 folders under ARDUINO_HOME/hardware/libraries. Note: the LCD library was updated since the previous version, so if you have the previous version, delete the LCD4x20.o file, to force Arduino to recompile the library.

Now upload the Sketch to the Arduino. The sketch (DropletRemote.pde) is located in "Droplet/Arduino/DropletRemote". But before uploading we need to make one minor change. Find the following line:

XBeeAddress64 addr64 = XBeeAddress64(0x0013a200, 0x403e0f30);

and replace the 64-bit address with the (SH + SL) of your XBee coordinator. See the previous blog entry for info on how to get your SH + SL address. Remember to put the XBee Shield jumpers in the "USB" position to upload the Sketch and return the jumpers to the "XBee" position after a successful upload.

Now for the Service Gateway. As before, create an Eclipse project and point it to the Droplet folder. Refer to the previous version if necessary. There are just a few things to change in the Java file. Open DropletDemo.java in Eclipse. This file is located in "/src/com/rapplogic/droplet/impl/" In this file you'll need to specify the address of the remote XBee for the variable "remoteXBee". Also specify the COM port and baud rate of the XBee coordinator.

Since we are using Google and Twitter, you'll need to specify the login information to access your accounts if you want these services. Open google.credentials and specify your Google/Gmail username and password. Now open twitter.credentials and specify your Twitter username and password.

The demo starts up all available services, but possibly you are not interested in running them all. If this is the case, comment out the services you do not want with the Java comment syntax "//" at the beginning of the line. Note: the weather services requires a zip code, so if you are using these services search for "20175" and replace with your zip code. Save the file.

Ok now we're ready to run it. Power up the Arduino, connect the XBee coordinator to the computer and start the application by selecting "Run" from the "Run" menu. Choose one of the services on the LCD menu and click the Select button and you should see the resulting message on the LCD. Woohoo!

Creating Custom Services

The service API has changed significantly since the last version, making it easier to write your own Droplet services. First you need to decide whether you want to create a Pull Service or Push Service. A Pull Service is listed on the menu and is only activated by a button press on the remote. A Push Service runs in the Java application on a periodic basic and sends messages to the remote.

Pull Services

Adding a Pull Service requires an implementation of the PullService interface. This can be accomplished by creating a class that implements the interface, or for simple services, using an anonymous inner class, as in this example. The PullService interface requires that you implement the execute method:

public IContent execute(Integer serviceId, XBeeResponse response, ServiceContext serviceContext) throws Exception

This method provides the following arguments:
  • serviceId: this is the menu position of the service, starting at 0. If you have a single service that supports multiple menu items, you'll need to refer to this variable.
  • response: the XBeeResponse received from the Arduino. This is useful if you need to know the remote that sent the request or any of details of the transmission, such as RF strength.
  • serviceContext: this object exposes several methods of the Droplet API, the most important method for Pull Services being the format method. This method is acessed by serviceContext.getFormatter().format(String); it takes an arbitrary String, of any length, and formats it for display on a LCD. This method will handle pagination if the String doesn't fit on the LCD. This method also optimizes the String for display on the LCD, according to the PaginationRules class. (see Javadoc for details of all Droplet classes/interfaces).
Within this method you can do whatever you need (send an email, turn on your sprinklers etc.), but the service method must return in a timely manner, specifically 6 seconds, although this is configurable. This method returns an instance of IContent, which really a fancy container of the text to be displayed on the LCD. You can return null but the framework will still send a default message to the remote. Here's an example:
PullService time = new PullService() {
public IContent execute(Integer serviceId, XBeeResponse response, ServiceContext serviceContext) throws Exception {

return serviceContext.getFormatter().format("The current time is " + new Date() + ". You will see this response on your LCD after the service executes. You can even use linefeed characters \n to format the response");
}
};
The last activity is to register your service with the Droplet framework. The first argument of the register method is the position of the item in the menu. The position starts at 0, so the second item in the menu is 1.
droplet.registerPullService(1, time);
Now you will need to add your new service as the second menu item in the Arduino Sketch. After the Sketch has been uploaded you should be able to select the second menu item on the remote and it will activate your service. Note: if you change the number of items in the menu, make sure to update the menuSize variable to the new number.

Push Services

There are three types of Push Services: Realtime, Delayed and Runnable. A Realtime service returns a message to be delivered immediately to the remote. A Delayed service returns a message to be delivered to the remote at some point in the future. And finally, a Runnable service occupies a dedicated thread and runs all the time. The Realtime and Delayed services are further divided into two subtypes: Recurring and OneTime, where Recurring services are invoked periodically, and OneTime services are invoked only once.

To create a Realtime service, you will need to extend the RealTimeAlertPushService and implement either the RecurringService or OneTimeService interface. To simplify things, there is a SimpleRealtimeRecurringPushService class that extends RealTimeAlertPushService and implements RecurringService, along with some default values.

Unlike Pull Services, Push Services need to know the 64-bit address of the remote XBee that should receive the message. Optionally you could specify a broadcast address to send to all remotes. Here's an example of a Push Service that sends a wake up alert every weekday at 6 AM. The service returns an instance of the Alert class. An Alert contains properties for determining whether or not the LED should flash or the buzzer should sound. While Pull Services always return a message to the LCD for display, Push Services may return null, in which case nothing is sent.
XBeeAddress64 remoteXBee = new XBeeAddress64(0x00,0x13,0xa2,0x00,0x40,0x0a,0x3e,0x02);

SimpleRealtimeRecurringPushService wakeUp = new SimpleRealtimeRecurringPushService(remoteXBee) {

@Override
public Alert execute(ServiceContext serviceContext) throws Exception {
Calendar cal = Calendar.getInstance();

int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);

if (dayOfWeek == Calendar.SATURDAY || dayOfWeek == Calendar.SUNDAY) {
// it's the weekend. you can sleep in
return null;
}

int minute = cal.get(Calendar.MINUTE);
int hour = cal.get(Calendar.HOUR_OF_DAY);

if (hour == 6 && minute == 0) {
// wake up!
Alert alert = new Alert();
alert.setContent(serviceContext.getFormatter().format("It's Wake Up Time !!!!!!"));
alert.setRemoteXBeeAddress(this.getRemoteXBeeAddress());
// flash the LED
alert.setFlashLed(true);
// sound the alarm!
alert.setSoundAlarm(true);

return alert;
}

// not time yet
return null;
}
};

// run once a minute (delay is in milliseconds, so multiple by 1000)
wakeUp.setDelay(60000);
wakeUp.setName("WakeUp");

// register the service with the framework
droplet.registerPushService(wakeUp);
For more examples, look in the com.rapplogic.droplet.impl.services.* packages for the source code to all services.

What's Next?
  • Plug Computer. I recently received a SheevaPlug. SheevaPlug is a low power ARM computer, about the size of a wall adapter, that runs Linux. My goal is to run the Droplet Java application on the SheevaPlug.
  • Extra Pins. After everything is wired-up, we still have 6 Arduino pins left: 4 Analog or Digital (Analog 2-5, Digital 16-19) and 2 Digital (12, 13), so you could wire up some dedicated "Radio" buttons, sensors, or even add a second LCD.
  • Case. Ok, this thing is a bit ungainly. At some point I'll start working on a case to hide the internals.
  • Larger Display. I've been on the lookout for a low cost alternative to the 4x20 LCD, with support for displaying more than 80 characters.

Comments are great but if you have questions please send email instead since I may not see the comments for a few days or a possibly longer. My email can be found from the "view my complete profile" link in the right column.

Monday, June 22, 2009

Google Talk XBee Motion Detector



I bought a PIR motion detector from SparkFun a while back. I played around with it a bit, and it works great (at 9V, not 5V), but being hardwired had its limitations. So recently I started a project to make the motion detector wireless, with an XBee radio, and send motion events over Google Talk. This allows you to easily monitor motion events while at work or away.

As for ideas on what to use it for: you could aim the PIR at your couch and see if your pets are violating the couch policy, get notified when your wife/kids get home, place by your front door and find out when a package arrives etc.


This solution uses the XBee I/O Line monitoring feature to send motion detection events to the XBee coordinator. The change detection feature is used to send an I/O sample whenever the PIR alarm pin goes low. With this configuration, the PIR alarm pin is connected directly to an XBee digital input pin, not requiring a micro-controller.

A Java class (XBeeMotion) uses XBee-API to receive motion events and send them to Google Talk. The Google Talk communication is provided by XBee-XMPP, which depends on the Smack library for XMPP.

Ingredients
XBee Configuration

This software works for both Series 1 and 2 XBees. For Series 2 you will need to load the API Coordinator and End Device firmware, if you don't already have it installed. Series 1 is nice in that one firmware image supports both AT and API mode and Coordinator/End Device.

Coordinator Configuration

Series 2 XBees only: place one of the XBees in the XBee Explorer and use X-CTU to flash the ZB Pro API Coordinator Firmware (version 2141 at this time).

Now apply the following configuration (both series, except where noted). Digi did a nice job providing a consistent command set across Series 1 and 2 XBees.


Click the "Restore" button to clear out any previous configurations.
You can skip this step if you just flashed the firmware. Note: for series 1,
this will change the API (AP) mode to 0 (disabled), so you will need to go back to
the "PC Settings" tab and uncheck the "Enable API" checkbox. Then, after you set AP=2
and click "Write", you'll need to re-enable this setting.

Set PAN ID to an arbitrary value. The End Device must also use this exact value
ID=1AAA

Set the node identifier to an arbitrary string.
This serves as a convenient way to identify your devices
NI=COORDINATOR

Series 1 Only. Make this the Coordinator
CE=1

Series 1 Only. Set the 16-bit XBee Address to any value
MY=1234

Set API mode to 2 (escape control bytes)
AP=2

Click the "Write" button to save the configuration


End Device Configuration

Series 2 Only: place the other XBee in the XBee Explorer and apply the ZB Pro API End Device Firmware (version 2941)


Click the "Restore" button to clear out any previous configurations

Set PAN ID to an arbitrary value. The End Device must also use this exact value
ID=1AAA

Set the node identifier to an arbitrary string. This serves as a convenient way to identify your devices
NI=MOTION

Series 1 Only. Make this an End Device
CE=0

Series 1 Only. Set the Destination Low Address to the Coordinator
DL=1234

Series 1 Only. Set the 16-bit XBee Address to any value
MY=5678

Set API mode to 2 (escape control bytes)
AP=2

Set DIO4 as A digital input
D4=3

Turn on change detection for DIO4 (bit 4 -> 16 = 0x10)
IC=10

Since we reset the XBee to the factory default, the internal pull-up resistors (PR) are on. The internal pull-up will drive the pin high when left floating

Series 2 Only. Set the Sleep mode to Pin Hibernate. You will need to ground pin 9 to keep the XBee awake
SM=1

Click the "Write" button to save the configuration


The PIR

I know the SparkFun site says the motion detector works at 5V, but trust me it does not. It may seem like it's working but it will fire erratically at times. My guess is that it would be stable at 6 volts or more but I know it works at 9V. According to the SparkFun product description, the PIR can be powered at 3.3V by soldering a jumper. This would be ideal however I have not tested this.

Google Talk

You will need two Google accounts, one for sending and one for receiving. Note: if you have a Google Apps domain, you can create additional user accounts through the administration tool.

Software Installation

Download the software from Google Code, unzip and create an Eclipse project

Open xbee-motion.properties

Enter your Google username/password. Notice the username must include "@gmail.com", unless you have a Google Apps account in which case it must include "@yourdomain.com"

gtalk-username=yourusername@gmail.com
gtalk-password=yourpassword

Specify the Google Talk user(s) to that will receive motion event messages.
You may enter one or more (comma separated list) GTalk users

gtalk-recipient=yourothergmailusername@gmail.com

Specify the COM port of the local XBee (Coordinator):

xbee-com-port=COM1

Enter the XBee Coordinator baud rate (XBee default is 9600):

xbee-baud-rate=9600

Set the minimum number of seconds between motion events, or 0 if you don't want a delay

motion-delay-seconds=3

Wire it Up

The PIR has three pins:

Red (9V)
Brown (Ground)
Black (Alarm)

Place the XBee End Device in the XBee Explorer Regulated and power it by running the 9V supply through the 5V Voltage Regulator. Connect the 4.7K resistor from 3.3V (XBee pin 1) to the Alarm pin. This will pull up the Alarm pin to 3.3V when there is no motion. Connect the red wire to 9V and the brown wire to Ground.
Now wire the Alarm pin to XBee pin 11 (DIO 4). The Alarm pin will go low (0V) during motion and trigger an I/O sample to be sent to the XBee Coordinator.



Series 2 only -- Remember to connect pin 9 (Sleep Mode) to ground (pin 10) if you are using ZB Pro firmware.

* I'm using a USB Explorer for the Motion Detector XBee, but it's only using the USB connection for power -- I promise. I didn't have a XBee Breakout board on hand.

Start the Application

Connect the Coordinator XBee to your computer and start XBeeMotion.java in Eclipse (Run->Run As->Java Application). You should see output in the Eclipse console. Login to the Google Talk with the account that you specified as a recipient, in xbee-motion.properties (gtalk-recipient). Now power up the XBee End Device and PIR. Initiate furious hand waving over the PIR sensor and you start to see IM messages appear.

Next Steps

While motion is present, the PIR generates a steady stream of events, about 1 every 300ms. This is somewhat undesirable as it clutters the airspace with unnecessary transmissions. A better solution would be to connect the PIR alarm pin to an Arduino to "debounce" the signal. For example, only report motion event every x seconds. For this solution, an Arduino digital output pin would need to be connected to the XBee DIO4, to trigger an XBee change detection I/O sample. The problem here is Arduino is 5V and XBee is 3.3V, so a logic level shifter board would be necessary to drop the voltage to 3.3V.

A even more sophisticated solution would involve using serial communication between an Arduino and XBee to send motion events, instead of I/O line monitoring. To do this you could use the XBee Arduino library to send motion packets to the XBee Coordinator. With this solution you could be informed of motion delivery failures and attempt redelivery. It's also possible to send meta data along, for example if you wanted to indicate continuous motion for x seconds. A benefit of this approach is that we only require a single TX packet per motion event, whereas I/O change detection involves two 2 packets per motion event: motion on and motion off.

Resources