Reading data from A9G

I am using Mega2560 with A9G and I can set any AT command through Serial1 with Serial read/write sketch:

while (Serial.available()) {
    Serial1.write(Serial.read());
  }
  while(Serial1.available()) {
    Serial.write(Serial1.read());
  }

When typing AT+LOCATION=2, I get my data. Data is in the format: 1234.1234,5678.5678 if that is important.
However, when I type:

    Serial1.write("AT+LOCATION=2");
  while(Serial1.available()) {
    Serial.write(Serial1.read());
  }

I get no output. I do realize I don't do it as I should, but I can't see what is wrong. At the very end, I need those data to put on a display, so they should be put in some variable. Say gpsData, or similar.

Thanks in advance.

how is the serial monitor configured? Does it use line ending?
If yes does it use new line or carriage return or both?
Then adding a "/n" at the end might help of your write

best regards Stefan

the issue is that after you sent this command    Serial1.write("AT+LOCATION=2");the hardware Serial component is transferring each byte at the baud rate you used for Serial1 in the background through interruptions. This is usually slow compared to the speed of your arduino. So after sending the command you check right away if there is already a response back

  while(Serial1.available()) {
    Serial.write(Serial1.read());
  }

and you should not be surprised to not see anything, Serial1.available() is likely 0 because the full command did not get a chance to reach your module and the module did not get a chance to answer.

A crappy way to do it would be to ensure the command has reached the module, then give enough time to the module to answer, and then check for the answer.

   Serial1.write("AT+LOCATION=2");
   Serial1.flush(); // ensure the command is sent
   delay(1000); // give the module one second to provide an answer
   while(Serial1.available()) {
     Serial.write(Serial1.read());
  }

that's still bogus because if the answer is longer than 64 bytes and comes in before the full second wait has elapsed, the Rx buffer will have overflown and you'll miss part of the answer or if the answer is not totally in, the while() will probably empty the buffer quickly, available() will report 0 (even if some data is still coming because it's not available yet) and you'll miss the end of the answer.

So what you need to remember is that you should never try to second guess the timing of asynchronous protocols. I would suggest to study Serial Input Basics to handle this

    Serial1.println("AT+LOCATION=2");
    delay(1000);
    while(Serial1.available()>0){
    String cstring = Serial1.readString();
    Serial.println(cstring);
    delay(1000);

This is the way it works. I will try flush()

Anyway, the next problem is to get only the data. Right now I have this as an output:

AT+LOCATION=2

12.345678,12.345678

OK

Now, I can turn Echo off, but I need it for checking if other commands pass through. The first solution is to turn it off and after I get the data, turn it on. Or to trim() it? Or any other suggestion...

flush() is ok. :wink:

who_took_my_nick:

    Serial1.println("AT+LOCATION=2");

delay(1000);
    while(Serial1.available()>0){
    String cstring = Serial1.readString();
    Serial.println(cstring);
    delay(1000);




This is the way it works. I will try flush()

well - this is still crappy and second guessing timing :slight_smile:

the right way to do it is to wait for and read the answer until a specific content comes in, like an empty line, a OK, etc or may be a longer timeout occurs (in which case you need to deal with the error)

:slight_smile:

Well, there is a function I will use to check that OK so I know I get the data. But, still have to trim that same OK.

really - I would suggest to study Serial Input Basics to handle this

I read it but didn't figure out how to remove everything after the GPS data. That OK is making me nuts. :slight_smile:

you send a command, then you wait for

  • receiving the full answer (track for "OK\r\n" for example)
  • a timeout

the "you wait for" should be done the way described in the tutorial, but instead of checking for a character based end marker, you need to wait for a small text (like "OK\r\n"). So as the reader accumulates data in a buffer, you need to check if the end of the buffer matches the small text

If you read French you can explore my small tutorial on that topic : Gestion de commandes sur port Série. I offer a class there that will let you send commands to a Stream (think Serial port) and then wait until some sort of keyword is sent back as acknowledgement or timeout occurs.

use "google translate" to translate my tutorial, the code itself for the StreamDevice class is "English friendly".

you could use it like this

#include <StreamDevice.h>
StreamDevice A9G(Serial1);

void setup() {
  Serial.begin(115200);
  Serial1.begin(115200); // assuming you've set it at that baud rate

  // synchronous example
  A9G.println("AT+LOCATION=2");
  A9G.endCommand("OK\r\n", 5000ul);      // Once done, tell the A9G that the command is done (wait default to 1s, you can add the timeout as a parameter - herer 5s)

  if (A9G.awaitKeyword() == KEYWORD_OK)  // this is blocking until timeout or correct answer whichever comes first
    Serial.println(F("Got the expected 'OK' text"));
  else
    Serial.println(F("Damned - timed out before getting the expected 'OK'"));
}

void loop() {}

basically declare your A9G device on the Serial1 port
then you send commands to A9G the same way you would do with print, write, println etc
once done sending the command, you notify A9G that the command is complete with A9G.endCommand() and you pass the text you will be looking for (here "OK\r\n") and optionally how long to wait for the answer before timing-out (default is 1s).

then you can do a blocking call with A9G.awaitKeyword() and you'll get KEYWORD_OK if "OK\r\n" has been received otherwise it means the time out kicked in.

hope it helps

I don't know a single word of French. My bad. But, there is almighty Google.

I read your article, and it is nicely written. I hope the translation didn't change a lot of it. And I said that as a long time reviewer in a computer magazine, with hundreds of articles. :slight_smile:

Back to the topic. Your example is pretty nice, except there is nothing as I can see that will return the incoming data without following new line and OK. There is:

fakeDevice.endCommand("Hi", 5000ul, tempBuffer, 100);

Here, I wait for Hi, and return the data. But how to return Hi and not the following? Beside, here you wait for a known word, in my case, GPS will send unkown data. This part is not clear to me. I am waiting for numbers, dots and commas. This is what I expect (20.45847,40.2569). Data is fake, not true position of me. :slight_smile:
Anything after that should be ignored.

In any case, here is the sketch I am up to till now. With added lines you suggested here.

#include <StreamDevice.h>
StreamDevice A9G(Serial1);
int bID = 1;
int delayTime = 10000;
int gpsData;
void setup()
{
Serial.begin(9600);
Serial1.begin(9600);
initGSM();
initGPS();
initGPRS();
connectGSM("ATE0","OK");
}
void loop(){
  //connectGPS();

  A9G.println("AT+LOCATION=2");
  A9G.endCommand("OK\r\n", 5000ul);      // Once done, tell the A9G that the command is done (wait default to 1s, you can add the timeout as a parameter - herer 5s)

  if (A9G.awaitKeyword() == KEYWORD_OK)  // this is blocking until timeout or correct answer whichever comes first
    Serial.println(F("Got the expected 'OK' text"));
    // here should be printed the GPS data only, if I am right
  else
    Serial.println(F("Damned - timed out before getting the expected 'OK'"));

  
  delay(5000);

  
}
void initGPS(){
  connectGSM("AT+GPS=1","OK");
}
void initGSM(){
  connectGSM("AT","OK");
  connectGSM("ATE1","OK");
  connectGSM("AT+CPIN?","READY");
}
void initGPRS(){
  connectGSM("AT+CREG=0","OK");
  connectGSM("AT+CREG?","0,1");
  connectGSM("AT+CIPSTATUS?","IP INITIAL");
  connectGSM("AT+CGDCONT=1,\"IP\",\"apn\"","OK");
  connectGSM("AT+CGATT=1","OK");
  connectGSM("AT+CGACT=1,1","OK");
  delay(1000);
}
void connectGPS (){
    Serial1.println("AT+LOCATION=2");
    Serial1.flush();
    while(Serial1.available()>0){
    String cstring = Serial1.readStringUntil('\r');
    Serial.println(cstring);
    delay(1000);
    }
 }
void connectGSM (String cmd, char *res){
  while(1){
    Serial.println(cmd);
    Serial1.println(cmd);
    delay(500);
    while(Serial1.available()>0){
      if(Serial1.find(res)){
        Serial.println(res);
        delay(1000);
        return;
      }
    }
    delay(1000);
   }
 }

Serial is 9600, it is the only one that works. Not sure why.

Anyway, I think you clearly understand the lines above.

Beside, here you wait for a known word, in my case, GPS will send unkown data.

let's start with the beginning: what's the real answer you get?

My understanding is that sending AT+LOCATION=2 returns something formatted as

<latitude>,<longitude>[color=red]\r\nOK\r\n[/color]

so if you send AT+LOCATION=2 with my library and get the tempBuffer back until the trailing "\r\nOK\r\n" for example you can then parse with atof() the latitude and longitude in the buffer you got back.

why wouldn't that work?

you could also replace your connectGSM() function (won't work all the time) with my library

I gave it a try based on the code you posted. Fully untested. What do you see in your console (opened at 115200 bauds) if you run this:

#include <StreamDevice.h>
StreamDevice A9G(Serial1);

char responseBuffer[100];

bool blockingCommandWithTimeout(const char* command, const char* response, uint32_t timeOut = 1000)
{
  A9G.println(command);
  A9G.endCommand(response, timeOut);
  if (A9G.awaitKeyword() == KEYWORD_OK)  // this is blocking until timeout or correct answer whichever comes first
    return true;
  else
    return false;
}

void requestLocation()
{
  // assumes we get back <latitude>,<longitude>\r\nOK\r\n
  A9G.println("AT+LOCATION=2");
  A9G.endCommand("\r\nOK\r\n", 10000ul, responseBuffer, 100);
}

bool initA9G()
{
  bool systemIsOk = true;
  if (systemIsOk) systemIsOk = blockingCommandWithTimeout("AT", "OK");
  if (systemIsOk) systemIsOk = blockingCommandWithTimeout("ATE1", "OK");
  if (systemIsOk) systemIsOk = blockingCommandWithTimeout("AT+CPIN?", "READY");
  if (systemIsOk) systemIsOk = blockingCommandWithTimeout("AT+GPS=1", "OK");
  if (systemIsOk) systemIsOk = blockingCommandWithTimeout("AT+CREG=0", "OK");
  if (systemIsOk) systemIsOk = blockingCommandWithTimeout("AT+CREG?", "0,1");
  if (systemIsOk) systemIsOk = blockingCommandWithTimeout("AT+CIPSTATUS?", "IP INITIAL");
  if (systemIsOk) systemIsOk = blockingCommandWithTimeout("AT+CGDCONT=1,\"IP\",\"apn\"", "OK");
  if (systemIsOk) systemIsOk = blockingCommandWithTimeout("AT+CGATT=1", "OK");
  if (systemIsOk) systemIsOk = blockingCommandWithTimeout("AT+CGACT=1,1", "OK");
  return systemIsOk;
}



void setup() {
  Serial.begin(115200);
  Serial1.begin(9600);
  if (! initA9G()) {
    Serial.println(F("A9G Initialisation error."));
    while (true); // die here
  }
  // send a command to request location, will wait for the asnwer in the loop
  requestLocation();
}


void loop() {
  static bool awaitingAnswer = true;

  if (awaitingAnswer)
    switch (A9G.answerStatus()) { // non blocking
      case KEYWORD_OK:
        Serial.print(F("GOT LOCATION. Buffer is ["));
        Serial.print(responseBuffer);
        Serial.println(F("]"));
        // look at what is in the buffer, you can parse it with cString functions such as atof()

        // we ask our location again after a 1s delay
        delay(1000);
        requestLocation();
        break;

      case KEYWORD_TIMEOUT:
        Serial.println(F("Did not get Location. Not asking again"));
        awaitingAnswer = false;
        break;

      case KEYWORD_PENDING: break;
    }

  // Here you can do something else
}

PS/ thanks for the kind words on the tutorial

It is not working at any other baud rate, except on 9600, both Serials. It just doesn't work. Not sure why. I tried every single combination.
The output is:

GOT LOCATION. Buffer is [AT+LOCATION=2

44.591675,20.374860

OK
]

atof(responseBuffer);

This returns 0.00

Btw, I really appreciate your effort.

who_took_my_nick:
It is not working at any other baud rate, except on 9600, both Serials. It just doesn't work.

I get that the module can be set at 9600, that's OK - but there is no reason Serial could not be at 115200, as long as you open your Serial console at 115200 too.... then you should see what's going on.

So we get what's the answer:

[color=blue]AT+LOCATION=2\r\n\r\n44.591675,20.374860\r\n\r\nOK\r\n[/color]

may be the \r\n are just \n

The reason why you can't do

atof(responseBuffer);

it's because at the start of the buffer you have [color=blue]AT+LOCATION=2\r\n\r\n[/color]before the data.

so you need to either remove the echo from the module so that you don't get this, or start the atof() parsing right after the second '\n' (assuming there are 2 since there was an empty line in what you showed)

here is a simple code demonstrating this, it's not rock solid but does double check a few things before reading the values

char responseBuffer[100] = "AT+LOCATION=2\r\n\r\n44.591675,20.374860\r\n\r\nOK\r\n";

void setup() {
  Serial.begin(115200);

  float latitude = NAN, longitude = NAN; // initialized with "Not a Number"

  // if the response starts with "AT+LOCATION=2" which is 13 characters
  if ( ! strncmp(responseBuffer, "AT+LOCATION=2", 13)) { // http://www.cplusplus.com/reference/cstring/strncmp
    // seems we have 2 '\n', find the second one
    char* newLinePointer = strchr(responseBuffer, '\n'); // see http://www.cplusplus.com/reference/cstring/strchr/
    if (newLinePointer != NULL) { // we got the first one, find the next one
      newLinePointer = strchr(newLinePointer + 1, '\n');
      if (newLinePointer != NULL) {
        // we got the second new line, we can parse the latitude
        latitude = atof(newLinePointer + 1);

        // now we need to skip to the comma to find the longitude
        newLinePointer = strchr(newLinePointer, ',');
        if (newLinePointer)
          longitude = atof(newLinePointer + 1);

        Serial.print(F("Latitude is "));
        if (isnan(latitude))
          Serial.println(F("unknown"));
        else
          Serial.println(latitude, 6); // print with 6 digits after the decimal point

        Serial.print(F("Longitude is "));
        if (isnan(longitude))
          Serial.println(F("unknown"));
        else
          Serial.println(longitude, 6); // print with 6 digits after the decimal point
      }
    }
  }
}

void loop() {}

Serial Monitor (@ 115200 bauds) will show

[color=purple]
Latitude is 44.591674
Longitude is 20.374858
[/color]

Note that the values are not exactly what you received (change at last digit), that's because float are single precision.

Sorry for the late reply.

First of all, Happy New Year and big THANK you!

I was busy these days so haven't even tried your sketch you provide. Everything works like a charm. Didn't tweak anything, just pop-up these few lines into the existing one, and it works as it should. Here is the whole thing, for you to say a word or two. And of course for the community. Definitely, some will find this to be important.

So here it is:

#include <StreamDevice.h>
StreamDevice A9G(Serial1);

char responseBuffer[100];
float latitude = NAN, longitude = NAN;

bool blockingCommandWithTimeout(const char* command, const char* response, uint32_t timeOut = 1000)
{
  A9G.println(command);
  A9G.endCommand(response, timeOut);
  if (A9G.awaitKeyword() == KEYWORD_OK)  // this is blocking until timeout or correct answer whichever comes first
    return true;
  else
    return false;
}

void requestLocation()
{
  // assumes we get back <latitude>,<longitude>\r\nOK\r\n
  A9G.println("AT+LOCATION=2");
  A9G.endCommand("\r\nOK\r\n", 10000ul, responseBuffer, 100);
}

bool initA9G()
{
  bool systemIsOk = true;
  if (systemIsOk) systemIsOk |= blockingCommandWithTimeout("AT", "OK");
  if (systemIsOk) systemIsOk |= blockingCommandWithTimeout("ATE1", "OK");
  if (systemIsOk) systemIsOk |= blockingCommandWithTimeout("AT+CPIN?", "READY");
  if (systemIsOk) systemIsOk |= blockingCommandWithTimeout("AT+GPS=1", "OK");
  if (systemIsOk) systemIsOk |= blockingCommandWithTimeout("AT+CREG=0", "OK");
  if (systemIsOk) systemIsOk |= blockingCommandWithTimeout("AT+CREG?", "0,1");
  if (systemIsOk) systemIsOk |= blockingCommandWithTimeout("AT+CIPSTATUS?", "IP INITIAL");
  if (systemIsOk) systemIsOk |= blockingCommandWithTimeout("AT+CGDCONT=1,\"IP\",\"apn\"", "OK");
  if (systemIsOk) systemIsOk |= blockingCommandWithTimeout("AT+CGATT=1", "OK");
  if (systemIsOk) systemIsOk |= blockingCommandWithTimeout("AT+CGACT=1,1", "OK");
  return systemIsOk;
}



void setup() {
  Serial.begin(9600);
  Serial1.begin(9600);
  if (! initA9G()) {
    Serial.println(F("A9G Initialisation error."));
    while (true); // die here
  }
  // send a command to request location, will wait for the asnwer in the loop
  requestLocation();
}


void loop() {
  static bool awaitingAnswer = true;

  if (awaitingAnswer)
    switch (A9G.answerStatus()) { // non blocking
      case KEYWORD_OK:
        Serial.print(F("GOT LOCATION. Buffer is ["));
        Serial.print(responseBuffer);
        Serial.println(F("]"));
        // look at what is in the buffer, you can parse it with cString functions such as atof()
        test();
        // we ask our location again after a 1s delay
        delay(1000);
        requestLocation();
        break;

      case KEYWORD_TIMEOUT:
        Serial.println(F("Did not get Location. Not asking again"));
        awaitingAnswer = false;
        break;

      case KEYWORD_PENDING: break;
    }

  // Here you can do something else
}
void test(){
    if ( ! strncmp(responseBuffer, "AT+LOCATION=2", 13)) { // http://www.cplusplus.com/reference/cstring/strncmp
    // seems we have 2 '\n', find the second one
    char* newLinePointer = strchr(responseBuffer, '\n'); // see http://www.cplusplus.com/reference/cstring/strchr/
    if (newLinePointer != NULL) { // we got the first one, find the next one
      newLinePointer = strchr(newLinePointer + 1, '\n');
      if (newLinePointer != NULL) {
        // we got the second new line, we can parse the latitude
        latitude = atof(newLinePointer + 1);

        // now we need to skip to the comma to find the longitude
        newLinePointer = strchr(newLinePointer, ',');
        if (newLinePointer)
          longitude = atof(newLinePointer + 1);

        Serial.print(F("Latitude is "));
        if (isnan(latitude))
          Serial.println(F("unknown"));
        else
          Serial.println(latitude, 6); // print with 6 digits after the decimal point

        Serial.print(F("Longitude is "));
        if (isnan(longitude))
          Serial.println(F("unknown"));
        else
          Serial.println(longitude, 6); // print with 6 digits after the decimal point
      }
    }
  }  
}

As always, the easiest way is to bring the solution to some, but the right thing is to make someone get the solution by himself. By helping him.

The next thing is to send the data to a server. When completed, it will be published here. If any problem, it will be here as well. :slight_smile:

Happy new year! Glad it worked out for you.

a few comments:

You should definitely set the Serial Console at 115200 or more. There is no need to go slow when discussing with your computer. Change Serial.begin(9600); into Serial.begin(115200); and set the Serial Monitor at 115200 bauds. that's all it takes.

in the initA9G() function, the OR ([color=red]|[/color]) in
if (systemIsOk) systemIsOk [color=red]|[/color]= ...
is not necessary as we test at every step the returned value. you can just go for

if (systemIsOk) systemIsOk = ...

ideally a some sort of counter could get incremented so that if something fails you would know which initial command did not succeed.

The system as coded will stop requesting a new location as soon as one request fails. This should probably be made more fault tolerant and just retry a few times before giving up.

thanks for sharing back with the community, I'm sure that will help others along the way.

As I didn't get the whole thing pretty clear, I don't know what should be changed when there is no output. For some reason after a dozen good readings, comes one without any. That triggers KEYWORD_TIMEOUT case and loop don't go again with KEYWORD_OK case. Or there is no such case? Which is a problem. The blue LED is lit at the requested interval on the A9G.

When I comment on that part (KEYWORD_TIMEOUT) it seems to work ok, but then there might be a time without any data. At the warming at the beginning, for instance. There is a delay in the cold start. The time after AT+GPS=1 command and connecting to the satellites and, getting the data can be half a minute.

Right now in the warm start, it works like a charm. But, there is but...

OK, issue nr1

Getting the data by commenting out (KEYWORD_TIMEOUT) gets the data. Those data should be sent back via Serial1 through AT+HTTPGET to a server.

If I put, say:
Serial1.println((String)"AT+HTTPGET="http://www.google.com"");
right instead:
// Here you can do something else
I get a buffer full of wrong data

GOT LOCATION. Buffer is [HTTP/1.0 404 Not Found
Content-Type: text/html; charset=UTF-192dpi){#logo{background:url(//www.g]
GOT LOCATION. Buffer is [AT+HTTPGET="http://www.google.com"

OK
]

My guess is to make another case?

As I believe your way might be the best one here, it is a little above my understanding.

Anyway, I made it. Not as elegant as it should be.

I stop using the TX1/RX1 from the A9G for both purposes (GSM, GPS). Instead, I get the GPS NMEA data from the GPS_RX/GPS_TX through Serial3 on Mega. The TinyGpsPlus library came in handy. Collected data I simply sent back to a GSM modem and on the server.

Here are my 50 cents in case someone needs it. Or someone can improve it. If someone improves it, please share.

#include <TinyGPS++.h>
TinyGPSPlus tinyGPS;
#define GPSBaud 9600
#define gpsPort Serial3
int bID = 1;
#define LOG_RATE 10000
unsigned long lastLog = 0;
void setup(){
Serial.begin(115200);
Serial1.begin(9600);
Serial3.begin(GPSBaud);
initGSM();
initGPS();
initGPRS();
connectGSM("ATE0","OK");
}
void loop(){
  connectGPS();
  smartDelay(1000);
}
void initGPS(){
  connectGSM("AT+GPS=1","OK");
}
void initGSM(){
  connectGSM("AT","OK");
  connectGSM("ATE1","OK");
  connectGSM("AT+CPIN?","READY");
}
void initGPRS(){
  connectGSM("AT+CREG=0","OK");
  connectGSM("AT+CREG?","0,1");
  connectGSM("AT+CIPSTATUS?","IP INITIAL");
  connectGSM("AT+CGDCONT=1,\"IP\",\"apn\"","OK");
  connectGSM("AT+CGATT=1","OK");
  connectGSM("AT+CGACT=1,1","OK");
  delay(1000);
}
void sendHTTPGET (){
  double gpsLng = tinyGPS.location.lng();
  long lngGps = gpsLng*1000000;
  double gpsLat = tinyGPS.location.lat();
  long latGps = gpsLat*1000000;
  float spd = tinyGPS.speed.kmph();
  int sat = tinyGPS.satellites.value();
  Serial.println(lngGps);
  Serial1.print("AT+HTTPGET=\"http://www.somesite.com/g.php?i=");
  Serial1.print(bID);
  Serial1.print("&lng=");
  Serial1.print(lngGps);
  Serial1.print("&lat=");
  Serial1.print(latGps);
  Serial1.print("&spd=");
  Serial1.print(spd);
  Serial1.print("&sat=");
  Serial1.print(sat);
  Serial1.print("\"");
  Serial1.println();
}
void connectGPS (){
  if ((lastLog + LOG_RATE) <= millis()){
  if (tinyGPS.location.isUpdated()){
   sendHTTPGET();
  }
  }
 }
static void smartDelay(unsigned long ms){
  unsigned long start = millis();
  do{
    // If data has come in from the GPS module
    while (gpsPort.available())
      tinyGPS.encode(gpsPort.read());
  } while (millis() - start < ms);
}

void connectGSM (String cmd, char *res){
  while(1){
    Serial.println(cmd);
    Serial1.println(cmd);
    delay(500);
    while(Serial1.available()>0){
      if(Serial1.find(res)){
        Serial.println(res);
        delay(1000);
        return;
      }
    }
    delay(1000);
   }
 }