Extracting characters from a string with substring

I am running the following code from SoftwareSerial example.
I am attempting to extract various fields from the GPS String.
For some reason I am not getting the correct data.
My Code:

void loop() { // run over and over

  if (mySerial.available()) {
    String s = mySerial.readStringUntil('\n');
    if (s.startsWith("$GPRMC")) {
      int posHeader = s.indexOf("$GPRMC");
      if (posHeader == 0) { // Got "$GPRMC"
        Serial.println(s);
        int posHour = s.indexOf(',');// Get first occurance of comma ","
        Serial.print("Position of First Comma: ");
        Serial.println(posHour);

        gpsHour = s.substring((posHour + 1), 2);// Get GPS Hours from String
        Serial.print("GPS Hour: ");
        Serial.println(gpsHour);
      }
    }
  }
}

Serial Monitor output:

$GPRMC,041725.000,A,2551.3703,S,02810.7587,E,0.02,223.13,211018,,,A*7C

Position of First Comma: 6 <<<<<<< THIS IS CORRECT
GPS Hour: PRMC,
Position of First Comma: 6 <<<<<<< THIS IS CORRECT
GPS Hour: PRMC, <<<<<<<< THIS IS INCORRECT - SHOULD BE "04"
gpsHour = s.substring((posHour + 1), 2);// Get GPS Hours from String

Reread the String.substring() reference.
Check your parameters. :wink:

darrob:

gpsHour = s.substring((posHour + 1), 2);// Get GPS Hours from String

Reread the String.substring() reference.
Check your parameters. :wink:

I have read and reread "String.substring"
For some reason I am not getting the correct characters from the String.
If I do:
gpsHour = s.substring(7, 3);
I should receive "04"
But I get "RMC,"

Declan:
I have read and reread "String.substring"
...
gpsHour = s.substring(7, 3);
I should receive "04"
But I get "RMC,"

So you read about the from and to indices?

And still you want a substring starting on position 7 and ending before position 3?

I think substring trys to be clever and swaps the parameters if to is not bigger than from.

gpsHour = s.substring(7, 3);

The two parameters are "from" and "to - 1" positions in the given string

How do you expect the function to return the substring from position 7 to position 3 ?
Try

 s.substring(7, 9)  //position 7 to position 8

Are you confusing from/to with from/length ?

UKHeliBob:

gpsHour = s.substring(7, 3);

The two parameters are "from" and "to - 1" positions in the given string

How do you expect the function to return the substring from position 7 to position 3 ?
Try

 s.substring(7, 9)  //position 7 to position 8

Are you confusing from/to with from/length ?

Thanks, I was

This sorted the problem:

gpsHour = s.substring(posHour +1, posHour + 3);

So my code is:

void loop() {

  if (mySerial.available()) {
    String s = mySerial.readStringUntil('\n');
    if (s.startsWith("$GPRMC")) {
      int posHeader = s.indexOf("$GPRMC");
      if (posHeader == 0) { // Got "$GPRMC"

        Serial.print("Length of String = ");
        Serial.println(s.length());

        if (s.length() >= 69) {
          Serial.println("That's a perfectly acceptable text message");
          s.trim();
          Serial.println(s);
          int posHour = s.indexOf(',');// Get first occurance of comma ","
          Serial.print("Position of First Comma: ");
          Serial.println(posHour);

          gpsHour = s.substring(posHour + 1, posHour + 3); // Get GPS Hours from String
          Serial.print("GPS Hour: ");
          Serial.println(gpsHour);
        }
      }
    }
  }
}

I would now like to do away with using Arduino Strings.
Could someone please point me to a possible example of how to change the code?

Declan:
I would now like to do away with using Arduino Strings.
Could someone please point me to a possible example of how to change the code?

Have you seen Serial Input Basics - simple reliable ways to receive data.

...R

@Robin2
Awesome write-up and examples.

Based on the examples, I have the following sketch:

#include <SoftwareSerial.h>

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

const byte numChars = 72;
char receivedChars[numChars];
char tempChars[numChars];        // temporary array for use when parsing

// variables to hold the parsed data
char gpsData[numChars] = {0};
char gpsTime[6];
char gpsValid[1];
float gpsLat = 0.0000;
char gpsNS[1];
float gpsLon = 0.0000;
char gpsEW[1];
float gpsKnots = 0;
float gpsTrack = 0;
char gpsDate[6];

boolean newData = false;

//============

void setup() {
  Serial.begin(9600);
  gpsSerial.begin(9600);


//  Serial.println("This demo expects 3 pieces of data - text, an integer and a floating point value");
//  Serial.println("Enter data in this style <HelloWorld, 12, 24.7>  ");
//  Serial.println();
}

//============

void loop() {
  recvWithStartEndMarkers();
  if (newData == true) {
    strcpy(tempChars, receivedChars);
    // this temporary copy is necessary to protect the original data
    //   because strtok() used in parseData() replaces the commas with \0
    parseData();
    showParsedData();
    newData = false;
  }
}

//============

void recvWithStartEndMarkers() {
    static boolean recvInProgress = false;
    static byte ndx = 0;
    char startMarker = '$GPRMC';
    char endMarker = '\n';
    char rc;

  while (gpsSerial.available() > 0 && newData == false) {
    rc = gpsSerial.read();

    if (recvInProgress == true) {
      if (rc != endMarker) {
        receivedChars[ndx] = rc;
        ndx++;
        if (ndx >= numChars) {
          ndx = numChars - 1;
        }
      }
      else {
        receivedChars[ndx] = '\0'; // terminate the string
        recvInProgress = false;
        ndx = 0;
        newData = true;
      }
    }

    else if (rc == startMarker) {
      recvInProgress = true;
    }
  }
}

//============

void parseData() {      // split the data into its parts

  char * strtokIndx; // this is used by strtok() as an index

  strtokIndx = strtok(tempChars, ",");     // get the first part - the string
  strcpy(gpsTime, strtokIndx); // copy it to gpsTime

  strtokIndx = strtok(NULL, ",");     // get the first part - the string
  strcpy(gpsValid, strtokIndx); // copy it to gpsValid

  strtokIndx = strtok(NULL, ",");
  gpsLat = atof(strtokIndx);     // convert this part to a float

  strtokIndx = strtok(NULL, ",");     // get the first part - the string
  strcpy(gpsNS, strtokIndx); // copy it to gpsData

  strtokIndx = strtok(NULL, ",");
  gpsLon = atof(strtokIndx);     // convert this part to a float

  strtokIndx = strtok(NULL, ",");     // get the first part - the string
  strcpy(gpsEW, strtokIndx); // copy it to gpsEW

  strtokIndx = strtok(NULL, ",");
  gpsKnots = atof(strtokIndx);     // convert this part to a float

  strtokIndx = strtok(NULL, ",");
  gpsTrack = atof(strtokIndx);     // convert this part to a float

  strtokIndx = strtok(NULL, ",");     // get the first part - the string
  strcpy(gpsDate, strtokIndx); // copy it to gpsDate

  Serial.println("------------------------------------------------");
  Serial.println();
}

//============

void showParsedData() {
  Serial.print("Time: ");
  Serial.println(gpsTime);
  Serial.print("Valid: ");
  Serial.println(gpsValid);
  Serial.print("Latitude: ");
  Serial.println(gpsLat,4);
  Serial.print("North/South: ");
  Serial.println(gpsNS);
  Serial.print("Longitude: ");
  Serial.println(gpsLon,4);
  Serial.print("East/West: ");
  Serial.println(gpsEW);
  Serial.print("Knots: ");
  Serial.println(gpsKnots);
  Serial.print("Track: ");
  Serial.println(gpsTrack);
  Serial.print("Date: ");
  Serial.println(gpsDate);
}

My Serial Monitor output is:

Time: 
Valid: A
Latitude: 2551.3125
North/South: S
Longitude: 2810.7500
East/West: E
Knots: 0.01
Track: 124.00
Date: 211018

For some reason I am not getting gpsTime with:

  strtokIndx = strtok(tempChars, ",");     // get the first part - the string
  strcpy(gpsTime, strtokIndx); // copy it to gpsTime

Put in some code to print the received data before you parse it.

...R

I have add

Serial.print("Received Data: ");
Serial.println(tempGPS);

To "void parseGPS()"

My Serial Monitor outspu is now:

Received Data: ,132916.000,A,2551.3730,S,02810.7596,E,0.02,345.73,211018,,,A*70

Time: 
Valid: A
Latitude: 2551.3125
North/South: S
Longitude: 2810.7500
East/West: E
Knots: 0.02
Track: 345.73
Date: 211018
------------------------------------------------

I cannot understand why I am not able to parse the Time - I am definitely doing something wrong.

Another problem I have is being able to extract the Day/Month/Year from "gpsDate".
I receive: "211018" and require this in separate "fields" to write to a RTC.

gpsValid is too small. You're trying to strcopy 'A' and a null into a single character "array".

@wildbill
Many thanks

Any ideas how I can:

Another problem I have is being able to extract the Day/Month/Year from "gpsDate".
I receive: "211018" and require this in separate "fields" to write to a RTC.

As @Wildbill has said you need to change char gpsValid[1]; to char gpsValid[2]; to make space for the terminating NULL. I would do the same with the other single character variables.

Also increase the size of char gpsTime[6]; so that it has plenty of space for the full time text.

...R

Serial.print("Received Data: ");
Serial.println(tempGPS);

The last code posted does not contain "tempGPS". Please post your current code.

char startMarker = '$GPRMC';

This looks wrong. char is a signed value between -128 to 127. What do you think the value of '$GPRMC' is?

Your start character should be '$'.

void showParsedData() 
{
char Day[3],Month[3],Year[3];

  Serial.print("Time: ");
  Serial.println(gpsTime);
  Serial.print("Valid: ");
  Serial.println(gpsValid);
  Serial.print("Latitude: ");
  Serial.println(gpsLat, 4);
  Serial.print("North/South: ");
  Serial.println(gpsNS);
  Serial.print("Longitude: ");
  Serial.println(gpsLon, 4);
  Serial.print("East/West: ");
  Serial.println(gpsEW);
  Serial.print("Knots: ");
  Serial.println(gpsKnots);
  Serial.print("Track: ");
  Serial.println(gpsTrack);
  Serial.print("Date: ");
  Serial.println(gpsDate);
  strncpy(Day,gpsDate,2);
  Day[2]=0;
  Serial.print("Day: ");
  Serial.println(Day);
  strncpy(Month,&gpsDate[2],2);
  Month[2]=0;
  Serial.print("Month: ");
  Serial.println(Month);
  strncpy(Year,&gpsDate[4],2);
  Year[2]=0;
  Serial.print("Year: ");
  Serial.println(Year);  
}

Thanks all.
I have changed the startMarker "headerGPS " to '$'. - Thanks cattledog
I have also included the code from wildbill to parse the date and time

My Code so far:

#include <SoftwareSerial.h>

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

const byte charsGPS = 72;
char receivedGPS[charsGPS];
char tempGPS[charsGPS];        // temporary array for use when parsing

// variables to hold the parsed data
char gpsData[charsGPS] = {0};
char gpsHeader[6];
char gpsTime[7];
char gpsValid[2];
float gpsLat = 0.0000;
char gpsNS[2];
float gpsLon = 0.0000;
char gpsEW[2];
float gpsKnots = 0;
float gpsTrack = 0;
char gpsDate[6];

boolean newGPS = false;

//============

void setup() {
  Serial.begin(9600);
  gpsSerial.begin(9600);
  gpsSerial.println("$PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29");// GPS only uotputs $GPRMC message

}

//============

void loop() {
  receiveGPS();
  if (newGPS == true) {
    strcpy(tempGPS, receivedGPS);
    // this temporary copy is necessary to protect the original data
    //   because strtok() used in parseGPS() replaces the commas with \0
    parseGPS();
    showGPS();
    newGPS = false;
  }
}

//============

void receiveGPS() {
    static boolean recvGPSinProgress = false;
    static byte ndxGPS = 0;
    char headerGPS = '

Serial Monitor output:

Received Data: GPRMC,154326.000,A,2551.3714,S,02810.7593,E,0.02,131.12,211018,,,A*7C

Header: 000
Time: 154326.000
Valid: A
Latitude: 2551.3713
North/South: S
Longitude: 2810.7590
East/West: E
Knots: 0.02
Track: 131.12
Date: 211018
------------------------------------------------
Day: 21
Month: 10
Year: 18
Hours: 15
Minutes: 43
Seconds: 26
------------------------------------------------

All looking great - many thanks to all.

Now I have a major challenge:
I must print gpsLat / gpsLon / gpsNS / gpsEW as HEX as required by Sigfox.
I can only send 12 Bytes and need to send as follows
gpsLat + gpsNS as 5 bytes
gpsLon + gpsEW as 5 bytes

Any suggestions or pointers?;
    char endstrGPS = '\n';
    char rxGPS;

while (gpsSerial.available() > 0 && newGPS == false) {
    rxGPS = gpsSerial.read();

if (recvGPSinProgress == true) {
      if (rxGPS != endstrGPS) {
        receivedGPS[ndxGPS] = rxGPS;
        ndxGPS++;
        if (ndxGPS >= charsGPS) {
          ndxGPS = charsGPS - 1;
         
        }
      }
      else {
        receivedGPS[ndxGPS] = '\0'; // terminate the string
        recvGPSinProgress = false;
        ndxGPS = 0;
        newGPS = true;
      }
    }

else if (rxGPS == headerGPS) {
      recvGPSinProgress = true;
    }
  }
}

//============

void parseGPS() {      // split the data into its parts

Serial.print("Received Data: ");
Serial.println(tempGPS);

char * strtokIndx; // this is used by strtok() as an index

strtokIndx = strtok(tempGPS, ",");    // get the first part - the string
  strcpy(gpsHeader, strtokIndx); // copy it to gpsTime

strtokIndx = strtok(NULL, ",");    // get the first part - the string
  strcpy(gpsTime, strtokIndx); // copy it to gpsTime

strtokIndx = strtok(NULL, ",");    // get the first part - the string
  strcpy(gpsValid, strtokIndx); // copy it to gpsValid

strtokIndx = strtok(NULL, ",");
  gpsLat = atof(strtokIndx);    // convert this part to a float

strtokIndx = strtok(NULL, ",");    // get the first part - the string
  strcpy(gpsNS, strtokIndx); // copy it to gpsData

strtokIndx = strtok(NULL, ",");
  gpsLon = atof(strtokIndx);    // convert this part to a float

strtokIndx = strtok(NULL, ",");    // get the first part - the string
  strcpy(gpsEW, strtokIndx); // copy it to gpsEW

strtokIndx = strtok(NULL, ",");
  gpsKnots = atof(strtokIndx);    // convert this part to a float

strtokIndx = strtok(NULL, ",");
  gpsTrack = atof(strtokIndx);    // convert this part to a float

strtokIndx = strtok(NULL, ",");    // get the first part - the string
  strcpy(gpsDate, strtokIndx); // copy it to gpsDate

}

//============

void showGPS() {
int latHEX;

Serial.print("Header: ");
  Serial.println(gpsHeader);
  Serial.print("Time: ");
  Serial.println(gpsTime);
  Serial.print("Valid: ");
  Serial.println(gpsValid);
  Serial.print("Latitude: ");
  Serial.println(gpsLat,4);
  Serial.print("North/South: ");
  Serial.println(gpsNS);
  Serial.print("Longitude: ");
  Serial.println(gpsLon,4);
  Serial.print("East/West: ");
  Serial.println(gpsEW);
  Serial.print("Knots: ");
  Serial.println(gpsKnots);
  Serial.print("Track: ");
  Serial.println(gpsTrack);
  Serial.print("Date: ");
  Serial.println(gpsDate);
  Serial.println("------------------------------------------------");

char Day[3];
char Month[3];
char Year[3];

char Hours[3];
char Minutes[3];
char Seconds[3];

strncpy(Day,gpsDate,2);
  Day[2]=0;
  Serial.print("Day: ");
  Serial.println(Day);
  strncpy(Month,&gpsDate[2],2);
  Month[2]=0;
  Serial.print("Month: ");
  Serial.println(Month);
  strncpy(Year,&gpsDate[4],2);
  Year[4]=0;
  Serial.print("Year: ");
  Serial.println(Year);

strncpy(Hours,gpsTime,2);
  Hours[2]=0;
  Serial.print("Hours: ");
  Serial.println(Hours);
  strncpy(Minutes,&gpsTime[2],2);
  Minutes[2]=0;
  Serial.print("Minutes: ");
  Serial.println(Minutes);
  strncpy(Seconds,&gpsTime[4],2);
  Seconds[2]=0;
  Serial.print("Seconds: ");
  Serial.println(Seconds);

Serial.println("------------------------------------------------");
  Serial.println();
}


Serial Monitor output:

§DISCOURSE_HOISTED_CODE_1§


All looking great - many thanks to all.

Now I have a major challenge:
I must print gpsLat / gpsLon / gpsNS / gpsEW as HEX as required by Sigfox.
I can only send 12 Bytes and need to send as follows
gpsLat + gpsNS as 5 bytes
gpsLon + gpsEW as 5 bytes

Any suggestions or pointers?

I must print gpsLat / gpsLon / gpsNS / gpsEW as HEX as required by Sigfox.

What is the required format?

It is a 2 character HEX byte

XX = Header (1Byte) XXXXXXXX = Latitude (4Bytes) XX = NS(1Byte) XXXXXXXX = Longitude(4Bytes) XX = EW(1Byte) XX = Battey Voltage (1Byte)

I hope this makes sense.

Time: 154326.000
//char gpsTime[7];
char gpsTime[11];

You are writing into memory you don't own with unpredictable results.

When I test your code and fix this, I get

Header: GPRMC instead of Header: 000 which should have been a red flag.

Alternatively, if you want to get rid of the .000 in gpsTime you can use

strncpy(gpsTime, strtokIndx, 6);

and you won't walk on your memory.