Go Down

Topic: GPS + GSM Serial (Read 3625 times) previous topic - next topic

ross135

Jul 11, 2011, 10:32 pm Last Edit: Jul 11, 2011, 10:38 pm by ross135 Reason: 1
Hi there,

Very new to Arduino and the forum. I am having trouble with a project I am working on. I just want a simple device that when it receives an sms with a specific command, it responds to the sender with the current GPS longitude and latitude values. I am using an SM5100B and an EM408 GPS module. The SM5100B is connected to D2, D3 and the EM408 is connected to D12, D13 both via NewSoftSerial. I understand that you can't use 2 software serials at once, so I am polling each every 10 seconds. The reason I am not using the Tx and Rx pins is because I would like to use those pins for debugging/learning since this project is to help me learn I want to see "inside" what's going on. Also it is expensive to send many sms' to test :) Here is my setup and loop:

Code: [Select]

void setup()
{
 cell.begin(9600);
 gps.begin(4800);
 Serial.begin(9600);
 Serial.println("Setup Complete");
}


char c = 0;
String line = "";
String SenderNumber = "";
String SenderMessage = "";
String Data = "";
int flag = 0;

void loop()
{
if ((millis() / 500) % 2 == 0) { //poll every 10 seconds
 if (cell.available() > 0) {
   c = cell.read();            
   while ((c != '\n') && (c != '\r')) { //look for new lines from SM5100
                 line = line + c;
                 c = cell.read();
               }
               
               if (line == "+SIND: 4") {
                 cell.println("AT+CNMI=3,3,0,0");
                 delay(20);
                 cell.println("AT+CMGF=1");
                 delay(20);
                 cell.println("AT+CMGD=1,4");
                 delay(1000);
               }    
               
               if (line.substring(0,4) == "+CMT") {
                 SenderNumber = ("0" + line.substring(10,19));
                 line = "";
               }
               
               if ((line.substring(0,4) == "#LOC"))  { //#LOC is sms command from sender
                 SenderMessage = (line);
                 line = "";
                 flag = 1;
               }
             line = "";  
  }
}
 else {
   if (gps.available() > 0) {
      c = gps.read();
     while ((c != '$') && (c != '*')) { //look for GPS lines
     line += c;
      c = gps.read();
     }
     
     if (line.substring(0,5) == "GPRMC") { //only want GPRMC line to be displayed
     Serial.println(line);
   }
    line = "";              
   }
 }
}


The SM5100B code is working like I want it to, although there may be a better way to do it I am open for suggestions. I can receive the sms, save the senders number, and locate the command #LOC. The GPS code however is returning nothing. If I just print the line value without the substring filter I get gibberish aswell as pieces of the GPRMC. Probably have to redo all of this I just need some help. I am also able to sms from the SM5100B and grab the values needed from the GPRMC string so that shouldn't be a problem.

Thanks very much


PaulS

Code: [Select]
if ((millis() / 500) % 2 == 0)
{ //poll every 10 seconds

Collecting data from the phone or the GPS should not be dependent on time. Get rid of this.

Quote
I understand that you can't use 2 software serials at once, so I am polling each every 10 seconds.

No. You are polling the phone at some difficult to decipher/control interval and the GPS the rest of the time.

Quote
The GPS code however is returning nothing. If I just print the line value without the substring filter I get gibberish aswell as pieces of the GPRMC. Probably have to redo all of this I just need some help.

It's obvious, then, that you need to create a new sketch just to concentrate on getting data from the GPS. Ditch the phone until you can read from the GPS reliably.

Code: [Select]
    if (gps.available() > 0) {
      c = gps.read();
      while ((c != '$') && (c != '*')) { //look for GPS lines
      line += c;
      c = gps.read();
      }

So, if there is one or more characters available to read, and the first character is not a '$' or a '*', keep reading from the GPS (even if there is nothing to read) and appending what was read to the string, until the GPS finally does return the end of record.

I think it's clear why you are getting garbage in the record.

The art of getting good answers lies in asking good questions.

ross135

Thanks very much for your reply Paul.

I have removed the code that I thought would poll every 10 seconds. I understand it would be difficult to control. I also see now that my while statement to try find GPS lines was useless. I have taken an example off sparkfun that uses the TinyGPS library, and basically changed the variable types to global to make the data available to the GSM's loop. This is the code I have so far:

Code: [Select]

void setup()
{
  Serial.begin(115200);
  cell.begin(9600);
  uart_gps.begin(4800);
  Serial.println("Setup Complete");
}

float latitude, longitude, alt, spd, crs;
int year;
byte month, day, hour, minute, second, hundredths;
char c;
String line, SenderNumber, SenderMessage;

void loop()
{
//  while (cell.available())
//  {
//    c = cell.read();
//    Serial.println(c, BYTE);
//    delay(10);
//  }
 
  while(uart_gps.available())   
  {
      int c = uart_gps.read();   
      if(gps.encode(c))     
      {
        getgps(gps);         
        uart_gps.flush();
      }
  }
//  Serial.println();
//  Serial.print(latitude, 5);
//  Serial.print(' ');
//  Serial.println(longitude, 5);
}

void getgps(TinyGPS &gps)
{
  gps.f_get_position(&latitude, &longitude);
  gps.crack_datetime(&year,&month,&day,&hour,&minute,&second,&hundredths);
  alt = gps.f_altitude();
  crs = gps.f_course();
  spd = gps.f_speed_kmph();
}


The GPS code works and returns the values needed after it's while loop. When I add in the other while loop for the GSM data, the variables dont get populated which shows me the 'uart_gps.available()' isnt returning true. Really cluessless here, at the moment I am just doing trial and error and it's not working for me.

The GSM code I made earlier works, but as you pointed out I don't think it's the right way to do it. This while loop here works well without the GPS while loop:

Code: [Select]

if (cell.available() > 0)
{
    c = cell.read();             
    while ((c != '\n') && (c != '\r')) //look for new lines from SM5100
                {         
                  line = line + c;
                  c = cell.read();
                }
               
                if (line == "+SIND: 4")
                {
                  cell.println("AT+CNMI=3,3,0,0");
                  delay(20);
                  cell.println("AT+CMGF=1");
                  delay(20);
                  cell.println("AT+CMGD=1,4");
                  delay(1000);
                }     
               
                if (line.substring(0,4) == "+CMT")
                {
                  SenderNumber = ("0" + line.substring(10,19));
                  line = "";
                }
               
                if ((line.substring(0,4) == "#LOC")) //#LOC is sms command from sender
                {
                  SenderMessage = (line);
                  line = "";
                  int flag = 0;
                }
              line = ""; 
}


Thanks again Paul.

PaulS

On the NewSoftSerial site:
Quote
Any time you "use" an object by calling its begin(), available(), read(), or print[ln]() methods, it becomes the "active" object, and the previously active object is deactivated and its RX buffer discarded. An important point here is that object.available() always returns 0 unless object is already active. This means that you can't write code like this:

Code: [Select]
void loop()
{
  if (device1.available() > 0)
  {
    int c = device1.read();
    ...
  }
  if (device2.available() > 0)
  {
    int c = device2.read();
    ...
  }
}


This code will never do anything but activate one device after the other.


The important note here is the "and its RX buffer discarded". You can only receive data from one object at a time.

What you could do is, when the phone (message) says that it needs GPS data, activate the GPS using a throwaway call to gps.available(), then wait for the start of a new record from the GPS, then wait for the end of the message, then parse it, and then make the phone active again (cell.available() which will return 0) and send the reply.

Or, get a Mega with 4 hardware serial ports, and listen to both devices at the same time.
The art of getting good answers lies in asking good questions.

ross135

Ah crap. I guess I could just use the hardware serial port and one NSS port and not do any debugging. Sorry to ask again but do you think my While loop for checking the incoming data from the GSM modem is ok? As I said, it does work, but maybe not in the long run or efficiently?

Thanks

PaulS

Quote
do you think my While loop for checking the incoming data from the GSM modem is ok? As I said, it does work, but maybe not in the long run or efficiently?

Well, I'm surprised that it works.
Code: [Select]
if (cell.available() > 0)
{
    c = cell.read();             
    while ((c != '\n') && (c != '\r')) //look for new lines from SM5100
                {         
                  line = line + c;
                  c = cell.read();
                }

You have the same issue here, where you assume that all the serial data will arrive at once, so you do no checking that cell.read() can read a character, or that it actually did read a character.

Code: [Select]
                if (line == "+SIND: 4")
                {
                }     
               
                if (line.substring(0,4) == "+CMT")
                {
                }
               
                if ((line.substring(0,4) == "#LOC")) //#LOC is sms command from sender
                {
                }

They can't possibly all be true. Some elses in here would be useful.

Other than that, yeah, it looks OK.

Do you have some need to monitor the GPS unless the phone says to?
The art of getting good answers lies in asking good questions.

ross135

Would it be better for me to do nested if statements reading each character at a time? For example:

Code: [Select]

c = cell.read()
if (c == '+')
{
   c = cell.read()
   if (c == 'G')
   {
     c = cell.read()
     if (c == 'M')
     {
       //Assume that the phrase "+GM" was received
     }
   }
}


I have seen that method used, and to me it just seems messy. I don't know.

Ideally I just want to receive an sms, poll the gps for its current data, return the data to the senders number. The gps doesn't have to be polled continously the only reason I had it like that was because I thought that with NSS I could have 2 serial ports running seperately but not "nested" if that makes sense.

Thanks

PaulS

Quote
Would it be better for me to do nested if statements reading each character at a time?

No. Read the whole message, until the end of message marker is received, or until there is no more data, storing it in an array (or String). When the end of message marker is received, parse the message, and reset the array index to 0 (or the String object to "").

Quote
I have seen that method used, and to me it just seems messy. I don't know.

I do. It is.

Quote
Ideally I just want to receive an sms, poll the gps for its current data, return the data to the senders number. The gps doesn't have to be polled continously the only reason I had it like that was because I thought that with NSS I could have 2 serial ports running seperately but not "nested" if that makes sense.

Then the approach I outlined (pay attention to the phone until you know you need to listen to the GPS, then listen to the GPS until you get the end of a message, followed by a complete message, then parse it, then return to paying attention to the phone, and send the reply) should work.
The art of getting good answers lies in asking good questions.

ross135

#8
Jul 13, 2011, 03:24 am Last Edit: Jul 13, 2011, 03:29 am by ross135 Reason: 1
Hi Paul,

I really hope that I have it now  :smiley-eek: I have done away with TinyGPS, the code is not returning an sms but I have a feeling it's just something small. The sms is being delivered, I am not sure how to test whether my gps data has all been received before exiting that While loop. If you have some time could you please review my code and let me know. Thanks very much. Hope my indentation is OK and it's not overall confusing.

Code: [Select]

#include <NewSoftSerial.h>
#include <String.h>

NewSoftSerial cell(2,3);

String line, SenderNumber, SenderMessage, strBuffer;;
int ct = 0;
char c, buffer[128];

void setup()
{
 cell.begin(9600);
 Serial.begin(4800);
 pinMode(13, OUTPUT);
 digitalWrite(13, LOW);    
}

void loop()
{
 if (cell.available())
 {
   c = cell.read();
   if (c == '\n')
   {
     buffer[ct] = 0;
     strBuffer = buffer;
     if (strBuffer.substring(0,8) == "+SIND: 4")
     {
        cell.println("AT+CNMI=3,3,0,0");
        delay(20);
        cell.println("AT+CMGF=1");
        delay(20);
        cell.println("AT+CMGD=1,4");
        delay(1000);
        digitalWrite(13, HIGH);
     }
     
     if (strBuffer.substring(0,5) == "+CMT:")
     {
       SenderNumber = ("0" + strBuffer.substring(10,19));
     }
     
     if (strBuffer.substring(0,4) == "#LOC")
     {
     int flag = 0;  
     String LatRaw = "";
     String LatDir = "";
     String LonRaw = "";
     String LonDir = "";
     String Speed = "";
     
     Serial.flush();
     //I think this loop is the problem, not sure how else to confirm data is received.
     //Without this while in a new sketch, it gets data flawlessly from the GPS.
     while (flag != 1)
     {
         if (Serial.available())
         {
           c = Serial.read();
           if (c == '$')
           {
             buffer[ct] = 0;
             strBuffer = buffer;
             if (strBuffer.substring(0,5) == "GPRMC")
             {
               int comma1 = strBuffer.indexOf(',');
               int comma2 = strBuffer.indexOf(',', comma1 + 1);
               int comma3 = strBuffer.indexOf(',', comma2 + 1);
               int comma4 = strBuffer.indexOf(',', comma3 + 1);
               int comma5 = strBuffer.indexOf(',', comma4 + 1);
               int comma6 = strBuffer.indexOf(',', comma5 + 1);
               int comma7 = strBuffer.indexOf(',', comma6 + 1);
               int comma8 = strBuffer.indexOf(',', comma7 + 1);
               int comma9 = strBuffer.indexOf(',', comma8 + 1);
               
               LatRaw = strBuffer.substring(comma3 + 1,comma4);
               LatDir = strBuffer.substring(comma4 + 1,comma5);
               LonRaw = strBuffer.substring(comma5 + 1,comma6);
               LonDir = strBuffer.substring(comma6 + 1,comma7);
               Speed = strBuffer.substring(comma7 + 1,comma8);
               if (Speed != "")
               {
                 flag = 1;
               }
             }
            ct = 0;
          }
          else
          {
            buffer[ct] = c;
            ct += 1;
           
            if (ct >= sizeof(buffer))
            {
              ct = 0;
            }
          }
         }
     }
       SenderMessage = "LONG:";
       SenderMessage += LonRaw;
       SenderMessage += " LAT:";
       SenderMessage += LatRaw;
       SenderMessage += " SPD:";
       SenderMessage += Speed;
       SenderMessage += "Km/h";    
       sms(SenderNumber, SenderMessage);
       cell.println("AT+CMGD=1,4");
       delay(1000);
     }
    ct = 0;
  }
  else
  {
    buffer[ct] = c;
    ct += 1;
    if (ct >= sizeof(buffer))
    {
      ct = 0;
    }
  }
 }
}

void sms(String number, String message)
{
 cell.println("AT+CMGF=1");
 cell.print("AT+CMGS=");
 cell.print(34, BYTE);
 cell.print(number);
 cell.println(34, BYTE);
 delay(500);
 cell.print(message);
 cell.println(26,BYTE);
 delay(15000);
}


EDIT: About those if statements in your previous post, they didn't have any elses on them because I was only testing for those 2 or 3 results. The rest of the results were to be discarded so I wouldn't have needed to handle them.

PaulS

Quote
EDIT: About those if statements in your previous post, they didn't have any elses on them because I was only testing for those 2 or 3 results. The rest of the results were to be discarded so I wouldn't have needed to handle them.

I didn't mean that each if needed an else clause. What I meant was that you should have had:
Code: [Select]
                if (line == "+SIND: 4")
                {
                }     
               
                else if (line.substring(0,4) == "+CMT")
                {
                }
               
                else if ((line.substring(0,4) == "#LOC")) //#LOC is sms command from sender
                {
                }


Now, for today's code:
Code: [Select]
      Serial.flush();
So, you got an SMS requesting location data, so you go arbitrarily throw away a random amount of GPS data. Why?

With the GPS connected to the hardware serial port, you have two independent, non-conflicting sources of data. You should have a loop that looks like:
Code: [Select]
void loop()
{
  while(cell.available() > 0)
  {
     // Process all pending SMS data
  }
  while(Serial.available() > 0)
  {
     // Process all pending GPS data
  }
}


For now, create two sketches. In one sketch, write the code to read the GPS data using NewSoftSerial, and debug using Serial. Name the NewSoftSerial instance gpsSerial, so that it is easy to remove the debug stuff, then rename gpsSerial to Serial.

In the other sketch, write the code to receive and parse the SMS data, using NewSoftSerial, and return bogus lat/lon data, using Serial to output debug messages.

When each sketch works correctly, merge the two so that the lat/lon data returned is not bogus.

Quote
I have done away with TinyGPS

Why? I really am curious? There was a lot of effort put into that library, to make collecting data from a GPS easy. Why re-invent the wheel?

Quote
if (c == '$')
            {
              buffer[ct] = 0;
              strBuffer = buffer;
              if (strBuffer.substring(0,5) == "GPRMC")

Does the GPS sentence start with, or end with, a $. I thought it started with a $.

Code: [Select]
                int comma1 = strBuffer.indexOf(',');
                int comma2 = strBuffer.indexOf(',', comma1 + 1);
                int comma3 = strBuffer.indexOf(',', comma2 + 1);
                int comma4 = strBuffer.indexOf(',', comma3 + 1);
                int comma5 = strBuffer.indexOf(',', comma4 + 1);
                int comma6 = strBuffer.indexOf(',', comma5 + 1);
                int comma7 = strBuffer.indexOf(',', comma6 + 1);
                int comma8 = strBuffer.indexOf(',', comma7 + 1);
                int comma9 = strBuffer.indexOf(',', comma8 + 1);
               
                LatRaw = strBuffer.substring(comma3 + 1,comma4);
                LatDir = strBuffer.substring(comma4 + 1,comma5);

It's important to test that commaN represents a valid value, before using it as an argument in the substring() call.
The art of getting good answers lies in asking good questions.

Go Up