SmartStarter, part 1

car_starter_square This is part 1 of a 3 part series on the SmartStarter project.

  • Part 1 covers hardware, setup, and software development of the Arduino + GSM functionality.
  • Part 2 covers hardware and software revisions and integration of the unit into the car
  • Part 3 covers setup and development of the GPS tracking functionality.

Recently, I've been toying around with the idea of building more complicated IoT devices. Inspired by recent visits to HackManhattan and FatCat Fab Lab, I decided to pull the trigger on an Adafruit GSM shield. Around the same time, I was looking for a way to expand the range of the remote starter in my car--I often end up parked 3-4 blocks away from my apartment, which renders the remote starter in my car useless. Nothing like walking to your car in 20 degree weather and spending another ten minutes in the car freezing your ass off becuase the range on your remote starter is garbage.

And so, out of these two things, the SmartStarter project was born. By combining an Arduino, GSM shield, and a spare car remote, I can now remotely start and lock/unlock my car anywhere I have cell service.

At a later date, I plan to capture GPS coordinates via the onboard GPS chip at 30 minute intervals and push them to a MySQL database on my AWS server.

 

Full breakdown and code below the break.

Project Goals:

  1. Remotely start or lock/unlock car via text (part 1)
  2. Power device via car always on 12V rail (part 2)
  3. Obtain GPS signal via OEM antenna and push coordinates into database at 30 min intervals (part 3)
  4. Write front end Google Maps overlay for GPS coordinates (part 3)

Materials Used

  • Arduino Uno
  • Adafruit FONA 3g GSM shield w/ 3g SIM
  • GSM antenna
  • GPS antenna -- I am splicing into the factory SiriusXM antenna in my car
  • Li-Po battery
  • Spare car remote

Hardware

Any microprocessor programmable via the Arduino IDE will work, in this case I went with an official Arduino Uno. When looking at GSM shields, I was only interested in shields with 3g compatible chipsets. I take my car out to some remote areas when I go climbing and backpacking, so I wanted to guarantee the most amount of coverage possible. I also had some concern with sunsetting 2g coverage rendering my device useless in the near future.

I chose the Adafruit 3g FONA Shield for a few reasons: it's cost effective, it has an onboard GPS chip, the FONA library is well supported in the maker scene, and Adafruit is an awesome NYC based company.

Setup

Arduino + FONA + Antennas + Battery

adafruit_fona

One of the awesome things about Adafruit is that they provide in-depth setup documentation for most of their products. In this case, they have a overview that walks you through basic setup of the FONA 3g GSM shield. For the purposes of this blog post, I'm going to skip those steps, since Adafruit already covers them so well.

Car Remote

I disassembled the car remote to take a look at the buttons used for each item: lock, unlock, and remote start.

[IMG]

I then used a multimeter in continuity setting to get an understanding of how the press button switches worked. I learned that the buttons had two negative (ground) and two positive leads, connected to a 3v coin battery on the other side.  I realized that I could ground the negative side of the switches and use the Arduino to switch between I/O pin mode to effectively tri-state the switches, in order to get an electronic button press.

I soldered connections to each of the necessary pads:

img_1339_text

I then connected the positive leads to the Arduino IO pins and the negative leads to ground on the breadboard:

img_1341_text

Button Press Test

To test my connections, I wrote a basic Arduino program that sent a low signal to pin 5 in output mode, waited a second, and changed the pin to input mode--"pressing" the lock button on the remote.

void setup() {
   pinMode(5, OUTPUT);
}

void loop() {

  int lock(){
    digitalWrite(5, LOW);
    delay(1000);
    pinMode(5, INPUT);
  }  

}

Hooray, it works!

[GIF]

 

Code

At this point, I was ready to write an Arduino sketch. I needed code that would check the SIM card for an SMS, parse the SMS text, and run a specific function based on the SMS text.

Fortunately, Adafruit had a project guide for an SMS controlled door lock that I was able to use as the basis for my SMS read-in code. It already had the bones for the SMS read-in functionality, I just needed to build my specific functionality on top.

Note: all credit for the open-sesame code goes to LadyAda of Adafruit; her full code for that project is on the Adafruit Github page.

My Code

Using the open-sesame code as a base, I wrote a series of functions (doors, remote, and sms) that interact with the key-fob pins depending on the text of the SMS message. After attempting to lock/unlock or remote start the car, the device will send a response SMS back to the sender, indicating that it has attempted to "press" that button.

Success!

[GIF]

Full code on Github [fac_icon icon="github"]

#include 
#include "Adafruit_FONA.h"

#define FONA_RX 2
#define FONA_TX 3
#define FONA_RST 4
#define FONA_RI 5

#define LOCK_PIN 6
#define UNLOCK_PIN 7
#define REMOTE_PIN 8

#define LED 13

#define LOCK true
#define UNLOCK false

#define BUSYWAIT 5000  // milliseconds

// stores senderient SMS #
char sender[25];

// this is a large buffer for replies
char replybuffer[255];

SoftwareSerial fonaSS = SoftwareSerial(FONA_TX, FONA_RX);

Adafruit_FONA_3G fona = Adafruit_FONA_3G(FONA_RST);

uint8_t readline(char *buff, uint8_t maxbuff, uint16_t timeout = 0);

boolean fonainit(void) {
  while (!Serial);

  fonaSS.begin(4800);
  Serial.println(F("Initializing....(May take 3 seconds)"));

  if (! fona.begin(fonaSS)) {
    Serial.println(F("Couldn't find FONA"));
    return false;
  }
  Serial.println(F("FONA is OK"));
  return true;

}

void setup() {

  // set LED output for debugging
  pinMode(LED, OUTPUT);

  // set key fob pins so they are off
  pinMode(LOCK_PIN, INPUT);
  pinMode(UNLOCK_PIN, INPUT);  
  pinMode(REMOTE_PIN, INPUT);

  Serial.begin(115200);
  Serial.println(F("FONA basic test"));

  while (! fonainit()) {
    delay(5000);
  }

  // Print SIM card IMEI number.
  char imei[15] = {0}; // MUST use a 16 character buffer for IMEI!
  uint8_t imeiLen = fona.getIMEI(imei);
  if (imeiLen > 0) {
    Serial.print("SIM card IMEI: "); Serial.println(imei);
  }

  pinMode(FONA_RI, INPUT);
  digitalWrite(FONA_RI, HIGH); // turn on pullup on RI
  // turn on RI pin change on incoming SMS!
  fona.sendCheckReply(F("AT+CFGRI=1"), F("OK"));
}

int8_t lastsmsnum = 0;

void loop() {
   digitalWrite(LED, HIGH);
   delay(100);
   digitalWrite(LED, LOW);

  while (fona.getNetworkStatus() != 1) {
    Serial.println("Waiting for cell connection");
    delay(6000);
  }

  // Check if the interrupt pin went low, 
  // and after BUSYWAIT milliseconds break out to check
  // manually for SMS' and connection status
  for (uint16_t i=0; i<BUSYWAIT; i++) {
     if (! digitalRead(FONA_RI)) {
        // RI pin went low, SMS received?
        Serial.println(F("RI went low"));
        break;
     } 
     delay(1);
  }

  int8_t smsnum = fona.getNumSMS();
  if (smsnum < 0) {
    Serial.println(F("Could not read # SMS"));
    return;
  } else {
    Serial.print(smsnum); Serial.println(F(" SMS on SIM card!"));
  }

  if (smsnum == 0) return;

  // there's an SMS!
  uint8_t n = 0; 
  while (true) {
     uint16_t smslen;
     //char sender[25];

     uint8_t len = fona.readSMS(n, replybuffer, 250, &smslen); // pass in buffer and max len!
     // if the length is zero, its a special case where the index number is higher
     // so increase the max we'll look at!
     if (len == 0) {
        Serial.println(F("[empty slot]"));
        n++;
        continue;
     }
     if (! fona.getSMSSender(n, sender, sizeof(sender))) {
       // failed to get the sender?
       sender[0] = 0;
     }

     Serial.print(F("***** SMS #")); Serial.print(n);
     Serial.print(" ("); Serial.print(len); Serial.println(F(") bytes *****"));
     Serial.println(replybuffer);
     Serial.print(F("From: ")); Serial.println(sender);
     Serial.println(F("*****"));

     if (strcasecmp(replybuffer, "lock") == 0) {
       // lock the doors
       digitalWrite(LED, HIGH);
       doors(LOCK);
       digitalWrite(LED, LOW);
     }
     if (strcasecmp(replybuffer, "unlock") == 0) {
       // unlock the doors
       digitalWrite(LED, HIGH);
       doors(UNLOCK);
       digitalWrite(LED, LOW);
     }
     if (strcasecmp(replybuffer, "remote") == 0) {
       // start/stop the car
       digitalWrite(LED, HIGH);
       remote();
       digitalWrite(LED, LOW);
     }

     delay(3000);
     break;
  }  
  fona.deleteSMS(n);

  delay(1000); 
}

void doors(boolean locked){
  if (locked) {
    pinMode(LOCK_PIN, OUTPUT);
    digitalWrite(LOCK_PIN, LOW);
    delay(1000);
    pinMode(LOCK_PIN, INPUT);

    Serial.println("Attemped to lock doors");
    Serial.print("Attempting to send SMS to: "); Serial.println(sender);
    sms(sender, "Door lock signal sent!");
  } else {
    pinMode(UNLOCK_PIN, OUTPUT);
    digitalWrite(UNLOCK_PIN, LOW);
    delay(1000);
    pinMode(UNLOCK_PIN, INPUT);

    Serial.println("Attemped to unlock doors");
    Serial.print("Attempting to send SMS to: "); Serial.println(sender);
    sms(sender, "Door unlock signal sent!");
  }
}

void remote(){
  pinMode(REMOTE_PIN, OUTPUT);
  digitalWrite(REMOTE_PIN, LOW);
  delay(1000);
  pinMode(REMOTE_PIN, INPUT);

  Serial.println("Attemped to remote start/stop car");
  Serial.print("Attempting to send SMS to: "); Serial.println(sender);
  sms(sender, "Remote start/stop signal sent!");
}

void sms(char* sender, char* message){
  flushSerial();
  if (!fona.sendSMS(sender, message)) {
    Serial.println(F("Failed"));
  } else {
    Serial.print(F("Sent SMS response to: ")); Serial.println(sender);
  }
}

void flushSerial() {
  while (Serial.available())
    Serial.read();
}