Dynamic 3rd party SIM card APN Credentials from SIM card IMSI


#1

I wrote a test program that should serve as a good building block for anyone who is looking to use 3rd party sim cards and particle sim cards, and have many potential networks that they will be connecting to.

This program programatically parses the IMSI on the SIM card that is currently inserted into the Electron, determines the MCC and MNC based on the IMSI and the length of the MNC portion of the IMSI.

It then determines which cellular APN credentials and keepAlive to use based on the SIM card that is inserted.

I leave it to your imaginations to populate an array of credentials that can be used based on the MNC and MCC that is pulled from the SIM card. I plan on having an SD card with a CSV table of MNC MCC keepAlive and APN credentials and extract the correct APN and keepalive after pulling the MNC and MCC.

Perhaps this could be incorporated into the Cellular library in the spark firmware at some point?

Hope this helps some of you, and avoids you having to dig through the AT command manual and the 3GPP TS 51.011 and ETSI TS 102 221 specifications like I did! :doge:

Here it is:

#include "Particle.h"
#include "cellular_hal.h"

//------------------Particle system configuration------------------------------

#define ALTERNATIVE_SETUP_CELL_CREDENTIALS
// #define ALERNATIVE_DYNAMIC_CELL_CREDENTIALS

#define QUICK_CREDENTIAL_CHANGE
// #define POWER_CYCLE_MODEM_ON_NEW_CREDENTIALS
// #define DISPLAY_RAM_CHANGE_INFO

const unsigned long DYNAMIC_ALTERNATIVE_CREDENTIALS_WAIT_TIME = 35000; //Time after loop() first runs at which we attempt to change the cellular credentials
const unsigned long POWER_CYCLE_MODEM_OFF_TIME = 5000;


SYSTEM_MODE(SEMI_AUTOMATIC);
SYSTEM_THREAD(ENABLED);

STARTUP(
   System.enableFeature(FEATURE_RESET_INFO);          //Sets system flag that retains reset reason in memory for reporting after reset
   System.enableFeature(FEATURE_RETAINED_MEMORY);     //Sets system flag that allows for our application to retain some variables even through power outage and reset
);



/*=============================================>>>>>
= DEBUG VARIABLES =
===============================================>>>>>*/

#define WAIT_FOR_SERIAL_MONITOR                 //Comment out to allow setup() to run immediately, instead of waiting for serial input first
#define WAIT_FOR_SERIAL_MONITOR_TIMEOUT 25000   //Time after the Electron restarts to wait for a serial character to be entered before proceeding

//Logging objects
SerialLogHandler logHandler(LOG_LEVEL_ALL,  //Default logging level for non-application messages
   {
      { "app", LOG_LEVEL_ALL },             //Dafault logging level for logs from any "app" category logging level
   }
);

static Logger myLog("app.main");  //Logger object used in this "main.cpp" file


/*=============================================>>>>>
= Connection tracking variables =
===============================================>>>>>*/
bool isCellularReady = false;
bool isCellularConnecting = false;
bool isCloudConnected = false;

/*=============================================>>>>>
= Cellular credentials variables and type definitions =
===============================================>>>>>*/

bool isAlternativeCredentials = false;                         //True if alternative credentials have been set
unsigned long alternativeCredentialsTimeStart = 0;             //millis() stamp at which alternative credential wait time is started

bool correctATresponse = false;

struct IMSI_t{
   char MCC_str[4] = "";
   char MNC_str[4] = "";
   uint16_t MCC = 0;
   uint16_t MNC = 0;
   uint8_t  MNC_len = 0;
   char     IMSI[16] = "";

   bool decode(){
      if( (strcmp(IMSI, "") != 0) && strlen(IMSI) > 6){

         //Decode the MCC (first 3 digits)

         MCC_str[0] = IMSI[0];
         MCC_str[1] = IMSI[1];
         MCC_str[2] = IMSI[2];
         MCC_str[3] = '\0';
         myLog.info("MCC_str == %s", MCC_str);
         MCC = strtol(MCC_str, NULL, 10) ;
         // long tempLong = strtol(temp, NULL, 10) ;
         // MCC = tempLong;

         //Decode MNC (digits 4,5,and sometimes 6)

         if(MNC_len > 0){
            MNC_str[0] = IMSI[3];
            MNC_str[1] = IMSI[4];
            if(MNC_len == 3){
               MNC_str[2] = IMSI[5];
            }
            else{
               MNC_str[2] = '\0';
            }
         }
         else{
            //Don't have enough information to determine the MNC
            myLog.warn("Couldn't decode the MNC, don't know how many digits it is!");
            MNC_str[0] = '\0';
         }

         MNC_str[3] = '\0';
         myLog.info("MNC string = %s", MNC_str);
         MNC = strtol(MNC_str, NULL, 10);
         // tempLong = strtol(temp, NULL, 10);
         // MNC = tempLong;
         if(MNC_len > 2 ){ myLog.info("Decoding IMSI resulted in: \r\n MCC = %03d  |  MNC = %03d", MCC, MNC);}
         else if(MNC_len == 2) {myLog.info("decoding IMSI resulted inL \r\n MCC = %03d  |  MNC = %02d", MCC, MNC);}
         else{myLog.warn("Couldn't decode the MNC... MNC_len not a valid number!");}
      }
      else{
         myLog.warn("Couldn't decode IMSI.. IMSI invalid!");
         return false;
      }
   }

   void print(){
      if(MNC_len>2)        {myLog.info("  IMSI = %s  |  MCC = %03d  |  MNC = %03d  | MNC_len = %1550d", IMSI, MCC, MNC, MNC_len);}
      else if(MNC_len==2)  {myLog.info("  IMSI = %s  |  MCC = %03d  |  MNC = %02d  | MNC_len = %1550d", IMSI, MCC, MNC, MNC_len);}
   }

};

//Declare a global variable of type IMSI_t
IMSI_t imsi;



/*=============================================>>>>>
= Cellular credentials struct... contains all information you would need
to successfully connect to a cellular network with a particular SIM card =
===============================================>>>>>*/

struct cellCredentials_t{
   uint8_t        MCC;
   uint8_t        MNC;
   char*          APN_name;
   char*          userName;
   char*          passWord;
   uint16_t       keepAlive;

   void print() const{
      Serial.printf("\n\n::cellCredentials_t::\nAPN_name = \"%s\" \n userName = \"%s\" \n passWord = \"%s\" \n keepAlive = %d\n\n",
                     APN_name, userName, passWord, keepAlive);
   }
};


const cellCredentials_t particleCellCredentials = {
   MCC:        0,
   MNC:        0,
   APN_name:    "spark.telefonica.com",
   userName:   "",
   passWord:   "",
   keepAlive:  (23*60)
};

const cellCredentials_t zantelCellCredentials = {
   MCC:        640,
   MNC:        3,
   APN_name:    "znet",
   userName:   "",
   passWord:   "",
   keepAlive:  (100)
};

const cellCredentials_t rogersCellCredentials = {
   MCC:        302,
   MNC:        720,
   APN_name:    "internet.com",
   userName:   "wapuser1",
   passWord:   "wap",
   keepAlive:  (150)
};


//Declare a global pointer to a const cellCredentials_t object representing the credentials we should use (defaults to NULL initialization)
const cellCredentials_t* targCellCredentials = &particleCellCredentials;


/*=============================================>>>>>
= Function Forward Declarations =
===============================================>>>>>*/
void printResetReason();
static void waitForSerialInput();

//Function for tranforming Particle API AT Response
const char* get_AT_response_code_tag(int codeNum);

uint16_t modemTurnOnDelayTime();

bool modemIsOn(unsigned int timeout=1000);

bool acquireIMSI(unsigned int timeout=5000);
bool get_MNC_len(unsigned int timeout = 10000);

const cellCredentials_t* getCellCredentials(const IMSI_t &targIMSI);

bool setupAlternativeCellCredentials();

/*=============================================>>>>>
= SETUP FUNCTION =
===============================================>>>>>*/

unsigned long lastFreeMemory = 0;

void setup(){
   //Start the serial monitor
   Serial.begin(115200);
   myLog.info("setup() just began!");

   //Wait for serial monitor input before proceeding with setup() (if WAIT_FOR_SERIAL_MONITOR is defined)
   #ifdef WAIT_FOR_SERIAL_MONITOR
   waitForSerialInput();
   #endif

   //Print out reset reason
   printResetReason();

   lastFreeMemory =  System.freeMemory() ;
   //Print out the reset reason to the serial monitor
   myLog.warn( "Free RAM is: %lu bytes", lastFreeMemory);

   #ifdef ALTERNATIVE_SETUP_CELL_CREDENTIALS

   Cellular.on();
   delay(modemTurnOnDelayTime());
   while(!setupAlternativeCellCredentials()){
      Particle.process();
   };

   #else

   myLog.info("Calling Particle.connect()");
   Particle.connect();

   #endif

   myLog.warn("setup() ending...");
   Serial.println();
   Serial.println();
   Serial.println("==================");
   Serial.println("      END");
   Serial.println("       OF");
   Serial.println("     SETUP()");
   Serial.println("==================");
   Serial.println();
   Serial.println();
}

/*= End of SETUP FUNCTION =*/
/*=============================================<<<<<*/


/*=============================================>>>>>
= MAIN LOOP  =
===============================================>>>>>*/



void loop(){

   #ifdef ALERNATIVE_DYNAMIC_CELL_CREDENTIALS
   //Initialize the timer for setting alternative credentials
   if(alternativeCredentialsTimeStart == 0){
      alternativeCredentialsTimeStart = millis();
   }

   //Check if its time to try and change the cellular credentials
   if( (millis()-alternativeCredentialsTimeStart) > DYNAMIC_ALTERNATIVE_CREDENTIALS_WAIT_TIME && !isAlternativeCredentials){
      isAlternativeCredentials = true;
      setupAlternativeCellCredentials();
   }

   #endif

   /*=============================================>>>>>
   = Check connectivity and report to serial monitor =
   ===============================================>>>>>*/
   bool temp = Cellular.connecting();
   if( temp != isCellularConnecting ){
      isCellularConnecting = temp;
      myLog.warn("cellular network is: %s", isCellularConnecting ? "connecting" : "not connecting");
   }

   temp = Cellular.ready();
   if( temp != isCellularReady ){
      isCellularReady = temp;
      myLog.warn("cellular network is: %s", isCellularReady ? "connected" : "disconnected");
   }

	temp = Particle.connected();
	if (temp != isCloudConnected) {
      isCloudConnected = temp;
      myLog.warn("cloud connection is: %s", isCloudConnected ? "connected" : "disconnected");
   }
   /*= End of Check connectivity and report to serial monitor =*/
   /*=============================================<<<<<*/

   #ifdef DISPLAY_RAM_CHANGE_INFO
   //Send out warning message if the amount of RAM has changed since last loop()
   if( lastFreeMemory != System.freeMemory()  ){
      myLog.warn("WARNING: RAM available changed from %6.2f kB to %6.2f kB", lastFreeMemory/1000.0 , System.freeMemory()/1000.0 );
      lastFreeMemory = System.freeMemory();
   }
   #endif

}

/*= End of MAIN LOOP  =*/
/*=============================================<<<<<*/


const cellCredentials_t* getCellCredentials(const IMSI_t &targIMSI){

   if(targIMSI.MCC == 640){
      //Tanzania
      if(targIMSI.MNC == 3){
         //Zantel
         myLog.info("WE HAVE A ZANTEL SIM CARD");
         return &zantelCellCredentials;
      }
   }

   else if(targIMSI.MCC == 302){
      //Canada
      if(targIMSI.MNC == 720){
         //Rogers wireless
         myLog.info("WE HAVE A ROGERS SIM CARD");
         return &rogersCellCredentials;
      }
   }

   else if(targIMSI.MCC == 214){
      //Spain
      if(targIMSI.MNC == 7){
         //Movistar
         myLog.info("WE HAVE A PARTICEL SIM CARD");
         return &particleCellCredentials;
      }
   }

   myLog.info("ASSUME A PARTICLE SIM CARD");
   return &particleCellCredentials;

}


/*=============================================>>>>>
= ALTERNATIVE CELLULAR CREDENTIALS CODE =
===============================================>>>>>*/

bool setupAlternativeCellCredentials(){

   if(!modemIsOn(50)){
      return false;
   }

   if( acquireIMSI() ){

      myLog.info("Now figuring out which cell credentials to use based on IMSI");
      imsi.decode();
      imsi.print();

      targCellCredentials = getCellCredentials(imsi);

      myLog.error("YOU HAVEN'T FINISHED CODING THIS BIT YET, NOW HAVE YOU? 8==D--");


   }

   // #ifdef QUICK_CREDENTIAL_CHANGE
   myLog.info("Attempting to set cellular credentials");
   Particle.disconnect();
   cellular_credentials_set(targCellCredentials->APN_name, targCellCredentials->userName, targCellCredentials->passWord, NULL);
   Particle.keepAlive(targCellCredentials->keepAlive);
   myLog.info("Calling Particle.connect()");
   Particle.connect();
   return true;
   // #endif

   Serial.println();
   Serial.println();
   Serial.println();
   Serial.print("!!========================!!\n\r!!========================!!\n\r");
   myLog.info("Setting up alternative cellular credentials -->setupAlternativeCellCredentials()");
   Serial.print("!!========================!!\n\r!!========================!!\n\r");
   
   myLog.info("Calling  Particle.disconnect()");
   Particle.disconnect();
   Particle.process();
   
   myLog.info("Checking if the modem is on...");
   int response = Cellular.command(10000, "AT\r\n");
   
   //Check if the modem is on or not
   if( response == RESP_OK ){
   
      myLog.info("Modem is on and responsive!");
   
      // 16:MT silent reset (with detach from network and saving of NVM parameters), with reset of the SIM card
      myLog.info("Issuing silent reset command to modem ");
   
      response = Cellular.command(60000, "AT+CFUN=16\r\n");
      Particle.process();
   
      myLog.info("Response of modem on AT+CFUN=16 was: %d", response);
   
      #ifdef POWER_CYCLE_MODEM_ON_NEW_CREDENTIALS
   
      myLog.warn("Turning off modem");
      response =  Cellular.command(60000, "AT+CPWROFF\r\n");
   
      // Cellular.off();
      // Particle.process(); <---commented this after things were working
   
      // myLog.info("Checking if modem was turned off yet");
      // response =  Cellular.command(5000, "AT\r\n");
      // Particle.process();
   
      // while( response == RESP_OK ){
      //    myLog.warn("Modem is still responding... must not be off!");
      //    response =  Cellular.command(5000, "AT\r\n");
      //    Particle.process();
      // };
   
      if( response != RESP_OK ){
         myLog.info("Failed to turn off modem... response was: %d", response);
      }
      else{
         myLog.info("Modem successfully turned off!");
      }
   
      myLog.info("Now waiting for %d ms before turning modem back on", POWER_CYCLE_MODEM_OFF_TIME);
      delay(POWER_CYCLE_MODEM_OFF_TIME);
   
      myLog.warn("Turning on modem ");
      Cellular.on();
      Particle.process();
   
      response =  Cellular.command(5000, "AT\r\n");
      Particle.process();
   
      while( response != RESP_OK ){
         myLog.warn("Modem is not responding... must not be on yet!");
         response =  Cellular.command(5000, "AT\r\n");
         Particle.process();
      };
   
      #endif
   
   }
   else{
      myLog.warn("Response to AT command for checking if modem is on/off returned: %d ", response);
      myLog.warn("Looks like modem wasn't even on!  No need to power cycle!");
      myLog.warn("Turning on the modem!");
      Cellular.on();
   
      myLog.info("Sending a test command to see if modem is on! ");
      response = Cellular.command(20000, "AT\r\n");
   
      while( response != RESP_OK ){
         myLog.error("Failed to turn on the modem! response == %d", response);
         Cellular.on();
         Particle.process();
         response = Cellular.command(10000, "AT\r\n");
         Particle.process();
         delay(100);
      };
   
      myLog.info("Successfully turned on the modem!");
   
   }
   
   
   //Check that the modem is on and ready for AT commands
   myLog.info("Checking if modem is on");
   response =  Cellular.command(5000, "AT\r\n");
   while( response != RESP_OK ){
      myLog.warn("Modem not responding... must not be on!");
      response =  Cellular.command(5000, "AT\r\n");
      Particle.process();
   };
   
   myLog.info("Modem is on!");
   
   myLog.info("=============================================================================");
   myLog.info("Setting alternative cellular credentials -->setupAlternativeCellCredentials()");
   myLog.info("=============================================================================");
   targCellCredentials->print();
   
   cellular_credentials_set(targCellCredentials->APN_name, targCellCredentials->userName, targCellCredentials->passWord, NULL);     ///   THE BIG KAHUNA <--------------------------
   myLog.info("Finished setting cellular credentials!");
   // Particle.process();
   
   myLog.info("Setting keepAlive");
   Particle.keepAlive(targCellCredentials->keepAlive);
   myLog.info("Finished setting keepAlive");
   // Particle.process();
   
   myLog.info("Now calling Particle.connect()");
   Particle.connect();

}


/*=============================================>>>>>
= Function to get the code tag string corresponding to a given AT response code =
===============================================>>>>>*/
const char* get_AT_response_code_tag(int codeNum){
   switch(codeNum){
      case NOT_FOUND:
         return "NOT_FOUND";
      case WAIT:
         return "WAIT";
      case RESP_OK:
         return "RESP_OK";
      case RESP_ERROR:
         return "RESP_ERROR";
      case RESP_PROMPT:
         return "RESP_PROMPT";
      case RESP_ABORTED:
         return "RESP_ABORTED";
      default:
         return "INVALID RESPONSE CODE";
   }
}

const char* get_AT_response_type_tag(unsigned int codeNum){
   switch(codeNum){
      case TYPE_UNKNOWN:
         return "TYPE_UNKNOWN";
      case TYPE_OK:
         return "TYPE_OK";
      case TYPE_ERROR:
         return "TYPE_ERROR";
      case TYPE_RING:
         return "TYPE_RING";
      case TYPE_CONNECT:
         return "TYPE_CONNECT";
      case TYPE_NOCARRIER:
         return "TYPE_NOCARRIER";
      case TYPE_NODIALTONE:
         return "TYPE_NODIALTONE";
      case TYPE_BUSY:
         return "TYPE_BUSY";
      case TYPE_NOANSWER:
         return "TYPE_NOANSWER";
      case TYPE_PROMPT:
         return "TYPE_PROMPT";
      case TYPE_PLUS:
         return "TYPE_PLUS";
      case TYPE_TEXT:
         return "TYPE_TEXT";
      case TYPE_ABORTED:
         return "TYPE_ABORTED";
      default:
         return "UNDEFINED AT CALLBACK TYPE";
   }
}


/*=============================================>>>>>
= FUNCTION TO FIGURE OUT IF MODEM IS CURRENTLY ON =
===============================================>>>>>*/
int callbackModemOn(int type, const char* buf, int len, bool* modemOn){

   Serial.printlnf("AT command response type = %#X (%s)", type, get_AT_response_type_tag(type));
   Serial.printlnf("AT command response buffer = %s", buf);

   //Check if this chunk of the AT repsonse stream is a OK type response
   //and that we have a non-null pointer to modemOn work withmodemOn
   if( ( type == TYPE_OK ) && modemOn ){

         //We have successfully found the length of the MNC
         Serial.println("Modem is on!");
         *modemOn = true;
         return RESP_OK;

   }
   else{
      *modemOn = false;
   }

   return WAIT;

}


bool modemIsOn(unsigned int timeout){
   myLog.info("Checking if the modem is on...");
   bool modemOn = false;

   int response = Cellular.command(callbackModemOn, &modemOn, timeout, "AT\r\n");
   const char* responseTag = get_AT_response_code_tag(response);
   myLog.info("Response code was %d (%s)", response, responseTag);

   myLog.info("Returning modemIsOn() = %s", (modemOn)? "TRUE":"FALSE" );

   return modemOn;
}





/*============================================================= >>>>>
= FUNCTIONS TO RETRIEVE IMSI  # FROM MODEM and parse MNC/MCC=
================================================================>>>>>*/

//Function to parse the AT response of the  MNC length
int callback_MNC_length(int type, const char* buf, int len, uint8_t* mncLen){

   Serial.printlnf("AT command response type = %#X (%s)", type, get_AT_response_type_tag(type));
   Serial.printlnf("AT command response buffer = %s", buf);

   //Check if this chunk of the AT repsonse stream is a plus type response
   //and that we have a non-null pointer to mncLen work with
   if( ( type == TYPE_PLUS ) && mncLen ){
      if( sscanf(buf, "\r\n+CRSM: 144,0,\"%x\"", mncLen) == 1){
         //We have successfully found the length of the MNC
         Serial.printlnf("Found MNC length == %d", *mncLen);
         correctATresponse = true;
      }
   }

   return WAIT;

}


int callbackIMSI(int type, const char* buf, int len, char* toBuf){

   Serial.printlnf("AT command response type = %#X (%s)", type, get_AT_response_type_tag(type));
   Serial.printlnf("AT command response buffer = %s", buf);

   if ((type == TYPE_UNKNOWN ) && toBuf) {   //Check that the callback type is TYPE_PLUS and that imsi pointer is initialized (non-null)

      // myLog.trace("AT BUFFER SO FAR IS: %s", buf);
      // int result = sscanf(buf, "\r\n%d\r\n", toBuf);

      // if (sscanf(buf, "\r\n%[^\r]\r\n", toBuf) == 1){
      if (sscanf(buf, "\r\n%s\r\n", toBuf) == 1){
         if( strlen(toBuf) == 15 ){
            Serial.printlnf("Successfully Parsed IMSI: %s", toBuf);
            correctATresponse = true;
            return WAIT;
         }
      }
   /*nothing*/;
   }
   else if(type == RESP_OK && correctATresponse){
      //If we got the "OK" line after the IMSI was reported
      return RESP_OK;
   }
   else{
      return WAIT;
   }


}


uint16_t modemTurnOnDelayTime(){

   switch( System.resetReason() ){
      case RESET_REASON_PIN_RESET:
         return 510;
      case RESET_REASON_POWER_MANAGEMENT:
         return 510;
      case RESET_REASON_POWER_DOWN:
         return 4000;
      case RESET_REASON_POWER_BROWNOUT:
         return 4000;
      case RESET_REASON_WATCHDOG:
         return 510;
      case RESET_REASON_UPDATE:
         return 510;
      case RESET_REASON_UPDATE_TIMEOUT:
         return 510;
      case RESET_REASON_FACTORY_RESET:
         return 510;
      case RESET_REASON_SAFE_MODE:
         return 510;
      case RESET_REASON_DFU_MODE:
         return 510;
      case RESET_REASON_PANIC:
         return 4000;
      case RESET_REASON_USER:
         return 510;
      case RESET_REASON_UNKNOWN:
         return 4000;
      case RESET_REASON_NONE:
         return 4000;
      default:
         return 4000;
   }
}



bool acquireIMSI(unsigned int timeout){

   myLog.info("Checking if modem is on before attempting to proceed with acquireIMSI() function");

   // bool modemOn = modemIsOn(500);
   //Modem is off, turn it on


   if( !modemIsOn(50) ){

      return false;

   }

   //We want to get the IMSI number in order to parse the MCC and MNC out of it in order to know
   //what cell credentials to use.  This is complicated by the fact that some MNCs are 2 digits of the IMSI
   //and some are 3 digits of the IMSI.  In order to know how many digits of the IMSI are devoted to the
   //MNC, we need to read the Administrative Data Elementary File of the SIM card, and look at byte 4, which
   //contains the length of the MNC code for this SIM card's IMSI

   //Use Particle API to get MNC length
   correctATresponse = false;
   uint8_t numTries = 0;

   while(correctATresponse == false && numTries < 10){
      numTries++;
      imsi.MNC_len = 0;
      myLog.info("About to request the 4th byte of E.F. 6FAD from SIM card via modem");
      //Use the AT+CRSM command to read/write data to the SIM card using the modem as an interpreter
      //See 3GPP TS 51.011 Section 10.3.18 "EFad (Administrative Data)"... which says byte 4 of this SIM register determines length of MNC
      //AT+CRSM =     176     ,     28589          ,     0                                         , 3                                             , 1
          //      READ BINARY , ef identifier 6FAD ,   upper bits of byte offset to start read from, lower bits of byte offset to start read from  , number of bytes to read
      int response = Cellular.command(callback_MNC_length, &imsi.MNC_len, timeout, "AT+CRSM=176,28589,0,3,1\r\n");

      if( response == RESP_OK ){
         if ( correctATresponse ) {
            myLog.info("Successfully parsed the MNC length..\r\n================>  MNC length is: %d", imsi.MNC_len);
         }
         else{
            myLog.error("AT command to get MNC length returned RESP_OK but no MNC length was parsed!");
         }
      }
   };

   if(numTries >= 10){
      myLog.error("Couldn't determine the length of the MNC!!!");
   }

   //Use Particle API to query modem to query sim card to retrieve IMSI number and parse it
   correctATresponse = false;
   numTries = 0;
   while(correctATresponse == false && numTries < 10){
      numTries++;
      imsi.IMSI[0] = '\0';
      myLog.info("About to request the IMSI number from the modem");
      //Use Cellular.command(AT+CIMI) to request the IMSI number from modem
      Cellular.command(callbackIMSI, imsi.IMSI, timeout, "AT+CIMI\r\n");

      if ( strcmp(imsi.IMSI, "") != 0 ) {
         myLog.info("Successfully parsed a potential IMSI number..\r\n================>  parsed string is: %s", imsi.IMSI);
      }
      else{
         myLog.error("AT command to get IMSI number returned RESP_OK but no IMSI number was parsed!");
      }

   };

   return correctATresponse;

}



/*=============================================>>>>>
= TEST MODE FUNCTIONS AND DEBUGGING FUNCTIONS =
===============================================>>>>>*/

static void waitForSerialInput(){
   unsigned long timeStart = millis();
   while(!Serial.available()){
      if((millis() - timeStart) > WAIT_FOR_SERIAL_MONITOR_TIMEOUT){
         myLog.warn("Waiting for serial input timed out!");
         return;
      }
      myLog.warn("Waiting for serial input before proceeding");
      delay(500);
   }
   //Now we need to empty the serial buffer
   while(Serial.available()){
      char uselessChar = Serial.read();
   }
   myLog.warn("Received serial input, now proceeding!");

    // while(!Serial.isConnected())
    //     Particle.process();

    myLog.info("Serial monitor open!  Now proceeding!");

}

void printResetReason(){
   //Print out Electron system ID
   char deviceID[ System.deviceID().length() + 1 ] ;
   System.deviceID().toCharArray(deviceID, strlen(deviceID));

   //Print out reset reason
   myLog.info("Device ID: %s ", deviceID);
   myLog.info("SYSTEM RESET REASON IS: ");
   switch( System.resetReason() ){
      case RESET_REASON_PIN_RESET:
         myLog.info("Reset button or reset pin");
      break;
      case RESET_REASON_POWER_MANAGEMENT:
         myLog.info("Low-power management reset");
      break;
      case RESET_REASON_POWER_DOWN:
         myLog.info("Power-down reset");
      break;
      case RESET_REASON_POWER_BROWNOUT:
         myLog.info("Brownout reset");
      break;
      case RESET_REASON_WATCHDOG:
         myLog.info("Hardware watchdog reset");
      break;
      case RESET_REASON_UPDATE:
         myLog.info("Successful firmware update");
      break;
      case RESET_REASON_UPDATE_TIMEOUT:
         myLog.info("Firmware update timeout");
      break;
      case RESET_REASON_FACTORY_RESET:
         myLog.info("Factory reset requested");
      break;
      case RESET_REASON_SAFE_MODE:
         myLog.info("Safe mode requested");
      break;
      case RESET_REASON_DFU_MODE:
         myLog.info("DFU mode requested");
      break;
      case RESET_REASON_PANIC:
         myLog.info("System panic");
      break;
      case RESET_REASON_USER:
         myLog.info("User-requested reset");
      break;
      case RESET_REASON_UNKNOWN:
         myLog.info("Unspecified reset reason");
      break;
      case RESET_REASON_NONE:
         myLog.info("Information is not available");
      break;
   }
};

#2

Thanks for sharing!!