Receiving SMS in Particle Electron

Thanks to @mrlowalowa, @developer_bt, @ScruffR, @RWB, etc… for their amazing work on Electron and SMS.

I managed to receive SMS and process it using third party SIM. Whenever an Call or SMS is received, the interrupt on the RI_UC will be triggered. Also we can read the SMS using AT+CMGR Command. The syntax is AT+CMGR=<index>. The index is the index of the memory at which the message is stored. Here the key is to find the index of the last SMS. For this purpose we can use AT+CPMS? command. This command returns the details of the selected SMS Storage. We can parse the last storage index from this command return value. For a more detailed explanation please refer to this link.

Here is a sample program that turn on the LED on D7 on/off when SMS is received from a predefined number. If you send the text D7=HIGH then it turns on the LED, if you send the text D7=LOW then it turns off the LED. If you send the text deleteall then it delete all the SMS (Warning, it will delete all the SMS, so use it with care. This is just to included for testing purpose only).

Hope this helps!

#define TIMEOUT                     10000
#define CMD_NONE                    0
#define CMD_DELETE_ALL              1

#define LED_PIN                     D7
// Replace with a phone number, the application will only process
// the SMS received from this number
#define AUTHORIZED_PHONE_NUMBNER    "<replace with a mobile number>"

void riInterrupt(void);

int state = LOW;
bool isCallRecognized = false;
char szReturn[32] = "";
int maxIndex = 1;
int ledState = LOW;
int command = CMD_NONE;

int phoneCallback(int type, const char* buf, int len, char* param){  
    Serial.print("Phone Callback: ");
    
    if(strncmp(&buf[2], "+CMGR:", 6) == 0){
        Serial.write((const uint8_t*)&buf[2], len - 2);
        Serial.println();
        
        char szState[32];
        char szPhoneNumber[18];

        sscanf(&buf[9], "%[^/','],%[^/','],%s", szState, szPhoneNumber);
        
        maxIndex++;
        
        strcpy(szPhoneNumber, &szPhoneNumber[1]);        
        szPhoneNumber[strlen(szPhoneNumber) - 1] = 0;
        
        if(strcmp(AUTHORIZED_PHONE_NUMBNER, szPhoneNumber) == 0){
            Serial.println("SMS detected");
            
            isCallRecognized = true;    
        }
        
        Serial.print("Phone Number ");
        Serial.print(szPhoneNumber);
        Serial.print(", ");
        Serial.print(szState);
        Serial.println();
    }
    else if(strncmp(&buf[2], "+CPMS:", 6) == 0){
        char szData[13];
        
        Serial.write((const uint8_t*)&buf[2], len - 2);
        Serial.println();

        sscanf(&buf[9], "%[^/','], %d", szData, &maxIndex);
        
        maxIndex++;

        Serial.print("Current Index ");
        Serial.print(maxIndex);
        Serial.print(", ");
        Serial.print(szData);
        Serial.println();
    }
    else{
        Serial.write((const uint8_t*)buf, len - 2);
        Serial.println();
        
        if(isCallRecognized){
            isCallRecognized = false;
            
            if(strncmpi(buf, "deleteall", 9) == 0){
                Serial.println("Delete all command received.");
                
                command = CMD_DELETE_ALL;
            }
            else{
                char szPin[3];
                char szState[5];
                
                sscanf(buf, "%[^/'=']=%s", szPin, szState);
                
                Serial.println();
                Serial.print(szPin);
                Serial.print("=");
                Serial.print(szState);
                Serial.println();
                
                if(strcmpi(szPin, "D7") == 0){
                    if(strcmpi(szState, "HIGH") == 0){
                        ledState = HIGH;
                    }
                    else if(strcmpi(szState, "LOW") == 0){
                        ledState = LOW;
                    }
                }
            }
        }
    }
    
    state = LOW;
    
    return WAIT;
}

void setup(){
    Serial.begin(115200);
    
    pinMode(LED_PIN, OUTPUT);
    pinMode(RI_UC, INPUT_PULLUP);
    
    attachInterrupt(RI_UC, riInterrupt, CHANGE);
    
    Cellular.command(phoneCallback, szReturn, TIMEOUT, "AT+CMGF=1\r\n");
    Cellular.command(phoneCallback, szReturn, TIMEOUT, "AT+CPMS?\r\n");
}

void loop(){
    digitalWrite(LED_PIN, ledState);
    
    if(CMD_DELETE_ALL == command){
        Cellular.command(phoneCallback, szReturn, TIMEOUT, "AT+CMGD=1,4\r\n");

        command = CMD_NONE;
    }
    else if(state == HIGH){
        char szCommand[32];
        
        sprintf(szCommand, "AT+CMGR=%d\r\n", maxIndex);
        
        Serial.println();
        Serial.print("Sending command: ");
        Serial.print(szCommand);
        Serial.println();
        
        Cellular.command(phoneCallback, szReturn, TIMEOUT, szCommand);
    }
}

void riInterrupt(){
    state = digitalRead(RI_UC);
}

Demo Video

11 Likes

I tried the example works great. :smiley:

2 Likes

it works also delete all does a loop of some kind, but now i can have fun with this code.

1 Like

@x1800MODMY360x, that loop issue is fixed in the original code, take a look. Thanks for pointing out! :smile:

Hello,
I need a bit help :slight_smile:
How can I receive the message into a string variable?
In which part of the code can be inserted.
I need to keep the message into string variable so I can send to another function.
So I need something like only to read the message and puts puts it in a string.

Hi,

Suggest adding maxIndex = 1 in CMD_DELETE_ALL to avoid crashes on SMS_RECEIVE after DELETEALL command;

if(CMD_DELETE_ALL == command){
        Cellular.command(phoneCallback, szReturn, TIMEOUT, "AT+CMGD=1,4\r\n");

        command = CMD_NONE;
        maxIndex = 1
    }
3 Likes

Thanks @alan_m for pointing out!.

1 Like

I too am having some difficulty finding the right commands to pull out just the message in a string format (without the pre-able info).

Has anyone had a chance to solve this?
I think it is a combination of a string trim function
e.g. https://docs.particle.io/reference/firmware/electron/#remove-

But I'm a little concerned that if a user were to use a shorter or longer phone number than expected, then it may result in the function cutting off too much of the message and failing to process the instruction.

Would greatly appreciate any advice here

I am using a library to manage SMS on the electron. Works great: https://github.com/robynjayqueerie/electron-atcommand-library

1 Like

@Cameron
I have used the library from Twilio and works great. Only problem is that no information about the receiving number and has no feature for sending messages. Also good feature is that the library works with interruptions.

For sending messages @ScruffR has written an excellent example.

@Fabien
This is a excellent library, I do not have used so far but I see has a function for sending, receiving and ID feature.
I think it contains everything required for working with SMS. :+1:

Thanks, that library looks good - but I'm not getting the message body back from the functions listed in the sms.cpp file.

I (think) it gets lost at >
Cellular.command(_cbCMGR, &param, "AT+CMGR=%d\r\n", ix)

Which is located in sms.cpp
It returns the value -2 and after putting in Serial.println everywhere, I can't seem to find a function that puts out the message string

Do you have a working copy you may be able to share for me?

Thanks @Fabien

I managed to get the library working! yay :smile:

Although I’m a tad concerned around stability, the library author does mention a few issues with it in their GitHub page re: buffer checking and when I send 4 messages rapidly, the message text comes back with the text from the 4 messages on new lines in the same string.

Any chance you have found any other issues in your experiences with that library?

I’m wondering whether I spend a lot of time finding the risk factors/issues with the library and start solving them - or find a slightly simpler approach… ponders*

@ScruffR

Any chance you have tackled the reading a direct String from SMS challenge after your amazing SMS sending code?

I liked your really low overhead/no complexity approach last time

Here is a simple example that I just did but at the moment I’m not able to test.

// This #include statement was automatically added by the Particle  IDE.
#include "application.h"
// This #include statement was automatically added by the Particle IDE.
#include "sms.h"

STARTUP(cellular_credentials_set("internet", "internet", "t-mobile", NULL));
/* Receive SMS callback. Requires base firmware 0.5.1.rc2
https://github.com/spark/firmware/releases/tag/v0.5.1-rc.2 */
STARTUP(cellular_sms_received_handler_set(sms_received, NULL, NULL));

SYSTEM_THREAD(ENABLED);
//SYSTEM_MODE(AUTOMATIC);
SYSTEM_MODE(SEMI_AUTOMATIC);

const int REL1 = D7; 

volatile int newSMS = 0;
String smsBody;

void setup(){

  Particle.keepAlive(45);  // send keep alive ping every 45 sec

    Serial.begin(115200);
  
    delay(50);
  
    pinMode(REL1, OUTPUT);

    Particle.variable("mesage", smsBody);

    Particle.connect();  // for semi automatic mod

    //smsBody.reserve(40);  // 

    /* Delete all potentially stale SMS from the modem */
    SmsDeleteFlag flag = DELETE_ALL;
    smsDeleteAll(flag);
 
}

void loop(){

if (newSMS > 0) {
  smsBody = checkUnreadSMS();
  if (smsBody != NULL) {
 
    Serial.println(smsBody);

    if(smsBody == "ON")
    {
       digitalWrite(REL1, HIGH);
    }
    else if(smsBody == "OFF")
    {
       digitalWrite(REL1, LOW);
    }

  }
  if (newSMS > 0) newSMS--;
}

}

/* ----------------------------------------------

     INCOMING SMS-BASED COMMANDS

------------------------------------------------ */
void sms_received(void* data, int index)
{
    newSMS++;
}
1 Like

Not gone there yet, but I might do.
How close is @developer_bt’s solution to your expectations?

Humm - no luck. Can’t seem to get it to go past this line:

smsBody = checkUnreadSMS();

@developer_bt - I’m wondering - maybe the libraries were edited on the GitHub page and don’t work any more. Do you have a copy of your working library copy?

I think the problem is probably to Particle firmware version.
Which version are you using? It is necessary to use 0.5.1. or up.
Here is a copy:

#include "sms.h"

int _cbCMGL(int type, const char* buf, int len, CMGLparam* param)
{
    if ((type == TYPE_PLUS) && param && param->num) {
        // +CMGL: <ix>,...
        int ix;
        if (sscanf(buf, "\r\n+CMGL: %d,", &ix) == 1)
        {
            *param->ix++ = ix;
            param->num--;
        }
    }
    return WAIT;
}

int smsList(const char* stat /*= "ALL"*/, int* ix /*=NULL*/, int num /*= 0*/) {
    int ret = -1;
    CMGLparam param;
    param.ix = ix;
    param.num = num;
    if (RESP_OK == Cellular.command(_cbCMGL, &param, "AT+CMGL=\"%s\"\r\n", stat))
        ret = num - param.num;
    return ret;
}

bool smsDelete(int ix)
{
    /* <index> = Storage position */
    bool ok = false;
    ok = (RESP_OK == Cellular.command("AT+CMGD=%d\r\n", ix));
    return ok;
}

bool smsDeleteAll(SmsDeleteFlag flag) {
  /*
  * <flag> = Deletion flag. If present, and different from 0, <index> is ignored:
  * • 1: delete all the read messages from the preferred message storage, leaving unread messages
  * and stored mobile originated messages (whether sent or not) untouched
  * • 2: delete all the read messages from the preferred message storage and sent mobile originated
  * messages, leaving unread messages and unsent mobile originated messages untouched
  * • 3: delete all the read messages from the preferred message storage, sent and unsent mobile
  * originated messages leaving unread messages untouched
  * • 4: delete all the messages from the preferred message storage including unread messages
  */
  bool ok = false;
  ok = (RESP_OK == Cellular.command("AT+CMGD=1,%d\r\n", flag));
  return ok;
}

int _cbCMGR(int type, const char* buf, int len, CMGRparam* param)
{
    if (param) {
        if (type == TYPE_PLUS) {
            if (sscanf(buf, "\r\n+CMGR: \"%*[^\"]\",\"%[^\"]", param->num) == 1) {
            }
        } else if ((type == TYPE_UNKNOWN) && (buf[len-2] == '\r') && (buf[len-1] == '\n')) {
            memcpy(param->buf, buf, len-2);
            param->buf[len-2] = '\0';
        }
    }
    return WAIT;
}

bool smsRead(int ix, char* num, char* buf, int len)
{
    bool ok = false;
    CMGRparam param;
    param.num = num;
    param.buf = buf;
    ok = (RESP_OK == Cellular.command(_cbCMGR, &param, "AT+CMGR=%d\r\n", ix));
    return ok;
}

String checkUnreadSMS() {
    String body;
    char buf[512] = "";
    int ix[8];
    int n = smsList("REC UNREAD", ix, 8);

    if (n > 8) n = 8;

    while (n-- > 0)
    {
        char num[32];
        if (smsRead(ix[n], num, buf, sizeof(buf))) {
            body = String(buf);

            // All done with this message, let's delete it
            smsDelete(ix[n]);
            return body;
        }
    }
}

// header file
    #ifndef SMS_H
    #define SMS_H

    #include "application.h"

    typedef struct { char* buf; char* num; } CMGRparam;
    typedef struct { int* ix; int num; } CMGLparam;

    int _cbCMGL(int type, const char* buf, int len, CMGLparam* param);
    int _cbCMGR(int type, const char* buf, int len, CMGRparam* param);
    int smsList(const char* stat /*= "ALL"*/, int* ix /*=NULL*/, int num /*= 0*/);
    bool smsDelete(int ix);
    enum SmsDeleteFlag {DELETE_READ = 1, DELETE_READ_SENT = 2, DELETE_READ_SENT_UNSENT = 3, DELETE_ALL = 4};
    bool smsDeleteAll(SmsDeleteFlag flag);
    bool smsRead(int ix, char* num, char* buf, int len);
    String checkUnreadSMS();
    void checkReadSMS();

    #endif
1 Like

Got it working! :slight_smile:

I adjusted, while using the Twilio SMS library:

  1. The force delete in setup seems to work well.

void setup()
{
    char res;
    int atResult;
	Serial.println("Entering sms setup");
	atResult = uCmd.setSMSMode(1);
	if(atResult == RESP_OK)
	{
		Serial.println("Text mode setup was OK");
	}
	else
	{
		Serial.println("Did not set up text mode");
	}
	deleteSMSOnStart();
}

The next part, was more around where to put the SMS processing function.
For some reason when I send a SMS (so there should only be 1 SMS in the buffer)
The ‘for’ loop below in it’s last loop, picks up a blank SMS. So what I had to do was put the processing function within the ‘for’ loop.

void smsRecvCheck(void* data, int index)
{
    	Serial.println("Looking for an sms message");
		// read next available messages
		if(uCmd.checkMessages(10000) == RESP_OK){
			uCmd.smsPtr = uCmd.smsResults;
			for(i=0;i<uCmd.numMessages;i++){
				Serial.printlnf(uCmd.smsPtr->sms);
				Serial.printlnf(uCmd.smsPtr->phone);
				messageText = String(uCmd.smsPtr->sms);
				messageText.toLowerCase();
				phoneReturn = String(uCmd.smsPtr->phone);
				phoneReturn.trim();
				processMessage(messageText, phoneReturn);
				uCmd.smsPtr++;
			}
		}
		//----Delete SMS's----
		
		
    	uCmd.smsPtr = uCmd.smsResults;
		
		for(i=0;i<uCmd.numMessages;i++){
			if(uCmd.deleteMessage(uCmd.smsPtr->mess,10000) == RESP_OK)
			{
				Serial.println("message deleted successfully");
			}
			else 
			{
			    Serial.println("could not delete message");
			}
			uCmd.smsPtr++;
		}
			
}

The magic was putting the below in the ‘for’ loop.

messageText = String(uCmd.smsPtr->sms);
messageText.toLowerCase();
phoneReturn = String(uCmd.smsPtr->phone);
phoneReturn.trim();
processMessage(messageText, phoneReturn);

I found how to get the phone number as well from the device that sent it by adding in ->phone
And calling a new function I wrote called processMessage(…) also helped :slight_smile:

Now begins the trick of trying to separate commands out from comma separated string text.

2 Likes

@Cameron Excellent work :slight_smile:
Whether you can to put the complete code of the current library?
And if you can make a simple example of how you receive a message and phone number in the loop ().

Sure can! I found this approach to be a little bit easier than the one first posted very kindly by krvarma - but it was a bit over my head and didn’t seem to work for me out of the box.

I have added in a whole heap of comments, read me and list of future improvements which should be considered.

The code allows you to:

  1. Send a return SMS acknowledging a SMS command
  2. Check commands have the correct user pass code before processing the command
  3. Send codes in a multi-layer format: e.g. 1234,on,5 aka ,,<command_option>
  4. Automatically manage the receiving of SMSs
  5. Print out Strings of either the message and/or phone number that sent you the message.

Hope it works well for you and others. Feel free to message if you need more detail or branch improvements off it :slight_smile:

Cheers again for your help @developer_bt, @ScruffR

2 Likes