Splitting a char array by a delimiter and converting to integers and floats

Hello,

I'm writing a small library for my Quectel BG95.
Below you see my getGPS function.
I normally don't write code in arduino much, so don't laugh about how ridiculous devious the code looks like.
I tried many different ways but this seems to be the only way, as far as I figured.

As you can see below I split the _buffer which is holding the GPS data (I fill the buffer manually just for debugging).
It seems to be working for the most part, however at the end the outcome is wrong.
The outcome of the time and date should be :

_h = 18;
_m = 42;
_s = 41;
_da = 11;
_mo = 11;
_ye = 20;

But it is:

18
4218
414218
11414218
1111414218
201111414218

I don't see what is going wrong, someone perhaps does?

Thanks.

bool Quectel::getGPS(float *lat, float *lng, int time[])
{
	if (sendAndWaitForReply("AT+QGPSLOC=2", 2000))
	{
    ////+QGPSLOC: 184241.0,51.53045,5.99622,1.9,11.0,2,97.49,0.0,0.0,111120,04

    sprintf(_buffer,"+QGPSLOC: 184241.0,51.53045,5.99622,1.9,11.0,2,97.49,0.0,0.0,111120,04");
    
    char _response[8];

    char _time[7]="0";
    char _date[6]= "0";
    char _lat[8]= "0";
    char _lng[8]= "0";

    int _step = 0;
    int _step_i = 0;
    
    for (int i = 0; i < sizeof(_buffer) - 1; i++) {
        
        if(_buffer[i] == ':') {
            _step = 1;
            _step_i = 0;
        }
        else if(_buffer[i] == ',') {
            _step++;
            _step_i = 0;
        }
        else {
          
          if(_step == 0) _response[_step_i] = _buffer[i];
          if(_step == 1) _time[_step_i] = _buffer[i]; 
          if(_step == 2) _lat[_step_i] = _buffer[i];
          if(_step == 3) _lng[_step_i] = _buffer[i]; 
          if(_step == 10) _date[_step_i] = _buffer[i];
          
          _step_i++;
        }
    }

     char _h[2], _m[2], _s[2], _da[2], _mo[2], _ye[2];

     _h[0] = _time[1];
     _h[1] = _time[2];
     _m[0] = _time[3];
     _m[1] = _time[4];
     _s[0] = _time[5];
     _s[1] = _time[6];

     _da[0] = _date[0];
     _da[1] = _date[1];
     _mo[0] = _date[2];
     _mo[1] = _date[3];
     _ye[0] = _date[4];
     _ye[1] = _date[5];

    _debug->println(_h);
    _debug->println(_m);
    _debug->println(_s);
    _debug->println(_da);
    _debug->println(_mo);
    _debug->println(_ye);
    
    time[0] = atoi(_h);
    time[1] = atoi(_m);
    time[2] = atoi(_s);
    time[3] = atoi(_da);
    time[4] = atoi(_mo);
    time[5] = atoi(_ye);

    _debug->println(time[0]);
    
    *lat = atof(_lat);
    *lng = atof(_lng);
    return true;
	}
  return false;
}

You might find the strtok function useful.

Here is an example to split your character array into parts using the strtok() function.. The for loop using the strtok() function returns an array of pointers to the pieces of the original array. The the hours, minutes, seconds, day, month and year are pulled from piece [1] and [10] using pointer arithmetic and the memcpy() function. Maybe a easier or more elegant way to do it, but here is my take.

char array[] = "+QGPSLOC: 184241.0,51.53045,5.99622,1.9,11.0,2,97.49,0.0,0.0,111120,04";
char *strings[16]; // an array of pointers to the pieces of the above array after strtok()
char *ptr = NULL;

void setup()
{
   Serial.begin(9600);
   //Serial.print(array);
   byte index = 0;
   ptr = strtok(array, " ,");  // delimiters space and comma
   while (ptr != NULL)
   {
      strings[index] = ptr;
      index++;
      ptr = strtok(NULL, ",");
   }
   //Serial.println(index);
   // print all the parts
   Serial.println("The pieces, separated by strtok()");
   for (int n = 0; n < index; n++)
   {
      Serial.print(n);
      Serial.print("  ");
      Serial.println(strings[n]);
   }
   
   // get hours, minutes, seconds, day, month and year from piece [1] and [10].
   Serial.println();
   Serial.println("data separated from piece [1] and piece [10]");
   // hours 2 characters + null
   char h[] = {'0','0','\0'}; 
   memcpy(h, strings[1], 2);  // characters 0 and 1
   int _h = atoi(h);
   Serial.print(" _h = ");
   Serial.println(_h);
   // minutes
   char m[] = {'0','0','\0'};
   memcpy(m, strings[1] + 2, 2); // characters 2 and 3
   int _m = atoi(m);
   Serial.print(" _m = ");
   Serial.println(_m);
   // seconds
   char s[] = {'0','0','\0'};
   memcpy(s, strings[1] + 4, 2); // characters 4 and 5
   int _s = atoi(s);
   Serial.print(" _s = ");
   Serial.println(_s);
   // day
   char da[] = {'0','0','\0'};
   memcpy(da, strings[10], 2);
   int _da = atoi(da);
   Serial.print("\n _da = ");
   Serial.println(_da);
   // month
   char mo[] = {'0','0','\0'};
   memcpy(mo, strings[10] + 2, 2);
   int _mo = atoi(mo);
   Serial.print(" _mo = ");
   Serial.println(_mo);
   // year
   char yr[] = {'0','0','\0'};
   memcpy(yr, strings[10] + 4, 2);
   int _yr = atoi(yr);
   Serial.print(" _yr = ");
   Serial.println(_yr);
}

void loop()
{
   // step away, nothing to see here.
}

Output:

The pieces, separated by strtok()
0  +QGPSLOC:
1  184241.0
2  51.53045
3  5.99622
4  1.9
5  11.0
6  2
7  97.49
8  0.0
9  0.0
10  110520
11  04

data separated from piece [1] and piece [10]
 _h = 18
 _m = 42
 _s = 41

 _da = 11
 _mo = 11
 _yr = 20

Thank you very much!

Now it is working much better, and less code :slight_smile:

See getGPS function for reference:

bool Quectel::getGPS(float *lat, float *lng, int time[])
{
	if (sendAndWaitForReply("AT+QGPSLOC=2", 2000))
	{
     //FORMAT: +QGPSLOC: 184241.0,51.53045,5.99622,1.9,11.0,2,97.49,0.0,0.0,111120,04
    
     char *parts[16];
     char *ptr = NULL;
     byte index = 0;
     
     ptr = strtok(_buffer, " ,");  // delimiters space and comma
     while (ptr != NULL)
     {
        parts[index] = ptr;
        index++;
        ptr = strtok(NULL, ",");
     }
     
     if (strcmp(parts[0], "+QGPSLOC:") == 0) 
     {
         // get hours, minutes, seconds, day, month and year from piece [1] and [10].
         // hours 2 characters + null
         char h[] = {'0','0','\0'};
         memcpy(h, parts[1], 2);  // characters 0 and 1
         char m[] = {'0','0','\0'};
         memcpy(m, parts[1] + 2, 2); // characters 2 and 3
         char s[] = {'0','0','\0'};
         memcpy(s, parts[1] + 4, 2); // characters 4 and 5
         
         char da[] = {'0','0','\0'};
         memcpy(da, parts[10], 2);
         char mo[] = {'0','0','\0'};
         memcpy(mo, parts[10] + 2, 2);
         char yr[] = {'0','0','\0'};
         memcpy(yr, parts[10] + 4, 2);
    
         time[0] = atoi(h);
         time[1] = atoi(m);
         time[2] = atoi(s);
         time[3] = atoi(da);
         time[4] = atoi(mo);
         time[5] = atoi(yr);
    
         *lat = atof(parts[2]);
         *lng = atof(parts[3]);
         
         return true;
     }
	}
  return false;
}

Here is a version using the SafeString library (detailed tutorial here) (Edit: V2.0.2 includes the toInt() )

#include "SafeString.h"
char gpsData[] = "+QGPSLOC: 184241.0,51.53045,5.99622,1.9,11.0,2,97.49,0.0,0.0,111120,04";

bool getGPS(float &lat, float &lng, int time[]) {
  //if (sendAndWaitForReply("AT+QGPSLOC=2", 2000))
  char *data = gpsData;
  cSFP(sfData, data); // create a SafeString from the data
  sfData.trim();
  if (sfData.startsWith("+QGPSLOC:")) {
    cSF(sfToken, 12); // create SafeString for token
    cSF(sfSub,3); // create SafeString for substrings
    char delimiters[] = " ,"; 
    // could do more error checking here but for now
    sfData.nextToken(sfToken, delimiters);  // step over +QGPSLOC:
    sfData.nextToken(sfToken, delimiters);  // pick up 184241.0
    sfToken.substring(sfSub,0,2); // h
    sfSub.toInt(time[0]);  // returns false if not a valid long
    sfToken.substring(sfSub,2,4); // m
    sfSub.toInt(time[1]);  // returns false if not a valid long
    sfToken.substring(sfSub,4,6); // sec
    sfSub.toInt(time[2]);  // returns false if not a valid long
    
    sfData.nextToken(sfToken, delimiters);  // pick up 51.53045
    sfToken.toFloat(lat); // returns false if not a valid float
    sfData.nextToken(sfToken, delimiters);  // pick up 5.99622
    sfToken.toFloat(lng); // returns false if not a valid float

    sfData.nextToken(sfToken, delimiters);  // pick up 1.9
    sfData.nextToken(sfToken, delimiters);  // pick up 11.0
    sfData.nextToken(sfToken, delimiters);  // pick up 2
    sfData.nextToken(sfToken, delimiters);  // pick up 97.49
    sfData.nextToken(sfToken, delimiters);  // pick up 0.0
    sfData.nextToken(sfToken, delimiters);  // pick up 0.0
    sfData.nextToken(sfToken, delimiters);  // pick up 111120

    sfToken.substring(sfSub,0,2); // da
    sfSub.toInt(time[3]);  // returns false if not a valid long
    sfToken.substring(sfSub,2,4); // mo
    sfSub.toInt(time[4]);  // returns false if not a valid long
    sfToken.substring(sfSub,4,6); // yr
    sfSub.toInt(time[5]);  // returns false if not a valid long
    return true;
  } // else not  +QGPSLOC: msg
  return false;
}

void setup() {
  Serial.begin(9600);
  for (int i = 10; i > 0; i--) {
    Serial.print(' '); Serial.print(i);
    delay(500);
  }
  Serial.println();
  SafeString::setOutput(Serial); // for debugging and error msgs
  float lat;
  float lng;
  int timeDate[6] = {0,0,0,0,0,0};
  getGPS(lat, lng, timeDate);
  Serial.print("lat:"); Serial.println(lat, 5);
  Serial.print("lng:"); Serial.println(lng, 5);
  Serial.print(timeDate[0]);Serial.print(':');Serial.print(timeDate[1]);Serial.print(':');Serial.print(timeDate[2]);Serial.println();
  Serial.print("20");Serial.print(timeDate[5]);Serial.print('/');Serial.print(timeDate[4]);Serial.print('/');Serial.print(timeDate[3]);Serial.println();
}

void loop() {
}

The output is

lat:51.53045
lng:5.99622
18:42:41
2020/11/11