Track Your Treats - FONA808 & Adafruit IO

For the FONA808 & Adafruit IO version of the project load the Track_Your_Treats_FONA808 sketch, click Download: Project Zip in the code window below.

// Track Your Treats - FONA808 Shield & Adafruit IO Halloween Candy Route Tracker
// Author: Tony DiCola
//
// See the guide at:
// https://learn.adafruit.com/track-your-treats-halloween-candy-gps-tracker/overview
//
// Released under a MIT license:
// https://opensource.org/licenses/MIT
#include <SoftwareSerial.h>
#include "Adafruit_SleepyDog.h"
#include "Adafruit_FONA.h"
#include "Adafruit_MQTT.h"
#include "Adafruit_MQTT_FONA.h"


// Configuration (you need to change at least the APN and AIO username & key values):

#define LED_PIN              6   // Pin connected to an LED that flashes the status of the project.

#define BUTTON_PIN           5   // Pin connected to the button.

#define LOGGING_PERIOD_SEC   15  // Seconds to wait between logging GPS locations.

#define FONA_RX              2   // FONA serial RX pin (pin 2 for shield).

#define FONA_TX              3   // FONA serial TX pin (pin 3 for shield).

#define FONA_RST             4   // FONA reset pin (pin 4 for shield)

#define FONA_APN             ""  // APN used by cell data service (leave blank if unused).
                                 // Contact your cell service provider to get this value and
                                 // any username or password required too (see below).

#define FONA_USERNAME        ""  // Username used by cell data service (leave blank if unused).

#define FONA_PASSWORD        ""  // Password used by cell data service (leave blank if unused).

#define AIO_SERVER           "io.adafruit.com"  // Adafruit IO server name.

#define AIO_SERVERPORT       1883  // Adafruit IO port.

#define AIO_USERNAME         ""  // Adafruit IO username (see http://accounts.adafruit.com/).

#define AIO_KEY              ""  // Adafruit IO key (see settings page at: https://io.adafruit.com/settings).

#define PATH_FEED_NAME       "treat-path"  // Name of the AIO feed to log regular location updates.

#define GOOD_CANDY_FEED_NAME "treat-good-candy" // Name of the AIO feed to log good candy locations.

#define MAX_TX_FAILURES      3  // Maximum number of publish failures in a row before resetting the whole sketch.


// Global state (you don't need to change this):
SoftwareSerial fonaSS = SoftwareSerial(FONA_TX, FONA_RX);     // FONA software serial connection.
Adafruit_FONA fona = Adafruit_FONA(FONA_RST);                 // FONA library connection.
const char MQTT_SERVER[] PROGMEM    = AIO_SERVER;             // MQTT server, client, username, password.
const char MQTT_CLIENTID[] PROGMEM  = __TIME__ AIO_USERNAME;  // (stored in flash memory)
const char MQTT_USERNAME[] PROGMEM  = AIO_USERNAME;
const char MQTT_PASSWORD[] PROGMEM  = AIO_KEY;
Adafruit_MQTT_FONA mqtt(&fona, MQTT_SERVER, AIO_SERVERPORT,   // MQTT connection.
                        MQTT_CLIENTID, MQTT_USERNAME,
                        MQTT_PASSWORD);
uint8_t txFailures = 0;                                       // Count of how many publish failures have occured in a row.
uint32_t logCounter = 0;                                      // Counter until next location log is recorded.

// Publishing feed setup (you don't need to change this):
// Note that the path ends in '/csv', this means a comma separated set of values
// can be pushed to the feed, including location data like lat, long, altitude.
Adafruit_MQTT_Publish path = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/treat-path/csv");
Adafruit_MQTT_Publish goodCandy = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/treat-good-candy/csv");

// Halt function called when an error occurs.  Will print an error and stop execution while
// doing a fast blink of the LED.  If the watchdog is enabled it will reset after 8 seconds.
void halt(const __FlashStringHelper *error) {
  Serial.println(error);
  while (1) {
    digitalWrite(LED_PIN, LOW);
    delay(100);
    digitalWrite(LED_PIN, HIGH);
    delay(100);
  }
}

// Timer interrupt called every millisecond to keep track of when the location should be logged.
SIGNAL(TIMER0_COMPA_vect) {
  // Decrease the count since last location log.
  if (logCounter > 0) {
    logCounter--;
  }
}

// Serialize the lat, long, altitude to a CSV string that can be published to the specified feed.
void logLocation(float latitude, float longitude, float altitude, Adafruit_MQTT_Publish& publishFeed) {
  // Initialize a string buffer to hold the data that will be published.
  char sendBuffer[120];
  memset(sendBuffer, 0, sizeof(sendBuffer));
  int index = 0;

  // Start with '0,' to set the feed value.  The value isn't really used so 0 is used as a placeholder.
  sendBuffer[index++] = '0';
  sendBuffer[index++] = ',';

  // Now set latitude, longitude, altitude separated by commas.
  dtostrf(latitude, 2, 6, &sendBuffer[index]);
  index += strlen(&sendBuffer[index]);
  sendBuffer[index++] = ',';
  dtostrf(longitude, 3, 6, &sendBuffer[index]);
  index += strlen(&sendBuffer[index]);
  sendBuffer[index++] = ',';
  dtostrf(altitude, 2, 6, &sendBuffer[index]);

  // Finally publish the string to the feed.
  Serial.print(F("Publishing: "));
  Serial.println(sendBuffer);
  if (!publishFeed.publish(sendBuffer)) {
    Serial.println(F("Publish failed!"));
    txFailures++;
  }
  else {
    Serial.println(F("Publish succeeded!"));
    txFailures = 0;
  }
}

void setup() {
  // Initialize serial output.
  Serial.begin(115200);
  Serial.println(F("Track your Treats - FONA808 & Adafruit IO"));

  // Initialize LED and button.
  pinMode(LED_PIN, OUTPUT);
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  digitalWrite(LED_PIN, LOW);

  // Initialize the FONA module
  Serial.println(F("Initializing FONA....(may take 10 seconds)"));
  fonaSS.begin(4800);
  if (!fona.begin(fonaSS)) {
    halt(F("Couldn't find FONA"));
  }
  fonaSS.println("AT+CMEE=2");
  Serial.println(F("FONA is OK"));

  // Use the watchdog to simplify retry logic and make things more robust.
  // Enable this after FONA is intialized because FONA init takes about 8-9 seconds.
  Watchdog.enable(8000);
  Watchdog.reset();

  // Wait for FONA to connect to cell network (up to 8 seconds, then watchdog reset).
  Serial.println(F("Checking for network..."));
  while (fona.getNetworkStatus() != 1) {
   delay(500);
  }

  // Enable GPS.
  fona.enableGPS(true);

  // Start the GPRS data connection.
  Watchdog.reset();
  fona.setGPRSNetworkSettings(F(FONA_APN), F(FONA_USERNAME), F(FONA_PASSWORD));
  delay(2000);
  Watchdog.reset();
  Serial.println(F("Disabling GPRS"));
  fona.enableGPRS(false);
  delay(2000);
  Watchdog.reset();
  Serial.println(F("Enabling GPRS"));
  if (!fona.enableGPRS(true)) {
    halt(F("Failed to turn GPRS on, resetting..."));
  }
  Serial.println(F("Connected to Cellular!"));

  // Wait a little bit to stabilize the connection.
  Watchdog.reset();
  delay(3000);

  // Now make the MQTT connection.
  int8_t ret = mqtt.connect();
  if (ret != 0) {
    Serial.println(mqtt.connectErrorString(ret));
    halt(F("MQTT connection failed, resetting..."));
  }
  Serial.println(F("MQTT Connected!"));

  // Configure timer0 compare interrupt to run and decrease the log counter every millisecond.
  OCR0A = 0xAF;
  TIMSK0 |= _BV(OCIE0A);
}

void loop() {
  // Watchdog reset at start of loop--make sure everything below takes less than 8 seconds in normal operation!
  Watchdog.reset();

  // Reset everything if disconnected or too many transmit failures occured in a row.
  if (!fona.TCPconnected() || (txFailures >= MAX_TX_FAILURES)) {
    halt(F("Connection lost, resetting..."));
  }

  // Grab a GPS reading.
  float latitude, longitude, speed_kph, heading, altitude;
  bool gpsFix = fona.getGPS(&latitude, &longitude, &speed_kph, &heading, &altitude);

  // Light the LED solid if there's a GPS fix, otherwise flash it on and off once a second.
  if (gpsFix) {
    digitalWrite(LED_PIN, HIGH);
  }
  else {
    // No fix, blink the LED once a second and stop further processing.
    digitalWrite(LED_PIN, (millis()/1000) % 2);
    return;
  }

  // Check if the button is pressed.
  if (digitalRead(BUTTON_PIN) == LOW) {
    // Pause a bit to debounce.
    delay(100);
    if (digitalRead(BUTTON_PIN) == LOW) {
      // Button pressed! Log the current location to the good candy feed.
      logLocation(latitude, longitude, altitude, goodCandy);
      // Then flash the light 5 times to signal the location was recorded.
      for (int i=0; i<5; ++i) {
        digitalWrite(LED_PIN, HIGH);
        delay(250);
        digitalWrite(LED_PIN, LOW);
        delay(250);
      }
    }
  }

  // Periodically log the location.
  if (logCounter == 0) {
    // Log the current location to the path feed, then reset the counter.
    logLocation(latitude, longitude, altitude, path);
    logCounter = LOGGING_PERIOD_SEC*1000;
  }
}

Load the sketch in the Arduino IDE, you should see something like the following:

At the top of the sketch are #define values which configure how the code will behave.  There are a few values you will need to change:

  • FONA_APN - Set this to the APN that your cellular network provider requires for accessing their data network.  With a Ting SIM card in the US the value "wholesale" is what you want, but for other providers you might need to contact their support to get the value.
  • FONA_USERNAME & FONA_PASSWORD - If your cellular network provider requires a username and password to access their data network then put those values here.  If they aren't required then just leave the default blank values.
  • AIO_USERNAME - This needs to be set to your Adafruit account username, which you can find at https://accounts.adafruit.com/ under the Username field.
  • AIO_KEY - This needs to be set to your Adafruit IO key, which you can find from the Adafruit IO settings page for your account at https://io.adafruit.com/settings by clicking View AIO Keys.

The other configuration values don't necessarily need to be changed, but it's a good idea to skim them to see what they do.

To load the sketch on the hardware first make sure the hardware is ready.  You must have a cellular antenna and GPS antenna connected to the FONA808 shield.  In addition you need a LiPoly battery connected to the FONA808 shield so it can power the cellular modem.

Then connect the Arduino to your computer using a USB cable.  In the Tools -> Board menu of the Arduino IDE make sure the Arduino Uno board is selected.  In the Tools -> Port menu make sure the right serial port is selected for the Arduino (it should say Arduino Uno next to it).  Click the Sketch -> Upload command to upload the sketch to the Arduino.

Once the sketch uploads open the Tools -> Serial Monitor to see serial output from the hardware.  Make sure the baud is set to 115200 in the lower right corner.  You should see serial output similar to the following:

Track your Treats - FONA808 & Adafruit IO
Initializing FONA....(may take 10 seconds)
FONA is OK
Checking for network...
Disabling GPRS
Enabling GPRS
Connected to Cellular!
MQTT Connected!

While the hardware is waiting to get a GPS location fix you should see the button LED blink every second.  Once a location fix is made the LED will stay solidly lit.  You might need to move the hardware outside where it has a clear view of the sky in order for it to get a GPS fix (it can even take up to 30 minutes in some cases!).

If something goes wrong the button LED will blink quickly (around 5 times a second).  Look in the serial monitor to see if an error message is displayed, like for example a cellular data connection can't be made (check your SIM card and cell account has data access), or if the MQTT/Adafruit IO connect fails (check your AIO username and key values).

Once the hardware has a GPS fix it will start periodically logging location to Adafruit IO.  Every 15 seconds the location will be logged (but you can change this with the #defines at the top of the sketch).

If you press the button the current location will be logged and noted to have good candy, then the LED will blink 5 times to acknowledge the button press.  It takes a few moments for the location to be logged after pressing the button so be sure to hold it down until it starts blinking 5 times.  Also note you can't log locations with button if the GPS doesn't have a fix (i.e. the button is blinking every second).  Wait until the GPS has a fix and the button stays lit solidly before pressing the button.

Once the sketch is running walk around and press the button a few times to get sample data, then jump to the Adafruit IO page to learn how to view the tracker data on Adafruit IO.

This guide was first published on Oct 12, 2015. It was last updated on Oct 12, 2015.

This page (FONA) was last updated on Apr 18, 2021.

Text editor powered by tinymce.