Project tutorial
Unicorn Finder (Kid Tracker for Techie Parents)

Unicorn Finder (Kid Tracker for Techie Parents) © MIT

The Arduino Uno controls a 16 Neopixel unicorn horn. This includes a GPS module broadcasting through a cellular GSM shield.

  • 5,824 views
  • 2 comments
  • 15 respects

Components and supplies

Necessary tools and machines

09507 01
Soldering iron (generic)
3drag
3D Printer (generic)

Apps and online services

About this project

Intro

During Halloween my girls were unicorns (and my boys, ninjas). A few weeks before the holiday my oldest daughter jumped online to Google "rainbow unicorn horn". She found a killer tutorial from Adafruit outlining everything you needed to get a rainbow unicorn horn (check it out). I wanted to expand the project by adding GPS tracking sent to the cloud through Hologram's cellular network for IoT.

Why add GPS? As my kids get older, they want to try things without adult supervision. As a parent, that is one of the scariest thoughts. Being able to track my kids helps my wife and I transition into less-crazy parents.

What follows is the not-so-easy story on how we accomplished our Unicorn Finder.

3D-Printed NeoPixel Horn

This is the easy part since Adafruit did most of the heavy lifting. First we went to mHUB, a maker space here in Chicago, to 3D print the Adafruit unicorn horn on Thingiverse.

Next, we ordered the Adafruit Neopixel sticks and wired them up as per the tutorial. Once back in the 'burbs, we hit up Hobby Lobby for headband material, followed by mom sowing it all together.

The Neopixel wiring and unicorn horn were the only parts taken from the Adafruit tutorial. We decided to use an Arduino UNO as the base to build the rest of the project.

Arduino UNO + GSM Shield

A couple weeks before this project, the good folks at Arduino sent me a few GSM Shields to try on Hologram's cellular network. I decided to grab some UNOs along with those GSM Shields and go at it. The integration worked great and you can see the final code here. Below is a quick example using TCP.

You can to get your hands on an international Hologram SIM from their store.

Note: When using a Hologram SIM, you can send data directly to any cloud provider (no vendor lock) using any protocol that provider supports. In this example, I chose to send data to Hologram's data router because it is quick, secure and conserves data.

// Typical Arduino GSM globals
#include <GSM.h> 
#define PINNUMBER "" 
#define GPRS_APN "hologram" 
#define GPRS_LOGIN "" 
#define GPRS_PASSWORD "" 
GSMClient client; 
GPRS gprs; 
GSM gsmAccess; // pass (true) for debugger 
// Additional globals for Hologram TCP
char server[] = "23.253.146.203"; 
int port = 9999;
const char HOLOGRAMID[] = "xxx"; //replace w/your SIM id
const char HOLOGRAMKEY[] = "xxx"; //replace w/your SIM key
void setup() { 
 Serial.println(F("Initializing Arduino GSM...")); 
 boolean notConnected = true; 
 while(notConnected){ 
   if(
    (gsmAccess.begin(PINNUMBER)==GSM_READY) & 
    (gprs.attachGPRS(GPRS_APN, GPRS_LOGIN, GPRS_PASSWORD)==GPRS_READY)) { 
     notConnected = false; 
     Serial.println(F("Cellular Network Connected, sending message...")); 
     modemCloudWrite("Sent from Arduino GSM to Hologram Data Router"); 
   } else { 
     Serial.println(F("Not Connected to Cellular Network")); 
     delay(1000); 
   } 
 } 
} 
void loop() { }
// Make a TCP write 
bool modemCloudWrite(char * msg) { 
 // if you get a connection send message then disconnect 
 if (client.connect(server, port)) { 
   client.beginWrite(); 
   client.print(F("A")); 
   client.print((char*)HOLOGRAMID); 
   client.print((char*)HOLOGRAMKEY); 
   client.print(F(" ")); 
   client.print(F("S")); 
   client.print(msg); 
   client.println(F("\n\n")); 
   client.endWrite(); 
   client.stop();  
 } 
} 

Adding Adafruit Ultimate GPS Breakout

Here is where things got hairy. I ran into a conflict between Arduino's GSM library and Adafruit's GPS library. The issue resulted from the GSM library's implementation of software serial. Eventually after an embarrassing amount of time, I found this forum post providing an alternative GPS library using the wonderful AltSoftSerial library. I included the alt GPS zip file in this project's code section.

Unfortunately, that was not the only issue I ran into. My Arduino UNO was running out of dynamic memory (RAM). You'll see in my sketch where I check and print available RAM. I kept those serial prints just in case you modify it. If things start acting weird, it is likely because you have no RAM left.

Eventually, after some code reorganization and wrapping all strings in F(), I was able to free up just enough memory (around 85% used). Sigh of relief.

Cloud Data Routing to Losant Dashboard

We have rainbow lights; we have GPS location; we have cellular connectivity. Now we need a place to easily store, manipulate and display our IoT data. There are a number of great providers who offer these services. For this project I chose Losant.com - go ahead and create a free account.

Let's setup a connection from Hologram's data router to Losant's platform. We first will create a new Losant application. This application will consume data through a webhook, set the state of a virtual device then display our location on a dashboard map. Last, we create a new Hologram data route to send all new GPS coordinates to the Losant webhook.

1. Losant: Create an application and a webhook.

2. Losant: Create a standalone device with a GPS String attribute named gps.

3a. Losant: Create a new workflow then add/connect a webhook trigger to a device state output.

3b. Losant: The Webhook node will automatically map to the only available webhook you created in step 1. In the Device State node will need both the device and state values configured like the image below.

4. Losant: Create a Dashboard and add a GPS history block. You should have only one option in all the drop down inputs. You should now have an empty block waiting for it's first data.

5. Hologram: You should have a Hologram international SIM and a Hologram account. Create a new data route, select Advanced Webhook Builder and copy the setting from the image below (your Losant/Structure webhook ID will be different).

Conclusion

You should now see new coordinates update the Losant dashboard map. Since we are using the integrated GPS antenna, it may take up to 30 minutes for the satellites to connect. Enjoy!

Code

Arduino SketchArduino
// ADAFRUIT NEOPIXEL GLOBALS ------------------------------------------
#include <Adafruit_NeoPixel.h>
#define PIN 6
Adafruit_NeoPixel strip = Adafruit_NeoPixel(8, PIN, NEO_GRB + NEO_KHZ800);

// ADAFRUIT ULTIMATE GPS GLOBALS -------------------------------------
#include <Adafruit_GPS_Alt.h>
#include <AltSoftSerial.h>
AltSoftSerial mySerial(8, 7);
Adafruit_GPS_Alt GPS(&mySerial);
#define GPSECHO false
boolean usingInterrupt = false;
void useInterrupt(boolean);
uint32_t timer = millis();
String lastCoor = "\"coords\": [0,0]";

// ARDUINO GSM GLOBALS ------------------------------------------------
#include <GSM.h>
#define PINNUMBER ""
#define GPRS_APN "hologram"
#define GPRS_LOGIN ""
#define GPRS_PASSWORD ""
GSMClient client;
GPRS gprs;
GSM gsmAccess; // pass (true) for debugger

// HOLOGRAM CLOUD GLOBALS ---------------------------------------------
char server[] = "23.253.146.203"; // the base URL or IP
int port = 9999; // 80 for HTTP, 443 for HTTPS, 999 for TCP
const char HOLOGRAMID[] = "xxxx"; //replace with 4 digit shared id from device details on dashboard
const char HOLOGRAMKEY[] = "xxxx"; //replace with 4 digit shared key from device details on dashboard

void setup() {
  Serial.begin(115200);
  
  // ADAFRUIT NEOPIXEL SETUP ------------------------------------------  
  Serial.println(F("Initializing Adafruit NeoPixels..."));
  strip.begin();
  strip.setBrightness(100); //adjust brightness here
  strip.show(); // Initialize all pixels to 'off'

  // ADAFRUIT GPS SETUP -----------------------------------------------
  Serial.println(F("Initializing Adafruit GPS..."));
  GPS.begin(9600);
  // Setup GPS to report the data we need
  GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);
  GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ);
  GPS.sendCommand(PGCMD_ANTENNA);
  useInterrupt(true);
  delay(1000);
  mySerial.println(F(PMTK_Q_RELEASE));

  // ARDUINO GSM SETUP ------------------------------------------------
  Serial.println(F("Initializing Arduino GSM..."));
  boolean notConnected = true;
  while(notConnected){
    if((gsmAccess.begin(PINNUMBER)==GSM_READY) & (gprs.attachGPRS(GPRS_APN, GPRS_LOGIN, GPRS_PASSWORD)==GPRS_READY)) {
      notConnected = false;
      Serial.println(F("Cellular Network Connected"));
    } else {
      Serial.println(F("Not Connected to Cellular Network"));
      delay(1000);
    }
  }

  Serial.print(F("Arduino UNO Free RAM: ")); Serial.println(freeRam());
  delay(1000);
}

 
void loop() {
  // ADAFRUIT NEOPIXEL LOOP ------------------------------------------ 
  Serial.println(F("Rainbow Cycling...")); 
  rainbowCycle(10);

  // ADAFRUIT GPS LOOP -----------------------------------------------
  if (GPS.newNMEAreceived()) {
    if (!GPS.parse(GPS.lastNMEA()))
      return;
  }
  
  String message;
  String newCoor = lastCoor;
  Serial.print(F("GPS Fixed: ")); Serial.println((int)GPS.fix);
  Serial.print(F("GPS Satellites: ")); Serial.println((int)GPS.satellites);
  if (GPS.fix + GPS.satellites > 1) {
    newCoor = "";
    newCoor.concat(F("\"gps\":"));
    newCoor.concat(GPS.latitude);
    newCoor.concat(F(", "));
    newCoor.concat(GPS.longitude);
    
    message.concat(F("{"));
    message.concat(newCoor);
    message.concat(F("}")); 

    Serial.print(F("Arduino UNO Free RAM: ")); Serial.println(freeRam());
  }

  // ARDUINO GSM / HOLOGRAM CLOUD LOOP -------------------------------
  Serial.print(F("Last Coordinates: ")); Serial.println(lastCoor);
  Serial.print(F("New Coordinates: ")); Serial.println(newCoor);
  if (lastCoor != newCoor) {
    char msgbuf[38];
    Serial.print(F("message: ")); Serial.println(message);
    message.toCharArray(msgbuf, 38);
    modemCloudWrite(msgbuf);
    lastCoor = newCoor;
  }

  Serial.println(F("Loop Finished"));
  Serial.println();
}


int freeRam () 
{
  extern int __heap_start, *__brkval; 
  int v; 
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}


// ADAFRUIT NEOPIXEL FUNTIONS ------------------------------------------
 
// Slightly different, this makes the rainbow equally distributed throughout
void rainbowCycle(uint8_t wait) {
  uint16_t i, j;
  for(j=0; j<256*10; j++) { // 10 cycles of all colors on wheel
    for(i=0; i< strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));
    }
    strip.show();
    delay(wait);
  }
}
 
// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos) {
  if(WheelPos < 85) {
   return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  } else if(WheelPos < 170) {
   WheelPos -= 85;
   return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  } else {
   WheelPos -= 170;
   return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
}

// ARDUINO GSM / HOLOGRAM CLOUD FUNCTIONS -----------------------------

// Make a TCP write
bool modemCloudWrite(char * msg) {
  // if you get a connection send message then disconnect
  if (client.connect(server, port)) {
    Serial.println(F("Connected to Hologram"));
    client.beginWrite();
    client.print(F("A"));
    client.print((char*)HOLOGRAMID);
    client.print((char*)HOLOGRAMKEY);
    client.print(F(" "));
    client.print(F("S"));
    client.print(msg);
    client.println(F("\n\n"));
    client.endWrite();
  
    Serial.print(F("Message sent: "));
    Serial.println(msg);
    client.stop();
    Serial.println(F("Disconnecting Cellular"));
    Serial.println();
  }
}


// ADAFRUIT GPS FUNCTIONS -----------------------------
// Interrupt is called once a millisecond, looks for any new GPS data, and stores it
SIGNAL(TIMER0_COMPA_vect) {
  char c = GPS.read();
  // if you want to debug, this is a good time to do it!
#ifdef UDR0
  if (GPSECHO)
    if (c) UDR0 = c;  
    // writing direct to UDR0 is much much faster than Serial.print 
    // but only one character can be written at a time. 
#endif
}

void useInterrupt(boolean v) {
  if (v) {
    // Timer0 is already used for millis() - we'll just interrupt somewhere
    // in the middle and call the "Compare A" function above
    OCR0A = 0xAF;
    TIMSK0 |= _BV(OCIE0A);
    usingInterrupt = true;
  } else {
    // do not call the interrupt function COMPA anymore
    TIMSK0 &= ~_BV(OCIE0A);
    usingInterrupt = false;
  }
}
Adafruit_GPS_Alt.zipArduino
Use this GPS library instead of the one found in the library manager.
No preview (download only).

Custom parts and enclosures

3D Unicorn Horn
Available at http://www.thingiverse.com/thing:956359

Schematics

Unicorn Finder
Yx35qs1zfvxy5bvgfwhs
Fritzing Schematic
e4cXDfO9P2U2OPiV4Hnv.fzz

Comments

Similar projects you might like

Aggro-Crag-in-Real-Life-Button and Leader Board

Project tutorial by Sheena Martin and Derrick Wolbert

  • 1,971 views
  • 0 comments
  • 7 respects

Hiking Tracker

Project tutorial by Shahariar

  • 8,343 views
  • 16 comments
  • 51 respects

Carfox: A Device to Find Them All

Project tutorial by Luis Roda Sánchez

  • 6,654 views
  • 2 comments
  • 28 respects

Electrocardiogram Using TouchDesigner & Arduino

Project tutorial by HomeMadeGarbage

  • 8,914 views
  • 2 comments
  • 27 respects

Safe City: A Device to Keep You Safe

Project tutorial by Patel Darshil

  • 7,149 views
  • 0 comments
  • 24 respects
Add projectSign up / Login