Back to imamuseum.org

Tracking the Island Resident with Arduino

Andrea Zittel, American, b. 1965, “Indy Island,” 2010. Commissioned by the Indianapolis Museum of Art, Courtesy of the Artist and Andrea Rosen Gallery, New York.

As you may or may not already know, the IMA organizes an artist residency each summer on Andrea Zittel’s Indy Island within 100 Acres.  This year, the park will be inhabited by A. Bitterman and the project is called Indigenous.  As part of the project, Mr. Bitterman wants to provide spectators an opportunity to track the artist.

To accomplish this, we first looked into commercial GPS solutions that would allow us to send realtime GPS data over the web that could then be plotted on a map.  The closest thing we found is the Garmin Communicator API that works with select devices.  Unfortunately this came with limitations on how often data could be polled, so it turned out to be less then a desirable solution.

Enter Arduino.

What is this strange word and what does it have to do with tracking artists?  Arduino is an open source microcontroller for scientists, engineers, programmers, and hobbyists.  Stopping short of my personal opinion that this little device will revolutionize hardware like Linux revolutionized software, I will say it was exactly what we needed.

Most any store that sells an Arduino also sells what are called “shields.” These shields allow you to attach different electronic circuits to the Arduino so that you can program your software to control and utilize them.  For our application, we needed a GPS shield to track our artist and a cellular GSM shield to transmit the data back to us over the cellphone network.

All of the data aquired is plotted on a map using a hex binning algorithm

WARNING: the following contents are about to get very technical and nerdy.  If you aren’t interested in the technical bits and just want to see where the artist is, you can jump straight to the website to “track the artist.”

Overview

This device utilizes the Arduino Uno R3, the sm5100b GSM/GPRS cellular shield and the EM406 GPS shield.  The design was based off the work done by Jayesh and tronixstuff.  When the system is provided with power, it will first initialize the GPRS network.  Once the network has been initialized it will attempt to acquire GPS data and proceed to send the data back to a server running a Python script on port 81.  The Python script will receive the data and store the result into a MySQL database.  All of the source code for this project is available at github.  So fork it and try it out!

Assembly

Because the shields come pre-assembled, this step is relatively straight forward.  Along with your shields, you will need two sets of 6 and 8 pin headers per shield.  Solder these on and simply stack the GSM shield onto your Arduino, with the GPS shield on top of that.  If you would like to utilize the diagnostic LEDS, connect a green LED and 220 ohm resistor in series from pin 13 to ground, and a red LED and 220 ohm resistor in series from pin 12 to ground.  On the GPS shield, flip the switch from DLINE to UART.

Optional:  When the time came to test the device on battery power, I had an issue that prevented the sm5100b from initializing on power up.  This chip requires a fair amount of current when it is first powered on (up to 2amps).  The issue only occured for me when the GPS shield was powered on, as well.  To avoid this, I removed the 3.3v, 5v, and vIn pins from bottom side of the headers on the GPS shield.  After removing those, I ran a jumper from digital pin 9 to the 5v pin on the header.  More about this in the code break down.

Programming the Arduino

I will only cover the import sections of the source code in this article.  If you would like the full source, see the link at the bottom of this article.  Before you begin, you will need to download the PString and TinyGPS libraries from Arduiniana.

Error Handling:

/*****************************************
 * Error codes
 */
const int ERROR_GPS_UNAVAIL  = 0;
const int ERROR_GPS_STALE    = 1;
const int ERROR_SIM_UNAVAIL  = 2;
const int ERROR_GPRS_FAIL    = 3;
const int ERROR_NETWORK_FAIL = 4;
const int ERROR_HOST         = 5;
const int ERROR_GPRS_UNKNOWN = 6;
const int ERROR_GSM_FAIL     = 7;
const boolean DEBUG          = true;

/**
 * Flash an LED X amount of times and write to console based on the error code given
 */
static void error(const int errorCode) {
  int flashTimes = 0;
  int i = 0;
  boolean severity = true;

  switch(errorCode) {
    case ERROR_GPS_UNAVAIL:
      Serial.println("ERROR: GPS Unavailable");
      // This error is not severe enough to break the main loop
      severity = false;
      break;
    case ERROR_SIM_UNAVAIL:
      flashTimes = 3;
      Serial.println("ERROR: SIM Unavailable");
      break;
    case ERROR_GPRS_FAIL:
      flashTimes = 4;
      Serial.println("ERROR: Connection to network failed");
      break;
    case ERROR_NETWORK_FAIL:
      flashTimes = 5;
      Serial.println("ERROR: Could not connect to network");
      break;
    case ERROR_HOST:
      flashTimes = 6;
      Serial.println("ERROR: Could not connect to host");
      break;
    case ERROR_GPRS_UNKNOWN:
      flashTimes = 7;
      Serial.println("ERROR: Unknown");
      break;
    case ERROR_GSM_FAIL:
      flashTimes = 8;
      Serial.println("ERROR: GSM Timeout");
      break;  
  }

  digitalWrite(LED_STATUS, LOW);

  // Setup an infinite loop to require reset if something goes wrong
  while (severity) {
    blinkLed(LED_ERROR, flashTimes, 500);
    delay(1000);
  }
}

First, we define some constants for error handling, We will provide handling on a few different errors that, when triggered, will flash the red LED, much like the diagnostic codes that cars provide.  Our error handling function can then be called anywhere in the code by passing one of the predefined constants to trigger an error.  The switch statement will then tell us how many times to flash the error LED, and it will stay in an infinite loop repeating the diagnostic message until the Arduino is powered down or reset.

Setting up the shields:

/******************************************
 * Pin Definitions
 */

// UART MODE
int GPS_TX = 0;
int GPS_RX = 1;

// DLINE mode
//int GPS_TX = 2;
//int GPS_RX = 3;

int GPRS_TX = 2;
int GPRS_RX = 3;

int LED_STATUS = 13;
int LED_ERROR = 12;

int GPS_RELAY = 9;

Here we define the pins, The GPS shield transmits on pins 1 and 2, which means it will also be sharing the serial line with our Arduino.  The sm5100b will utilize pins 2 and 3 and we will use the SoftwareSerial library to interface with.  We also define our diagnostic LED pins as 12 and 13.  Lastly, our GPS relay is defined on pin 9.  This is only necessary if you have issues initializing the sm5100b on battery power.

Then we create a new TinyGPS instance and initialize a SoftwareSerial instance for the sm5100b.  In the setup() function, our LED pins and GPS relay pins are set as output.  Because the GPS shield is sharing its serial line with the Arduino, we don’t need to setup a software serial instance for it.  We simple call Serial.begin(4800), which will establish a serial line at 4800 baud,  the speed that the GPS chip communicates.  We also need to begin the software serial instance at 9600 baud by calling cell.begin(9600);

NOTE:  The sm5100b module should come preconfigured at 9600 baud.  In my case, it was configured at 152800 baud.  If this happens, you will need to connect a jumper from pin 1 to 4, and pins 2 to 3.  Create a sketch that defines an empty setup() and loop function and upload that to your Arduino.  Then from the serial monitor, send the command AT+IPR=9600.  This will configure the sm5100b to communicate at 9600 baud.

The main loop:

/*********************************************************
 * Main Loop
 */
void loop() {
  if (firstTimeInLoop) {
    firstTimeInLoop = false;
    Serial.println("Establishing Connection"); 
    establishNetwork();

  }

  bool newdata = false;
  unsigned long start = millis();

  while (millis() - start < 1000)	  	
  {	  	
    if (gpsAvailable())	  	
      newdata = true;
  }

  // Retrieve GPS Data
  pollGPS(gps);

  // Send data to cell network
  sendData(myStr);

  delay(SEND_DELAY);
}

First we check if firstTimeInLoop is true.  If it is, we know we need to wait for the sm5100b to initialize before we can start acquiring data from the GPS.  Once our cellular connection is established, we poll the GPS shield to get the data and then proceed to send that data back to the server.  Once that is complete, we will pause for ten seconds and then the loop will be repeated.

Initializing the cellular network and handling GSM responses:

/**
 * store the serial string in a buffer until we receive a newline
 */
static void readATString(boolean watchTimeout = false) {
  char c;
  char buffidx = 0;
  int time = millis();

  while (1) {
    int newTime = millis();

    if (watchTimeout) {
      // Time out if we never receive a response    
      if (newTime - time > 30000) error(ERROR_GSM_FAIL);
    }

    if (cell.available() > 0) {
      c = cell.read();
      if (c == -1) {
        at_buffer[buffidx] = '\0';
        return;
      }

      if (c == '\n') {
        continue;
      }

      if ((buffidx == BUFFSIZE - 1) || c == '\r') {
        at_buffer[buffidx] = '\0';
        return;
      }

      at_buffer[buffidx++] = c;
    } 
  }
}

/**
 * Send an AT command to the cell interface
 */
static void sendATCommand(const char* ATCom) {
  cell.println(ATCom);
  Serial.print("COMMAND: ");
  Serial.println(ATCom);

  while (continueLoop) {
    readATString();
    ProcessATString();
    Serial.print("RESPONSE: ");
    Serial.println(at_buffer);
  }

  continueLoop = true;
  delay(500);
}

/*
 * Handle response codes
 */
static void ProcessATString() {

  if (DEBUG) {
    Serial.println(at_buffer);
  }

  if (strstr(at_buffer, "+SIND: 1" ) != 0) {
    firstTimeInLoop = true;
    GPRS_registered = false;
    GPRS_AT_ready = false;

    // GPS unit pulls enough current to keep the GSM shield from starting
    // So we wait until it's initialized and then power it via a digital pin
    digitalWrite(GPS_RELAY, HIGH);
  }

  if (strstr(at_buffer, "+SIND: 10,\"SM\",0,\"FD\",0,\"LD\",0,\"MC\",0,\"RC\",0,\"ME\",0") != 0 
    || strstr(at_buffer, "+SIND: 0") != 0) {
    error(ERROR_SIM_UNAVAIL);
  }

  if (strstr(at_buffer, "+SIND: 10,\"SM\",1,\"FD\",1,\"LD\",1,\"MC\",1,\"RC\",1,\"ME\",1") != 0) {
    Serial.println("SIM card Detected");
    successLED();
  }

  if (strstr(at_buffer, "+SIND: 7") != 0) {
    error(ERROR_NETWORK_FAIL);  
  }

  if (strstr(at_buffer, "+SIND: 8") != 0) {
    GPRS_registered = false;
    error(ERROR_GPRS_FAIL);
  }

  if (strstr(at_buffer, "+SIND: 11") != 0) {
    GPRS_registered = true;
    Serial.println("GPRS Registered");
    successLED();
  }

  if (strstr(at_buffer, "+SIND: 4") != 0) {
     GPRS_AT_ready = true;
     Serial.println("GPRS AT Ready");
     successLED();
  }

  if (strstr(at_buffer, "+SOCKSTATUS:  1,0") != 0) {
     error(ERROR_HOST);
  }

  if (strstr(at_buffer, "+CME ERROR: 29") != 0) {
    continueLoop = false;
    return;
  }

  if (strstr(at_buffer, "+CME ERROR") != 0) {
    error(ERROR_GPRS_UNKNOWN);
  }

  if (strstr(at_buffer, "OK") != 0 || strstr(at_buffer, "NO CARRIER") != 0) {
    continueLoop = false;
    successLED();
  }

  if (strstr(at_buffer, "+SOCKSTATUS:  1,1") != 0) {
    continueLoop = false;
  }
}

static void establishNetwork() {
  while (GPRS_registered == false || GPRS_AT_ready == false) {
    readATString(true);
    ProcessATString();
  }
}

Talking to the sm5100b consists of three primary function.  ReadATString() creates a buffer that will read any responses character by character from the serial line.  Once a line break is received, the function returns with the variable at_buffer containing the contents of our response.  Once the buffer is filled, ProcessATString() is called and will compare the response to a few expected outcomes.

When the sm5100b initializes it will return the string +SIND: 1.  This is where the previous hack I mentioned with the GPS occurs.  As previously noted, we remove all pins that provide power to the GPS.  This will prevent the chip from powering up when the Arduino is powered on.  Once we receive the +SIND: 1 command telling us that the sm5100b is powered on and has detected the SIM card, we pull pin 9 high with will supply 5v to the GPS, thus powering it on.  Once we receive +SIND: 11 and +SIND 4, the cellular connection has been established and we can proceed to send AT commands.

Polling GPS data:

/*********************************************************
 * GPS Functions
 */

static void pollGPS(TinyGPS &gps) {
      unsigned long age;
      float lat, lng, speed;

      // Acquire data from gps
      gps.f_get_position(&lat, &lng, &age);
      speed = gps.f_speed_mph();

      if (lat == TinyGPS::GPS_INVALID_F_ANGLE) {
        error(ERROR_GPS_UNAVAIL);
      }

      /*
       * create string of "speed,latitude,longitude" to send to server
       */
      myStr.print("AT+SSTRSEND=1,\"");
      myStr.print(speed);
      myStr.print(",");
      myStr.print(lat, DEC);
      myStr.print(",");
      myStr.print(lng, DEC);
      myStr.print(",");
      myStr.print(TinyGPS::cardinal(gps.f_course()));
      myStr.print("\"");

}

Here we acquire the data via our instance of the TinyGPS class and build the AT command that will send the data to the server via the PString instance we created.   We are going to log latitude, longitude, cardinal direction, and speed.

Sending the data:

static void sendData(const char* data) {
  digitalWrite(LED_STATUS, HIGH);

  Serial.println("Setting up PDP Context");
  sendATCommand("AT+CGDCONT=1,\"IP\",\"wap.cingular\"");

  Serial.println("Configure APN");
  sendATCommand("AT+CGPCO=0,\"\",\"\", 1");

  Serial.println("Activate PDP Context");
  sendATCommand("AT+CGACT=1,1");

  // Change 0.0.0.0 to reflect the server you want to connect to
  Serial.println("Configuring TCP connection to server");
  sendATCommand("AT+SDATACONF=1,\"TCP\",\"0.0.0.0\",81");

  Serial.println("Starting TCP Connection");
  sendATCommand("AT+SDATASTART=1,1");

  delay(1000);

  //Serial.println("Getting status");
  //sendATCommand("AT+SDATASTATUS=1");

  Serial.println("Sending data");
  sendATCommand(data);

  //Serial.println("Getting status");
  //sendATCommand("AT+SDATASTATUS=1");

  Serial.println("Close connection");
  sendATCommand("AT+SDATASTART=1,0");

  Serial.println("Disable PDP Context");
  sendATCommand("AT+CGACT=0,1");

  // Clear string and flash LED
  myStr.begin();
  successLED();

  digitalWrite(LED_STATUS, LOW);
}

Now that we have our AT command ready to send, we will have to send all of the required commands to establish our GPRS connection.  Once the connection has been established, we will connect to the host and send the data.  Once the data has been sent we close the connection.  MyStr.begin() is called to clear the PString buffer and successLED() is called to blink the status LED, just so there is some type of visual indicator that the transmission has been sent.

This is just a small breakdown of the functionality of the Arduino sketch.  If you would like to see the full source code go here.

Server side

For the server side, we are going to use Python and leverage the open source socket library Twisted.  The examples sited at the beginning of this article use Python’s built in sockets class to aquire data.  I decided to use Twisted as it already has built in support for multiple connections, which is useful if for any reason our client does not successfully close the connection.  Those connections can gracefully time-out without impeding any new attempts to connect to the server.  Twisted can also handle exemptions without killing the process, which also proves useful.

server.py:

from twisted.internet.protocol import Factory, Protocol
from twisted.internet import reactor

import time
import _mysql
import config
import pprint

class SendContent(Protocol):
    def connectionMade(self):
        print 'Accepted connection'
    def dataReceived(self, data):
        date = time.time()
        values = data.split(',');
        if len(values) != 4:
            print 'Error: Bad Data'

        db = None;
        try:
            db = _mysql.connect(config.hostname, config.username, config.password, config.database)
            db.query("INSERT INTO tracker (date, speed, lat, lng, course)" +
                "VALUES ('" + str(date) + "', '" + str(values[0]) + "', '" + 
                str(values[1]) + "', '" + str(values[2]) + "', '" + str(values[3]) + "')")
            print "---------------------"
            print "| Data Logged       |"
            print "---------------------"
            print "Speed: " + values[0]
            print "Latitude: " + values[1]
            print "Longitude: " + values[2]
            print "Course: " + values[3]
        except _mysql.Error, e:
            print "Error %d: %s" % (e.args[0], e.args[1])
        finally:
            if db: 
                db.close()

class SendContentFactory(Factory):
    protocol = SendContent

reactor.listenTCP(81, SendContentFactory())
reactor.run()

This code is fairly straightforward.  When a connection is made, we will print to the console, and when data is received, we will attempt to issue a MySQL query that will insert the data into the database.

Conclusion

In the process of building this, I found many forum posts, articles, etc. of people having problems getting the sm5100b shield working.  Hopefully this post will provide assistance to others trying to work with this module.  Overall, I found the sm5100b to not be the most reliable chip in the world.  For mission critical apps, I would not recommend this set-up.  The code could be altered to more reliably handle network failures, but in this example it simply ignores them and moves on.

Hopefully you found this article helpful.  Feel free to post a comment and provide some feedback.

SOURCE CODE ON GITHUB

Filed under: Art and Nature Park, Technology

4 Responses to “Tracking the Island Resident with Arduino”