Before you upload the Open Sesame sketch, its a good idea to make sure you can use the Metro Mini with the FONA 800 module. Check out this tutorial on using the FONA, make sure to try out the FONAtest sketch with the updated pin defines:

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

Once you have that working nicely, and for example you have tested sending/receiving SMS messages, continue by downloading the Open Sesame sketch.

// SPDX-FileCopyrightText: 2019 Anne Barela for Adafruit Industries
//
// SPDX-License-Identifier: MIT

#include <SoftwareSerial.h>
#include "Adafruit_FONA.h"

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

#define MOTOR_PWM A1
#define MOTOR_1 A2
#define MOTOR_2 A3

#define LED 13

#define OPEN true
#define CLOSE false

#define BUSYWAIT 5000  // milliseconds

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

// or comment this out & use a hardware serial port like Serial1 (see below)
SoftwareSerial fonaSS = SoftwareSerial(FONA_TX, FONA_RX);

Adafruit_FONA fona = Adafruit_FONA(FONA_RST);

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

boolean fonainit(void) {
  Serial.println(F("Initializing....(May take 3 seconds)"));

  digitalWrite(LED, HIGH);
  delay(100);
  digitalWrite(LED, LOW);
  delay(100);
  digitalWrite(LED, HIGH);
  delay(100);
  digitalWrite(LED, LOW);
  delay(100);
   
  // make it slow so its easy to read!
  fonaSS.begin(4800); // if you're using software serial
  //Serial1.begin(4800); // if you're using hardware serial

  // See if the FONA is responding
  if (! fona.begin(fonaSS)) {           // can also try fona.begin(Serial1) 
    Serial.println(F("Couldn't find FONA"));
    return false;
  }
  Serial.println(F("FONA is OK"));
  return true;
  
}

void setup() {
  while (!Serial);  // useful for Leonardo/Micro, remove in production!

  // set LED output for debugging
  pinMode(LED, OUTPUT);
  
  // set up motor pins, but turn them off
  pinMode(MOTOR_1, OUTPUT);
  digitalWrite(MOTOR_1, LOW);
  pinMode(MOTOR_2, OUTPUT);
  digitalWrite(MOTOR_2, LOW);  
  pinMode(MOTOR_PWM, OUTPUT);
  digitalWrite(MOTOR_PWM, LOW);  
  
  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(2000);
  }
  
  // this is a 'busy wait' loop, we check if the interrupt
  // pin went low, and after BUSYWAIT milliseconds break out to check
  // manually for SMS's and connection status
  // This would be a good place to 'sleep'
  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's on SIM card!"));
  }
  
  if (smsnum == 0) return;

  // there's an SMS!
  uint8_t n = 1; 
  while (true) {
     uint16_t smslen;
     char sender[25];
     
     Serial.print(F("\n\rReading SMS #")); Serial.println(n);
     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, "open sesame") == 0) {
       // open the door!
       digitalWrite(LED, HIGH);
       moveMotor(OPEN, 800);
       digitalWrite(LED, LOW);
     }
     if (strcasecmp(replybuffer, "close cadabra") == 0) {
       // close the door!
       digitalWrite(LED, HIGH);
       moveMotor(CLOSE, 800);
       digitalWrite(LED, LOW);
     }
     
     delay(3000);
     break;
  }  
  fona.deleteSMS(n);
 
  delay(1000); 
}


void moveMotor(boolean direction, uint16_t t) {
  digitalWrite(MOTOR_PWM, HIGH);   // enable motor
  if (direction) {
    digitalWrite(MOTOR_1, HIGH);
    digitalWrite(MOTOR_2, LOW);
  } else {
    digitalWrite(MOTOR_2, HIGH);
    digitalWrite(MOTOR_1, LOW);  
  }
  delay(t);
  digitalWrite(MOTOR_1, LOW);
  digitalWrite(MOTOR_2, LOW);

  digitalWrite(MOTOR_PWM, LOW);    // disable motor
}

Before uploading code to your Metro, we recommend turning on FONA debugging mode where you can see all the messages sent/received, as it can make it easier to debug! Open up FONA.h in the sketchbook/libraries/Adafruit_FONA library and uncomment this line by removing the // and saving

//#define ADAFRUIT_FONA_DEBUG

Upload the code to your Metro Mini and open up the serial console to see the FONA brought up and connect to the network.

Once its on the network, it will constantly check for SMS messages. It does this in two ways, one it waits for a low pin message on FONA_RI and will instantly bail seeing a new SMS is received. It also manually queries the FONA every few seconds in case the RI pin didnt fire for some reason.

Try sending a text message, you will see a few seconds after sending, the FONA will see the message and print out the message and the sender's number or email address

It will then delete the message.

Finally, now that you know it can receive messages, try sending it "Open sesame" or "Close cadabra" to open or close the lock.

Understanding the code in detail

Even tho the code may be updated after this tutorial is written, here's the basic methods to the madness!

Setup and initialization

Here's where we include the required libraries (Software Serial for chatting to the FONA, and the FONA library)

We also define the pins from the Metro to the FONA and TB6612 motor driver. You can change these around to whatever you like. Pin 13 is the onboard LED so you'll want to keep that.

#include <SoftwareSerial.h>
#include "Adafruit_FONA.h"

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

#define MOTOR_PWM A1
#define MOTOR_1 A2
#define MOTOR_2 A3

#define LED 13

Other defines, we also define which way is 'open' and 'close' for the motor. If you want to have the motor go the opposite way, you can just swap these defines. We also have the amount to wait between querying the FONA for new SMS's.

#define OPEN true
#define CLOSE false

#define BUSYWAIT 5000  // milliseconds

Next up, we have the buffer for storing SMS messages (they can be long!) and creating the FONA object. If you're using a Micro or Leonardo you could use Serial1 instead of software serial.

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

// or comment this out & use a hardware serial port like Serial1 (see below)
SoftwareSerial fonaSS = SoftwareSerial(FONA_TX, FONA_RX);
Adafruit_FONA fona = Adafruit_FONA(FONA_RST);

We have a helper function for initializing the FONA, which we'll call whenever we want to reset the FONA. Normally we do this in setup() only but if the FONA 'hangs' or 'stalls' you can always call this for a hard-reset

boolean fonainit(void) {
  Serial.println(F("Initializing....(May take 3 seconds)"));

  digitalWrite(LED, HIGH);
  delay(100);
  digitalWrite(LED, LOW);
  delay(100);
  digitalWrite(LED, HIGH);
  delay(100);
  digitalWrite(LED, LOW);
  delay(100);
   
  // make it slow so its easy to read!
  fonaSS.begin(4800); // if you're using software serial
  //Serial1.begin(4800); // if you're using hardware serial

  // See if the FONA is responding
  if (! fona.begin(fonaSS)) {           // can also try fona.begin(Serial1) 
    Serial.println(F("Couldn't find FONA"));
    return false;
  }
  Serial.println(F("FONA is OK"));
  return true;
}

Setup() function

Setup begins by setting pin directions, turning off the motor and printing out the welcome message.

void setup() {
  while (!Serial);  // useful for Leonardo/Micro, remove in production!

  // set LED output for debugging
  pinMode(LED, OUTPUT);
  
  // set up motor pins, but turn them off
  pinMode(MOTOR_1, OUTPUT);
  digitalWrite(MOTOR_1, LOW);
  pinMode(MOTOR_2, OUTPUT);
  digitalWrite(MOTOR_2, LOW);  
  pinMode(MOTOR_PWM, OUTPUT);
  digitalWrite(MOTOR_PWM, LOW);  
  
  Serial.begin(115200);
  Serial.println(F("Open Sesame! An SMS-controlled door lock"));

It then calls our helper to boot up the FONA, check the SIM card is connected, and then tells the FONA to alert us when an SMS comes in by pulsing the RI pin low with the AT+CFGRI=1 command. (The RI pin is set to an input as well)

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"));

Loop()

In the loop we start out by blinking the LED (lets us visually tell the loop is running) and checking the FONA is up and running and connected to the cellular network

void loop() {
  digitalWrite(LED, HIGH);
  delay(100);
  digitalWrite(LED, LOW);
       
  while (fona.getNetworkStatus() != 1) {
    Serial.println("Waiting for cell connection");
    delay(2000);
  }

We then have the SMS waiting loop, we wait for either the RI pin to pulse low, or after 3 seconds, we manually check

  // this is a 'busy wait' loop, we check if the interrupt
  // pin went low, and after BUSYWAIT milliseconds break out to check
  // manually for SMS's and connection status
  // This would be a good place to 'sleep'
  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);
  }

then we check for how many SMS's are received! If we have 0 SMS's, we bail

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

If there is an SMS available, we go thru all the slots looking for it. SMS's are stored in slots but if you get a 2 SMS's and delete the first one, the second one doesn't "move up" a slot, so you have to check each one until you find the SMS you're looking for. Thats why we have the n++ and continue if we get an "[empty slot]"

  // there's an SMS!
  uint8_t n = 1; 
  while (true) {
     uint16_t smslen;
     char sender[25];
     
     Serial.print(F("\n\rReading SMS #")); Serial.println(n);
     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;
     }

OK now we  have the SMS message read and also know the sender. Print them out!

     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("*****"));

Now we can do something based on the SMS text message. We use strcasecmp for a case-insensitive comparison. You can also adapt this code to look for substrings or parse out values, we're keeping it simple! One command for opening, one for closing. Afterwards, we delay 3 seconds and restart the loop.

     if (strcasecmp(replybuffer, "open sesame") == 0) {
       // open the door!
       digitalWrite(LED, HIGH);
       moveMotor(OPEN, 800);
       digitalWrite(LED, LOW);
     }
     if (strcasecmp(replybuffer, "close cadabra") == 0) {
       // close the door!
       digitalWrite(LED, HIGH);
       moveMotor(CLOSE, 800);
       digitalWrite(LED, LOW);
     }
   delay(3000);
   break;
}  

Finally we delete the SMS, wait another second, and restart the loop

  fona.deleteSMS(n);
 
  delay(1000); 
}

moveMotor helper

At the very end we have our motor helper. This is a basic function that turns on the PWM pin to 'activate' the motor (its only active for the movement, then we turn off the motor to conserve power) and then turn it forward or backwards for t milliseconds.

void moveMotor(boolean direction, uint16_t t) {
  digitalWrite(MOTOR_PWM, HIGH);   // enable motor
  if (direction) {
    digitalWrite(MOTOR_1, HIGH);
    digitalWrite(MOTOR_2, LOW);
  } else {
    digitalWrite(MOTOR_2, HIGH);
    digitalWrite(MOTOR_1, LOW);  
  }
  delay(t);
  digitalWrite(MOTOR_1, LOW);
  digitalWrite(MOTOR_2, LOW);

  digitalWrite(MOTOR_PWM, LOW);    // disable motor
}

This guide was first published on Jun 26, 2015. It was last updated on Mar 27, 2024.

This page (Software) was last updated on Mar 27, 2024.

Text editor powered by tinymce.