Dealing with USSD responses coming from Serial

I have a remote device (a MEGA that counts sheep) that needs to send me an SMS every day that contains the current airtime balance on it. When requesting the balance with USSD, my provider uses the opportunity to advertise their junk, causing the response to contain no less than 102 characters.

All I'm after is the last few characters so I can parse the balance. When I send the AT command the result is supposed to look like this:

WIN BIG with MTN CHALILA on any Bundle 
bought using *777#. Dial NOW!Your Account 
Balance is K85.5280

I'm not sure if the incoming characters is truncated by the SIM900 module or the MEGA, but I only get this:

+CUSD: 0,"WIN BIG with MTN CHALILA

How do I get my hands on the whole response? This is the sketch I use to test this:

String  inData;  

void setup() {
      Serial.begin(9600);
      Serial2.begin(9600);
      CheckBalance();
}

void loop() {

}

void ShowSim900Data()
    {
        while(Serial2.available()!=0)
          Serial.write(Serial2.read());
    }


 void CheckBalance()
    {
       Serial2.println(F("AT+CMGF=1")); 
       delay(500);
       ShowSim900Data(); 
       Serial2.println(F("AT+CUSD=1,\"*114#\"")); 
       delay(5000);
 //      ShowSim900Data();  
       while (Serial2.available() !=0) 
       {
           char sim900response = Serial2.read();
           inData += sim900response;                     
       }  
       Serial.print(F("inData = "));
       Serial.print(inData);      
     }
       while (Serial2.available() !=0)
       {
           char sim900response = Serial2.read();
           inData += sim900response;                     
       }

This assumes that ALL of the data that constitutes a packet has arrived. Why do you assume that?

Do I have to request the next packet? How do I do this?

Serial is

S

L

O

W

Your Arduino can do thousands of calculations in between each letter arriving. The best solution is to never wait for a letter to arrive. If something comes, deal with it and then go back to doing what you were doing. If the something completes a valid message, then you can do something with that.

Look for "Serial input basics - updated" on this forum.

Basically, you want to look for the letters "account balance is " and then process the number that comes after that. The difficult part will be working out where the end of the number is. Hopefully there's a carriage return or some other kind of end marker after the number.

When you get an "a", you might be looking at the start of the balance. If you get a "c" at some time after that, then you wait for the next "c". If something different arrives, then go back to waiting for the "a".

This message says that using PDU mode instead of Text mode will do the trick.

  • Set Message Format to PDU (AT+CMGF=0)
  • Execute Your USSD Command (AT+CUSD=1,5442*3#,15) - example message
  • Read response from the Port.

Thanks @johnwasser, I tried that already but the result remains the same.

I think PaulS and MorganS (any relation?) are correct. You need to wait longer for the rest of the message to arrive and not give up the first time you empty the input buffer.

It's not matter of "packets". Each character is an individual and they get put into a buffer as they arrive. If you read faster than the characters arrive you will empty the buffer (Serial2.avaialble() == 0) before all of the characters arrive.

You can use a timer to allow up to 100 milliseconds for a character to arrive:

        unsigned long lastTime  = millis();
        // Wait for up to 1/10th of a second per character
        while (millis() - lastTime <= 100UL)
        {
            if (Serial2.available()) 
            {
                char sim900response = Serial2.read();
                lastTime = millis();
                inData += sim900response;        
            }             
        }

Thanks @johnwasser, I tried your method, but the result stays exactly the same. Before you posted that tip I tried to do the same by doing delay(100); right after inData += sim900response; .

Would this not do the same as your suggestion?

Anyhow, the result remains the same.

Now I did manage to get the whole USSD response printed out, but it's nasty and unpredictable. In my full program I have sendATcommand, a wonderful little piece of code that takes care of sending AT commands and dealing with the responses. See the code right at the bottom. When calling the template you specify the expected response, and the function repeats the command until the expected answer arrives or the timeout is reached. It reads the serial response until it finds your expected answer, and leaves the rest of the response in serial to do with as I please.

When I put in the expected answer as "777" (this is just for testing purposes, not a permanent solution) the output looks like this:

OK
AT+CMGF=0

OK
AT+CUSD=1,"*114#"

OK

+CUSD: 0,"WIN BIG with MTN CHALILA on any Bundle bought using  *777
inData = #. Dial NOW!Your Account Balance is K11.3620",64

When I use an expected answer that is located earlier in the response, like "MTN", the result looks like this:

AT+CUSD=1,"*114#"

OK

+CUSD: 0,"WIN BIG with MTN
inData =

When I use an expected answer like "Balance" (which would be the right one to use) the result is this:

AT+CUSD=1,"*114#"

OK

+CUSD: 0,"WIN BIG with MTN CHALILA on any Bundle bought using  *777#. Dial NOW))))))))))

with the ))) going on into eternity.

Any advice on how I could tweak sendATcommand to do the ritgh thing?

The full result:

WIN BIG with MTN CHALILA on any Bundle
bought using *777#. Dial NOW!Your Account
Balance is K85.5280

The sketch currently:

String inData;  

void setup() {
      Serial.begin(9600);
      Serial2.begin(9600);
      CheckBalance();
}

void loop() {

}

       
 void CheckBalance()
    {
       inData = "";
       Serial2.println(F("AT+CMGF=0")); 
       delay(500);
       while(Serial2.available() != 0)
         Serial.write(Serial2.read());
       Serial2.println(F("AT+CUSD=1,\"*114#\"")); 
       delay(5000);
       while (sendATcommand(F("AT+CUSD=1,\"*114#\""), F("777"), 8000) == 0);
       while(Serial2.available() != 0)
       {
          char sim900response = Serial2.read();
          inData += sim900response;
          delay(200);       
       }                    
       Serial.print(F("inData = "));
       Serial.println(inData);
    }


  template <typename ATcommandType>int8_t sendATcommand(const ATcommandType ATcommand, const __FlashStringHelper* expected_answer, unsigned int timeout)
   {
     uint8_t x = 0, answer = 0;
     char response[100];
     unsigned long previous;
     memset(response, '\0', 100);            // initalize string
     delay(100);
     while (Serial2.available() > 0)
       Serial2.read();                        // clears the buffer
     Serial2.println(ATcommand);
     x = 0;
     previous = millis();
     const char* expected_answer_pointer = (const char PROGMEM *)expected_answer;
     do
     {
       if (Serial2.available() != 0)
       {
         response[x] = Serial2.read();
         x++;
         if (strstr_P(response, expected_answer_pointer) != NULL)
         {
           answer = 1;
         }
       }
     } 
     while ((answer == 0) && ((millis() - previous) < timeout));
     Serial.println(response);
     return answer;
   }

SOLVED: increased response[100] to [200] and the result:

AT+CUSD=1,"*114#"

OK

+CUSD: 0,"WIN BIG with MTN CHALILA on any Bundle bought using  *777#. Dial NOW!Your Account Balance is
inData =  K11.3620",64

Now I can parse the balance out of inData. Still not the ideal solution though.

Are the quotes being printed part of the response?

Have a look at the examples in Serial Input Basics - simple reliable ways to receive data.

In the examples the array size is set at 32, but you can change the value in numChars to allow for the largest message you expect to receive.

...R

PaulS:
Are the quotes being printed part of the response?

When I receive the response on my phone there are no quotation marks, but when I get the response on the serial monitor from the SIM900 then they are included. I guess that's put there by the SIM900.

Then, you have start and end markers, to know when the packet is complete.