Parse GNSS -NMEA string

Hi there i made a code to parse a GNSS - NMEA string but at the moment i have troubles because strtok seems to sometimes miss the character and thus, doesn’t split the string correctly.

How can i improve this code?

Serial print is only for debug uses…

#include <SoftwareSerial.h>

char frame[100];
char GNSSrunstatus[1];
char Fixstatus[1];
char UTCdatetime[18];
char latitude[10];
char logitude[11];
char altitude[8];
char speedOTG[6];
char course[6];
char fixmode[1];
char HDOP[4];
char PDOP[4];
char VDOP[4];
char satellitesinview[2];
char GNSSsatellitesused[2];
char GLONASSsatellitesused[2];
char cn0max[2];
char HPA[6];
char VPA[6];

boolean state;
double serialnr;

SoftwareSerial mySerial(10, 11); // RX, TX

void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }


  Serial.println("Alles OK");

  // set the data rate for the SoftwareSerial port
  mySerial.begin(9600);
  mySerial.println("AT");
  mySerial.println("AT+CGNSPWR=1");
}

void loop() { // run over and over

  get_GPS();
  
}

int8_t get_GPS() {

  int8_t counter, answer;
  long previous;

  // First get the NMEA string
  // Clean the input buffer
  while ( mySerial.available() > 0) mySerial.read();
  // request Basic string
  mySerial.println("AT+CGNSINF"); //sendATcommand("AT+CGNSINF", "AT+CGNSINF\r\n\r\n", 2000);

  counter = 0;
  answer = 0;
  memset(frame, '\0', sizeof(frame));    // Initialize the string
  previous = millis();
  // this loop waits for the NMEA string
  do {

    if (mySerial.available() != 0) {
      frame[counter] = mySerial.read();
      counter++;
      // check if the desired answer is in the response of the module
      if (strstr(frame, "OK") != NULL)
      {
        answer = 1;
      }
    }
    // Waits for the asnwer with time out
  }
  while ((answer == 0) && ((millis() - previous) < 2000));

  frame[counter - 3] = '\0';

  // Parses the string
  strtok(frame, ",");
  strcpy(Fixstatus, strtok(NULL, ",")); // Gets GNSS run status
  strcpy(UTCdatetime, strtok(NULL, ",")); // Gets Fix status
  strcpy(latitude, strtok(NULL, ",")); // Gets UTC date & time
  strcpy(logitude, strtok(NULL, ",")); // Gets longitude
  strcpy(altitude, strtok(NULL, ",")); // Gets MSL altitude
  strcpy(speedOTG, strtok(NULL, ",")); // Gets speed over ground
  strcpy(course, strtok(NULL, ",")); // Gets course over ground
  strcpy(fixmode, strtok(NULL, ",")); // Gets Fix Mode
  strtok(NULL, ",");
  strcpy(HDOP, strtok(NULL, ",")); // Gets HDOP
  strcpy(PDOP, strtok(NULL, ",")); // Gets PDOP
  strcpy(VDOP, strtok(NULL, ",")); // Gets VDOP
  strtok(NULL, ",");
  strcpy(satellitesinview, strtok(NULL, ",")); // Gets GNSS Satellites in View
  strcpy(GNSSsatellitesused, strtok(NULL, ",")); // Gets GNSS Satellites used
  strcpy(GLONASSsatellitesused, strtok(NULL, ",")); // Gets GLONASS Satellites used
  strtok(NULL, ",");
  strcpy(cn0max, strtok(NULL, ",")); // Gets C/N0 max
  strcpy(HPA, strtok(NULL, ",")); // Gets HPA
  strcpy(VPA, strtok(NULL, "\r")); // Gets VPA

Serial.println("Fixstatus");
  Serial.println(Fixstatus);
Serial.println("UTCdatetime");
  Serial.println(UTCdatetime);
Serial.println("latitude");  
  Serial.println(latitude);
Serial.println("logitude");
  Serial.println(logitude);
Serial.println("altitude");
  Serial.println(altitude);
Serial.println("speedOTG");
  Serial.println(speedOTG);
Serial.println("course");
  Serial.println(course);
Serial.println("fixmode");
  Serial.println(fixmode);
Serial.println("HDOP");
  Serial.println(HDOP);
Serial.println("PDOP");
  Serial.println(PDOP);
Serial.println("VDOP");
  Serial.println(VDOP);
Serial.println("satellitesinview");
  Serial.println(satellitesinview);
Serial.println("GNSSsatellitesused");
  Serial.println(GNSSsatellitesused);
Serial.println("GLONASSsatellitesused");
  Serial.println(GLONASSsatellitesused);
Serial.println("cn0max");
  Serial.println(cn0max);
Serial.println("HPA");
  Serial.println(HPA);
Serial.println("VPA");
  Serial.println(VPA);
  
  return answer;
}

String without parse:

Alles OK
AT

OK
AT+CGNSPWR=1

OK
AT+CGNSINF

+CGNSINF: 1,1,20160501124254.000,47.199897,9.442750,473.500,0.35,36.8,1,1.1,1.9,1.6,13,7,39,

OK

Output - (not every time):

Fixstatus

UTCdatetime
20160501124554.000
latitude
47.199923
logitude
9.442710
altitude
474.100
speedOTG
0.37
course

fixmode
1
HDOP
1.3
PDOP
0.8
VDOP
?>0

the strange thing is also i basically never get the Fixstatus.
The same for the GNSSrunstatus, thats the reason why i started with Serial.println(Fixstatus) and strcpy(Fixstatus).

In addition the manual for the GNSS NEMA string (Page 9-10)
Manual

It looks like you are missing one element of the frame, at least from your comments:

strtok(frame, ",");
  strcpy(Fixstatus, strtok(NULL, ",")); // Gets GNSS run status  <<<< you don't get <GNSS run status> ?
  strcpy(UTCdatetime, strtok(NULL, ",")); // Gets Fix status   <<<<<<< your comments don't match your code?
  strcpy(latitude, strtok(NULL, ",")); // Gets UTC date & time
  strcpy(logitude, strtok(NULL, ",")); // Gets longitude
  strcpy(altitude, strtok(NULL, ",")); // Gets MSL altitude
  strcpy(speedOTG, strtok(NULL, ",")); // Gets speed over ground
  strcpy(course, strtok(NULL, ",")); // Gets course over ground

Oh sorry you are right with the comments. As posted before, I deleted GNSS run status because I only got the Fix status back and the structure was shifted by 1.

Now I have only the Fix status at the beginning but it doesn't come back as "1" , only the next value UTCdatetime comes back but the structure matches the values.

Try this modification, I just put this string into the terminal:

+CGNSINF: 1,1,20160501124254.000,47.199897,9.442750,473.500,0.35,36.8,1,,1.1,1.9,1.6,,13,7,,,39,, OK

Note that I showed you how to parse the data into useful numbers instead of char arrays… for a few

code:

#include <SoftwareSerial.h>

char frame[256];
byte GNSSrunstatus;  //<<<<<<< was char array
byte Fixstatus;           //<<<<<<< was char array
char UTCdatetime[15];
char latitude[10];
char logitude[11];
char altitude[8];
char speedOTG[6];
char course[6];
char fixmode[1];
char HDOP[4];
char PDOP[4];
char VDOP[4];
char satellitesinview[2];
char GNSSsatellitesused[2];
char GLONASSsatellitesused[2];
char cn0max[2];
char HPA[6];
char VPA[6];

boolean state;
double serialnr;

SoftwareSerial mySerial(10, 11); // RX, TX

void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }


  Serial.println("Alles OK");

  // set the data rate for the SoftwareSerial port
  mySerial.begin(9600);
  mySerial.println("AT");
  mySerial.println("AT+CGNSPWR=1");
}

void loop() 
{
  if(Serial.available())
  {
    get_GPS();
  }
}

int8_t get_GPS() 
{
  int8_t counter, answer;
  long previous;
  //while ( mySerial.available() > 0) mySerial.read();
  // request Basic string
  //Serial.println("AT+CGNSINF"); //sendATcommand("AT+CGNSINF", "AT+CGNSINF\r\n\r\n", 2000);

  counter = 0;
  answer = 0;
  memset(frame, '\0', sizeof(frame));    // Initialize the string
  previous = millis();
  // this loop waits for the NMEA string
  do {

    if (Serial.available() != 0) {
      frame[counter] = Serial.read();
      counter++;
      // check if the desired answer is in the response of the module
      if (strstr(frame, "OK") != NULL)
      {
        answer = 1;
      }
    }
    // Waits for the asnwer with time out
  }
  while ((answer == 0) && ((millis() - previous) < 2000));

  frame[counter - 3] = '\0';

  // Parses the string
  strtok(frame, ": ");
  GNSSrunstatus = atoi(strtok(NULL, ","));//Gets GNSS run status
  Fixstatus = atoi(strtok(NULL, ",")); // Gets Fix status
  strcpy(UTCdatetime, strtok(NULL, ".")); // Gets UTC date & time
  strtok(NULL,",");  // skip three zeroes
  strcpy(latitude, strtok(NULL, ",")); // 
  strcpy(logitude, strtok(NULL, ",")); // Gets longitude
  strcpy(altitude, strtok(NULL, ",")); // Gets MSL altitude
  strcpy(speedOTG, strtok(NULL, ",")); // Gets speed over ground
  strcpy(course, strtok(NULL, ",")); // Gets course over ground
  strcpy(fixmode, strtok(NULL, ",")); // Gets Fix Mode
  strtok(NULL, ",");
  strcpy(HDOP, strtok(NULL, ",")); // Gets HDOP
  strcpy(PDOP, strtok(NULL, ",")); // Gets PDOP
  strcpy(VDOP, strtok(NULL, ",")); // Gets VDOP
  strtok(NULL, ",");
  strcpy(satellitesinview, strtok(NULL, ",")); // Gets GNSS Satellites in View
  strcpy(GNSSsatellitesused, strtok(NULL, ",")); // Gets GNSS Satellites used
  strcpy(GLONASSsatellitesused, strtok(NULL, ",")); // Gets GLONASS Satellites used
  strtok(NULL, ",");
  strcpy(cn0max, strtok(NULL, ",")); // Gets C/N0 max
  strcpy(HPA, strtok(NULL, ",")); // Gets HPA
  strcpy(VPA, strtok(NULL, "\r")); // Gets VPA
  
  Serial.println("Runstatus");
  Serial.println(GNSSrunstatus);
  Serial.println("Fixstatus");
  Serial.println(Fixstatus);
  Serial.println("UTCdatetime");
  Serial.println(UTCdatetime);
  Serial.println("latitude");
  Serial.println(latitude);
  Serial.println("logitude");
  Serial.println(logitude);
  Serial.println("altitude");
  Serial.println(altitude);
  Serial.println("speedOTG");
  Serial.println(speedOTG);
  Serial.println("course");
  Serial.println(course);
  Serial.println("fixmode");
  Serial.println(fixmode);
  Serial.println("HDOP");
  Serial.println(HDOP);
  Serial.println("PDOP");
  Serial.println(PDOP);
  Serial.println("VDOP");
  Serial.println(VDOP);
  Serial.println("satellitesinview");
  Serial.println(satellitesinview);
  Serial.println("GNSSsatellitesused");
  Serial.println(GNSSsatellitesused);
  Serial.println("GLONASSsatellitesused");
  Serial.println(GLONASSsatellitesused);
  Serial.println("cn0max");
  Serial.println(cn0max);
  Serial.println("HPA");
  Serial.println(HPA);
  Serial.println("VPA");
  Serial.println(VPA);

  return answer;
}

Though not completely worked out:

AlleAlles OK
Runstatus
1
Fixstatus
1
UTCdatetime
20160501124254
latitude
47.199897
logitude
9.442750
altitude
473.500
speedOTG
0.35
course

fixmode
1
HDOP
1.9
PDOP
1.6
VDOP

satellitesinview
39
GNSSsatellitesused

GLONASSsatellitesused

cn0max

HPA

VPA

Well i don't need an int number thats the reason why i keept char because i'll transmit the whole thing as one string via HTTP to a server afterwards.

Now it doesn't work anymore anyway with your code. The values I get are wrong -> it is running but it doesn't give out a 1.

Runstatus 0 Fixstatus 0 UTCdatetime

latitude

logitude

altitude

speedOTG

course

fixmode

HDOP

PDOP

VDOP

satellitesinview

GNSSsatellitesused

GLONASSsatellitesused

cn0max

HPA

VPA

Just to make it complete, if I use your changes in the strtok function i just get again runstatus and fixstatus empty:

GNSSrunstatus

Fixstatus

UTCdatetime 20160501140841.000 latitude 47.199895 logitude 9.442748 altitude 467.300 speedOTG 0.19 course

fixmode 1 HDOP 1.6 PDOP 00 VDOP ?>.000 satellitesinview

GNSSsatellitesused

GLONASSsatellitesused

cn0max

HPA

VPA

EDIT: By just including to skip over the 3 zeros i get at least the fix status:

GNSSrunstatus

Fixstatus 1 UTCdatetime 20160501141918 latitude 47.199842 logitude 9.442688 altitude 461.500 speedOTG 0.06 course

fixmode 1 HDOP 1.6 PDOP 000 VDOP ?>8.000 satellitesinview

GNSSsatellitesused

GLONASSsatellitesused

cn0max

HPA

VPA

I can’t see your code from here.

I would be super helpful to know what fields you have interest in keeping versus tossing

Also, a char array of size one cannot be printed, you need at least two… one for the NULL terminator

char GNSSrunstatus[1]; // <<<<<< no good

BulldogLowell:
I
… a char array of size one cannot be printed, you need at least two… one for the NULL terminator

Well, not really. I think you meant to say you can’t print a one-element char array as a string:

void setup() {
  char GNSSrunstatus[1]; // <<<<<< no good

  Serial.begin(9600);
  GNSSrunstatus[0] = 'A';
  Serial.print("GNSSrunstatus[0] = ");
  Serial.println(GNSSrunstatus[0]);
}

void loop() {
}

Well i see, this makes at least some sense why these numbers are missing.

but for sattellitesinview or course still not... or do i have to every char array +1 char to print it as a string?

another problem i found myself is the function strtok doesn't work for this string: +CGNSINF: 1,1,20160501124254.000,47.199897,9.442750,473.500,0.35,36.8,1,,1.1,1.9,1.6,,13,7,,,39,, OK

there are multiple commas with an empty value which get ignored by strtok. so i have to find another way to split this string.

renedlog: Well i see, this makes at least some sense why these numbers are missing.

but for sattellitesinview or course still not... or do i have to every char array +1 char to print it as a string?

another problem i found myself is the function strtok doesn't work for this string: +CGNSINF: 1,1,20160501124254.000,47.199897,9.442750,473.500,0.35,36.8,1,,1.1,1.9,1.6,,13,7,,,39,, OK

there are multiple commas with an empty value which get ignored by strtok. so i have to find another way to split this string.

You will have to parse the string manually, if you expect to sometimes see data in those empty places.

As you observed, strtok() obliterates the duplicate tokens...

Check out Serial Input Basics on the top thread in this section for how to do that...

I don’t really need the data but I don’t programm something that’s not fail proof. I’m not sure if in some case there is an input and then I’ll have a problem if i don’t use them.

Found a nice implementation on Stackoverflow and I’ll try and report on it, today evening:

renedlog:
Well i see, this makes at least some sense why these numbers are missing.

but for sattellitesinview or course still not… or do i have to every char array +1 char to print it as a string?

another problem i found myself is the function strtok doesn’t work for this string:
+CGNSINF: 1,1,20160501124254.000,47.199897,9.442750,473.500,0.35,36.8,1,1.1,1.9,1.6,13,7,39, OK

there are multiple commas with an empty value which get ignored by strtok. so i have to find another way to split this string.

I’ve just looked my old GPS sources and I think I found the function I wrote as a replacement for strtok to get back each string from a line of GPS data, even if if is an empty sttring because multiple commas are following each other.

This is my function I wrote a couple of years ago:

char* chartoken(char* pointer, char c)
// similar strtok, but returns after a single char found
{
  static char* ptr;
  if (pointer!=NULL) ptr=pointer;
  else if (ptr!=NULL) ptr++;
  char* returnptr=ptr;
  if (ptr!=NULL)ptr=strchr(ptr,c);
  if (ptr!=NULL) ptr[0]='\0';  
  return returnptr;
}

The function is to be used exactly as the standard-C strtok() function, except the second parameter: My function chartoken() takes a single seperating character as the second parameter, and not a pointer to a character array of seperating characters like strtok() does.

The difference of my function against strtok() is, that my function does not eat up seperating characters greedily, but only one by one. So that if the original string contains many seperating characters in a sequence like “,” my function will return a string of zero length after each single seperating character.

I gave it up using char for the GNSSrunstatus value and the Fixstatus. I can’t really figure out why it doen’t work, even when i don’t use an one character array.

But with your code it works to some kind. Atm. i still have problems with the string:

GNSSrunstatus
1
Fixstatus
0
UTCdatetime
20160503163506.000
latitude
47.199757
logitude
9.442715
altitude
470.400
speedOTG
2.63
course
185.5
fixmode
1
HDOP
1.6
PDOP
1.8
VDOP
99
satellitesinview
.199
GNSSsatellitesused
47.199
GLONASSsatellitesused

cn0max

HPA

VPA

The actual code:

#include <SoftwareSerial.h>

char frame[100];
byte GNSSrunstatus;;
byte Fixstatus;
char UTCdatetime[18];
char latitude[10];
char logitude[11];
char altitude[8];
char speedOTG[6];
char course[6];
byte fixmode;
char HDOP[4];
char PDOP[4];
char VDOP[4];
char satellitesinview[2];
char GNSSsatellitesused[2];
char GLONASSsatellitesused[2];
char cn0max[2];
char HPA[6];
char VPA[6];

boolean state;
double serialnr;

SoftwareSerial mySerial(10, 11); // RX, TX

void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }


  Serial.println("Alles OK");

  // set the data rate for the SoftwareSerial port
  mySerial.begin(9600);
  mySerial.println("AT");
  mySerial.println("AT+CGNSPWR=1");
}

void loop() { // run over and over
  get_GPS();
}

int8_t get_GPS() {

  int8_t counter, answer;
  long previous;

  // First get the NMEA string
  // Clean the input buffer
  while ( mySerial.available() > 0) mySerial.read();
  // request Basic string
  mySerial.println("AT+CGNSINF"); //sendATcommand("AT+CGNSINF", "AT+CGNSINF\r\n\r\n", 2000);

  counter = 0;
  answer = 0;
  memset(frame, '\0', sizeof(frame));    // Initialize the string
  previous = millis();
  // this loop waits for the NMEA string
  do {

    if (mySerial.available() != 0) {
      frame[counter] = mySerial.read();
      counter++;
      // check if the desired answer is in the response of the module
      if (strstr(frame, "OK") != NULL)
      {
        answer = 1;
      }
    }
    // Waits for the asnwer with time out
  }
  while ((answer == 0) && ((millis() - previous) < 2000));

  frame[counter - 3] = '\0';

  // Parses the string
  strtok_single(frame, ": ");
  GNSSrunstatus = atoi(strtok_single(NULL, ","));;// Gets GNSSrunstatus
  Fixstatus = atoi(strtok_single(NULL, ",")); // Gets Fix status
  strcpy(UTCdatetime, strtok_single(NULL, ",")); // Gets UTC date and time
  strcpy(latitude, strtok_single(NULL, ",")); // Gets latitude
  strcpy(logitude, strtok_single(NULL, ",")); // Gets longitude
  strcpy(altitude, strtok_single(NULL, ",")); // Gets MSL altitude
  strcpy(speedOTG, strtok_single(NULL, ",")); // Gets speed over ground
  strcpy(course, strtok_single(NULL, ",")); // Gets course over ground
  fixmode = atoi(strtok_single(NULL, ",")); // Gets Fix Mode
  strtok_single(NULL, ",");
  strcpy(HDOP, strtok_single(NULL, ",")); // Gets HDOP
  strcpy(PDOP, strtok_single(NULL, ",")); // Gets PDOP
  strcpy(VDOP, strtok_single(NULL, ",")); // Gets VDOP
  strtok_single(NULL, ",");
  strcpy(satellitesinview, strtok_single(NULL, ",")); // Gets GNSS Satellites in View
  strcpy(GNSSsatellitesused, strtok_single(NULL, ",")); // Gets GNSS Satellites used
  strcpy(GLONASSsatellitesused, strtok_single(NULL, ",")); // Gets GLONASS Satellites used
  strtok_single(NULL, ",");
  strcpy(cn0max, strtok_single(NULL, ",")); // Gets C/N0 max
  strcpy(HPA, strtok_single(NULL, ",")); // Gets HPA
  strcpy(VPA, strtok_single(NULL, "\r")); // Gets VPA

Serial.println("GNSSrunstatus");
  Serial.println(GNSSrunstatus);
Serial.println("Fixstatus");
  Serial.println(Fixstatus);
Serial.println("UTCdatetime");
  Serial.println(UTCdatetime);
Serial.println("latitude");  
  Serial.println(latitude);
Serial.println("logitude");
  Serial.println(logitude);
Serial.println("altitude");
  Serial.println(altitude);
Serial.println("speedOTG");
  Serial.println(speedOTG);
Serial.println("course");
  Serial.println(course);
Serial.println("fixmode");
  Serial.println(fixmode);
Serial.println("HDOP");
  Serial.println(HDOP);
Serial.println("PDOP");
  Serial.println(PDOP);
Serial.println("VDOP");
  Serial.println(VDOP);
Serial.println("satellitesinview");
  Serial.println(satellitesinview);
Serial.println("GNSSsatellitesused");
  Serial.println(GNSSsatellitesused);
Serial.println("GLONASSsatellitesused");
  Serial.println(GLONASSsatellitesused);
Serial.println("cn0max");
  Serial.println(cn0max);
Serial.println("HPA");
  Serial.println(HPA);
Serial.println("VPA");
  Serial.println(VPA);
  
  return answer;
}

/* strtok_fixed - fixed variation of strtok_single */
static char *strtok_single(char *str, char const *delims)
{
    static char  *src = NULL;
    char  *p,  *ret = 0;

    if (str != NULL)
        src = str;

    if (src == NULL || *src == '\0')    // Fix 1
        return NULL;

    ret = src;                          // Fix 2
    if ((p = strpbrk(src, delims)) != NULL)
    {
        *p  = 0;
        src = ++p;
    }
    else
        src += strlen(src);

    return ret;
}