Parsing a part of a string of unknown length

Hi guys, I have searched, uploaded, and experimented yesterday for about 10 hours on this, but not getting exactly what I want. I am looking to parse metadata from a bluetooth module that looks like this: AVRCP_MEDIA TITLE: Captain

AVRCP_MEDIA ARTIST: Dave Matthews Band

AVRCP_MEDIA ALBUM: Busted Stuff

AVRCP_MEDIA NUMBER: 337

AVRCP_MEDIA TOTAL_NUMBER: 498

AVRCP_MEDIA GENRE: Rock

AVRCP_MEDIA PLAYING_TIME(MS): 225558 I have new lines as a reference, and the AVRCP_MEDIA TITLE to use, but I can't quite get it right. My goal is to have a string I can display on a touchscreen, so I can display title, artist, album, song length, etc.

Here is the code I have so far:

String command; // the command string
String title; // the title string *This is the string I'm after!*
void setup() {
  Serial.begin(115200); // start serial at 115200
  pinMode(13, OUTPUT); // an indicator for debugging
  Serial.println("Ready."); // indicate that we are connected and ready to go
}
void loop() {
  if (Serial.available()) {  // when the serial port is available
    char c = Serial.read(); // read the characters
    if (c == '\n') { // if there is a new line
      parseCommand(command); // go and parse the command string
      command = ""; // after the command is parsed, reset it
    }
    else { // if the command is not parsed yet (no new line),
      command += c; // keep adding to the command string
    }
  }
}
void parseCommand(String com) {
  if (command.startsWith("AVRCP")); { // if the command string starts with AVRCP
  title = com.substring(com.indexOf('AVRCP_MEDIA TITLE:') + 2, com.indexOf("\n")); // extract the title part of the string
  }
   Serial.println(title); // print the title string
}

Thanks in advance for the help, I know it is possible. I have done a lot with parsing integers out of strings, but not a section of a string marked by characters.

  title = com.substring(com.indexOf('AVRCP_MEDIA TITLE:') + 2, com.indexOf("\n")); // extract the title part of the string

Single quotes are for single characters. Please take a photo of your keyboard. Import it into Paint, and draw a circle around the ONE key you pressed to get that one character you are looking for in the first call to indexOf(). Post that picture.

title = com.substring(com.indexOf('AVRCP_MEDIA TITLE:') + 2, com.indexOf("\n")); // extract the title part of the string

OK, I get it. But when I had it set up that way, I got the following results:
Ready.
Captain
Dave Matthews Band
Busted Stuff
337
498
Rock
225558

title = com.substring(com.indexOf("AVRCP_MEDIA TITLE:") + 2, com.indexOf("\n")); // extract the title part of the string

With the correct quotes, I get this result:
Ready.
RCP_MEDIA TITLE: Captain
AVRCP_MEDIA ARTIST: Dave Matthews Band
AVRCP_MEDIA ALBUM: Busted Stuff
AVRCP_MEDIA NUMBER: 337
AVRCP_MEDIA TOTAL_NUMBER: 498
AVRCP_MEDIA GENRE: Rock
AVRCP_MEDIA PLAYING_TIME(MS): 225558

Again, I’m not an expert in this (obviously), so I need help on the process of looking for a section of a string that starts with “abc” and stops at “xyz”. I have searched and found all kinds of examples on how to find text at a known position in the string, but nothing on parsing a string as I described above.

Am I using indexOf in vein? Is there a better command to accomplish what I’m looking for? Thanks again, and no, I don’t have an "“AVRCP_MEDIA TITLE:” key, that was the more expensive option… :wink:

com.indexOf("AVRCP_MEDIA TITLE:") + 2

This will move the pointer to extract from to the R. Is that really what you want? Or, are you trying to move the pointer two places past the end of the string?

PaulS: com.indexOf("AVRCP_MEDIA TITLE:") + 2

This will move the pointer to extract from to the R. Is that really what you want? Or, are you trying to move the pointer two places past the end of the string?

I did this to eliminate the space in from of the string. I was getting " Captain" without it there. With it there I am getting "Captain". I did a LOT of fooling around with this, so this is just what got me closer to the result I was looking for.

I am trying to get the string between the "AVRCP_MEDIA TITLE:" and a new line ("\n"). That is the goal I'm after, and when I figure that out I can adapt the process for Artist, Album, etc. Thanks Paul.

If com contains the line to be parsed and you know the leadin is always 20 characters in length, how about:

// ...your code, then add:
int len = strlen(com);              // How long is the string?
strncpy(title, &com[19], len - 20); // Skip over 20 characters and copy the rest.

This still leaves the newline character at the end, but you can chop that off if you need to.

// I read each char in and if it equal \n then I know its the end of line.
if (myChar == '\n') {
               int     colonPosition;  // the position of the next comma in the string
          colonPosition = currentLine.indexOf(':');
          Title =currentLine.substring(colonPosition+1, currentLine.length());
          
}

If you want to search a string for a substring which starts with "abc" and ends with "xyz". then do this:

(1) write a function ( I'll call it fun_a( ) ), which looks for a specified character sequence, and returns the position of the first character where it occurs, or -1 if it doesn't occur anywhere.

int fun_a(  char*  string,    char*  substring )
{
}

(2) write another function fun_b( ) which does this.

int fun_b (  char* string,  char* substring1,  char* substring2 )
{
     int p= fun_a( string, substring1 );
    int q = fun_a( string, substring2 );
    if ( p >= 0 && q >=0 && q>p ) return p ;
    else return -1 ;
}

Thanks @michinyon, but I'm not quite sure how to implement it into the code. Here is what I have so far:

String command; // the command string
String title; // the title string *This is the string I'm after!*

void setup() {
  Serial.begin(115200); // start serial at 115200
  pinMode(13, OUTPUT); // an indicator for debugging
  Serial.println("Ready."); // indicate that we are connected and ready to go
}
void loop() {
  if (Serial.available()) {  // when the serial port is available
    char c = Serial.read(); // read the characters
    if (c == '\n') { // if there is a new line
      parseCommand(command); // go and parse the command string
      command = ""; // after the command is parsed, reset it
    }
    else { // if the command is not parsed yet (no new line),
      command += c; // keep adding to the command string
    }
    Serial.println(title); // print the title string
  }
}
void parseCommand(String com) {
  fun_a("TITLE:", "\n");
}


int fun_a(  char*  string,    char*  substring )
{
}


int fun_b (  char* string,  char* substring1,  char* substring2 )
{
     int p= fun_a( string, substring1 );
    int q = fun_a( string, substring2 );
    if ( p >= 0 && q >=0 && q>p ) return p ;
    else return -1 ;
}

I have tried a few different things, but all I get is blank output with a new line feed. Thanks for the help again, I will understand this!

Wouldn't it be easier to use:

  int bytesRead;
  if (Serial.available()) {  // when the serial port is available
      bytesRead = Serial.ReadBytesUntil('\n', command, MAXLEN);
     // more code...

This reads the entire line of code up to the newline character into the string buffer command and returns the number of characters that were read.

Personally I'm with jasit... it's easier to simply search for the colon because then you can easily find the column name and the contents of the column all by searching for one character.

An example (to just fetch the data not the column information) would look something like:

char * parseCommand(const char * toParse, const char toFind, char  *results) { 
  char * spot = strchr(toParse, toFind);
  results[0]  = '\0';
  
  if (spot != NULL) {
    // Warning: Make sure that 'results' is large enough to hold the string
    return strcpy(results, &toParse[spot - toParse + 2]);
  }
  else 
    return NULL;
}

[...]

 parseCommand("AVRCP_MEDIA TITLE: Captain", ':', name);
 parseCommand("AVRCP_MEDIA ARTIST: Dave Matthews Band", ':', artist);
 parseCommand("AVRCP_MEDIA ALBUM: Busted Stuff", ':',  album);
 parseCommand("AVRCP_MEDIA NUMBER: 337", ':', mediaNo);
 parseCommand("AVRCP_MEDIA TOTAL_NUMBER: 498", ':', totalNo);
 parseCommand("AVRCP_MEDIA GENRE: Rock", ':', genre);

Not really a very good example of usage but I hope you get the idea.

Regards,

Brad KF7FER

Some code samples that have String operations you might use. You could capture the String, check to see if contains key words like "TITLE", find the location of the colon : in the String, then capture the remainder of the String starting two places beyond the location of the colon :.

//zoomkat 3-5-12 simple delimited ',' string  
//from serial port input (via serial monitor)
//and print result out serial port

String readString, data;
int CD, CM, CT, CS, BR; 

void setup() {
  Serial.begin(9600);
  Serial.println("serial delimit test 1.0"); // so I can keep track of what is loaded
}

void loop() {

  //expect a string like CD01,CM01,CT01,CS03,BR255,

  if (Serial.available())  {
    char c = Serial.read();  //gets one byte from serial buffer
    
    if (c == ',') {
      Serial.println(readString); //prints string to serial port out
      
      if(readString.indexOf("CD") >=0) {
        data=readString.substring(2);
        Serial.print("CD is: ");
        Serial.println(data);
        CD = data.toInt();
        Serial.println(CD);
        Serial.println();
      }
      if(readString.indexOf("CM") >=0) {
        readString=readString.substring(2);
        Serial.print("CM is: ");
        Serial.println(readString);
        CM = readString.toInt();
        Serial.println(CM);
        Serial.println();
      }
      if(readString.indexOf("CT") >=0) {
        readString=readString.substring(2);
        Serial.print("CT is: ");
        Serial.println(readString);
        CT = readString.toInt();
        Serial.println(CT);
        Serial.println();
      }
       if(readString.indexOf("CS") >=0) {
        readString=readString.substring(2);
        Serial.print("CS is: ");
        Serial.println(readString);
        CS = readString.toInt();
        Serial.println(CS);
        Serial.println();
      }
       if(readString.indexOf("BR") >=0) {
        readString=readString.substring(2);
        Serial.print("BR is: ");
        Serial.println(readString);
        BR = readString.toInt();
        Serial.println(BR);
        Serial.println();
      }
      
      //do some stuff

      readString=""; //clears variable for new input
      data=""; 

    }  
    else {     
      readString += c; //makes the string readString
    }
  }
}
//zoomkat 3-5-12 simple delimited ',' string  
//from serial port input (via serial monitor)
//and print result out serial port

String readString, substring;
//String servo1;
int loc; 

void setup() {
  Serial.begin(9600);
  Serial.println("serial delimit test 1.0"); // so I can keep track of what is loaded
}

void loop() {

  //expect a string like AA BB01/10/2014 CC12:23:25 DD32.2 EE5432 FF54.35,

  if (Serial.available())  {
    char c = Serial.read();  //gets one byte from serial buffer
    //if (c == '\n') {  //looks for end of data packet marker

    if (c == ',') {
      Serial.println(readString); //prints string to serial port out
      //do stuff      
      loc = readString.indexOf("BB");
      //Serial.println(loc);
      substring = readString.substring(loc+2, loc+12);
      Serial.print("date is: ");
      Serial.println(substring);

      loc = readString.indexOf("CC");
      //Serial.println(loc);
      substring = readString.substring(loc+2, loc+11);
      Serial.print("time is: ");
      Serial.println(substring);

      loc = readString.indexOf("DD");
      //Serial.println(loc);
      substring = readString.substring(loc+2, loc+6);
      Serial.print("DD is: ");
      Serial.println(substring);

      loc = readString.indexOf("EE");
      //Serial.println(loc);
      substring = readString.substring(loc+2, loc+6);
      Serial.print("date is: ");
      Serial.println(substring);

      loc = readString.indexOf("FF");
      //Serial.println(loc);
      substring = readString.substring(loc+2);
      Serial.print("FF is: ");
      Serial.println(substring);      

      readString=""; //clears variable for new input
      substring=""; 

    }  
    else {     
      readString += c; //makes the string readString
    }
  }
}
      if(readString.indexOf("CD") >=0) {
        data=readString.substring(2);

While I'm not an advocate of the String class, I do want to point out some things here.

Notice that the substring starts at position 2 (so, positions 0 and 1 get skipped). Notice too that the string in the indexOf() call is 2 characters long. Finally, notice that the code assumes that the "CD" actually starts in position 0.

A somewhat more robust way of getting the data after a known string would be:

String lookFor = "CD: ";
int pos = readString.indexOf(lookFor);
if(pos > 0)
{
   data = readString.substring(pos + lookFor.length());
}

If readString contains "CD: Billy Joel's Greatest Hits", pos will be 0, and lookFor.length() will be 4, so data will be the substring starting at position 4, meaning that 0, 1, 2, and 3 ("CD: ") are skipped.

If readString cotains "My CD: Billy Joel's Greatest Hits", pos will be 3, and lookFor.length() will be 4, so data will be the substring starting at position 7, meaning that 0 to 6 ("My CD: ") are skipped.