I need help to split a string. I'm not good at strings

HI,

I am trying to send a string via serial and parse into parts.
A "KEY"="VALUE" format, like 'Diameter=24.34', or 'height=0.242' and so on.

So I have bogged down, trying to get the number of characters for the left
I have tried searching for the delimiter using strchr()
I'm not having much luck.. I thought it returns pointer, so shouldn't I get the offset?

const int MaxChars = 45;     // Size to allocate

char strValue[MaxChars+1];   // add space for null
int index = 0;              

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

void loop() {
  if( Serial.available()){
    char ch = Serial.read();
    if(index <  MaxChars ){
      strValue[index++] = ch; 
      if (ch == 10) {
        strValue[index] = '\0';           // terminate the string with a Null
        Serial.print(strValue);           // Echo string back

        index = (strchr(strValue,"=") - strValue);
        Serial.println(index);

        index = 0;
      }
    }
    else{                         // here for buffer full 
      strValue[index] = '\0';     // terminate the string
      index = 0;                  // loop the data.  Likely cat sleeping on keyboard
    }
  }
 }

It is not clear what the format of the string is from your description, but the strtok() function sounds like a likely way of parsing it is it is delimited

Do you send the data continuously or one key pair at a time?

A=B C=D E=F G=H ....

or

A=B
-- pause --
C=D
-- pause --
E=F
-- pause --
G=H
-- pause --

Have a look at the parse example in Serial Input Basics

The parsing process will be much the same regardless of where the string of text comes from.

...R

HI,

the SafeString-library offers a lot of useful functions for things like this parsing

here is a demo-code that show how to separate the ID=val pairs from a string a nd how to extract the value
at the botoom of the file there is the stripped down version without any explanation.

there are a lot of examples with it. But IMHO the examples are too complex to make it easy to understand.
there should be more examples like this one

/*
  Tokenizing SafeStrings and converting to numbers
  Examples of how to use the nextToken() and toFloat() to parse ini-file line

  by Stefan Ludwig
  This example code is in the public domain.
*/

#include "SafeString.h"

int PosOfSeparator;
float myFloat;

createSafeString(separator,  3, "=" );  
createSafeString(delimiters, 4, " ;\n" ); // indicates end of ID=val  charactersequence

createSafeString(line, 64, "diameter=24.34; height=0.242\n");

createSafeString(sfLine,64); // a SafeString large enough to hold the whole line.
createSafeString(One_ID_Val,32); 
createSafeString(value, 64);

 
void setup() {
  // Open serial communications and wait a few seconds
  Serial.begin(115200);

  for (int i = 0; i < 30; i++) {
    Serial.println(); // clear serial monitor throuh a lot of empty lines
  }
  
  Serial.println();
  Serial.println();
  Serial.println();
  Serial.println(F("Using SafeString nextToken() to parse the string line ID=value "));
  Serial.println(F("SafeString::setOutput(Serial); // verbose"));
  Serial.println();
  // see the SafeString_ConstructorAndDebugging example for debugging settings
  SafeString::setOutput(Serial); // enable full debugging error msgs

  sfLine = line; // store value into sfLine for processing
  Serial.print("0: before starting parsing sfLine contains #"); 
  Serial.print(sfLine); 
  Serial.println("#");
  Serial.println();

  sfLine.nextToken(One_ID_Val,delimiters); 
        
  Serial.println("1: after sfLine.nextToken(One_ID_Val,delimiters))");  
  Serial.print(": the first ID=val is stored into variable 'One_ID_Val' #");
  Serial.print(One_ID_Val);
  Serial.println("#");

  Serial.print("and the first ID=val is REMOVED from the processed String 'sfLine' #");
  Serial.print(sfLine); 
  Serial.println("#");
  
  Serial.println("so the parsing for the next ID=val can start with just the same command");
  Serial.println();
  Serial.println("we are interested in the value behind the ID ");

  PosOfSeparator = One_ID_Val.indexOf('=', 0);
  Serial.println("PosOfSeparator = One_ID_Val.indexOf('=', 0); ");

  Serial.print("returns the position of the separator inside of the string 'One_ID_Val'");
  Serial.print("which is ");
  Serial.println(PosOfSeparator);
  Serial.println();
    
  One_ID_Val.substring(value, PosOfSeparator + 1);
  Serial.print("now we can extract the value which starts behind the separator ");
  Serial.print("which is position PosOfSeparator + 1 =");
  Serial.println(PosOfSeparator + 1);

  Serial.print("and this is done using the substring-function");
  Serial.println("One_ID_Val.substring(value, PosOfSeparator + 1"); 

  Serial.print("now variable 'value' contains #"); 
  Serial.print(value);
  Serial.println("#"); 
  
  Serial.println("this string can be converted to a float using the toFloat-function");

  Serial.println("value.toFloat(myFloat)) ");
  value.toFloat(myFloat);
  Serial.print("myFloat=");
  Serial.print(myFloat,4);
  Serial.println(); 

  Serial.println(); 
  Serial.println("----------------------------------------------------------"); 
  Serial.println("round two with the same procedure"); 
  Serial.println(); 

  sfLine.nextToken(One_ID_Val,delimiters); 
        
  Serial.println("2: after sfLine.nextToken(One_ID_Val,delimiters))");  
  Serial.print(": the first ID=val is stored into variable 'One_ID_Val' #");
  Serial.print(One_ID_Val);
  Serial.println("#");

  Serial.print("and the first ID=val is REMOVED from the processed String 'sfLine' #");
  Serial.print(sfLine); 
  Serial.println("#");
  
  Serial.println("so the parsing for the next ID=val can start with just the same command");
  Serial.println();
  Serial.println("we are interested in the value behind the ID ");

  PosOfSeparator = One_ID_Val.indexOf('=', 0);
  Serial.println("PosOfSeparator = One_ID_Val.indexOf('=', 0); ");

  Serial.print("returns the position of the separator inside of the string 'One_ID_Val'");
  Serial.print("which is ");
  Serial.println(PosOfSeparator);
  Serial.println();
    
  Serial.print("now we can extract the value which starts behind the separator ");
  Serial.print("which is position PosOfSeparator + 1 =");
  Serial.println(PosOfSeparator + 1);

  Serial.print("and this is done using the substring-function");
  Serial.println("One_ID_Val.substring(value, PosOfSeparator + 1"); 

  One_ID_Val.substring(value, PosOfSeparator + 1);
  Serial.print("now variable 'value' contains #"); 
  Serial.print(value);
  Serial.println("#"); 
  
  Serial.println("this string can be converted to a float using the toFloat-function");
  Serial.println("value.toFloat(myFloat)) ");
  value.toFloat(myFloat);
  Serial.print("myFloat=");
  Serial.print(myFloat,4);
  Serial.println(); 

  Serial.println(F("After processing by nextToken() the Input line is empty because nextToken() removes the tokens and delimiters from the line being processed."));
  sfLine.debug();  
}

void loop() {
  // nothing here
}


// Without all the explanation and comments this boils down to
void ExtractFloatFromString() {
  
  sfLine = line; // store value into sfLine for processing
  sfLine.nextToken(One_ID_Val,delimiters); 
  PosOfSeparator = One_ID_Val.indexOf(separator, 0);
  One_ID_Val.substring(value, PosOfSeparator + 1);
  value.toFloat(myFloat);
  Serial.print("myFloat=");
  Serial.print(myFloat,4);
  Serial.println(); 
}

best regards Stefan

there should be more examples like this one

Why make things complicated ?
Try this

char line[64] = {"diameter=24.34; height=0.242\n"};

void setup()
{
  Serial.begin(115200);
  while (!Serial);
  char * ptr = strtok(line, "=");  //skip to first =
  ptr = strtok(NULL, ";");  //get the diameter as a string
  float diameter = atof(ptr); //convert to a float (if needed)
  ptr = strtok(NULL, "=");  //skip to next =
  ptr = strtok(NULL, ";");  //get the height as a string
  float height = atof(ptr); //convert to a float (if needed)
  Serial.print("diameter : ");  //print the results
  Serial.println(diameter, 2);
  Serial.print("height : ");
  Serial.println(height, 3);
}

void loop()
{
}

It would be even easier if the input string were formatted differently because we don't actually need the text, only the values delimited by a separator character

UKHeliBob:
Why make things complicated ?
Try this

char line[64] = {"diameter=24.34; height=0.242\n"};

void setup()
{
Serial.begin(115200);
while (!Serial);
char * ptr = strtok(line, "="); //skip to first =
ptr = strtok(NULL, ";"); //get the diameter as a string
float diameter = atof(ptr); //convert to a float (if needed)
ptr = strtok(NULL, "="); //skip to next =
ptr = strtok(NULL, ";"); //get the height as a string
float height = atof(ptr); //convert to a float (if needed)
Serial.print("diameter : "); //print the results
Serial.println(diameter, 2);
Serial.print("height : ");
Serial.println(height, 3);
}

void loop()
{
}



It would be even easier if the input string were formatted differently because we don't actually need the text, only the values delimited by a separator character

the complicated thing about your code is the missing of a detailed explanation how it works
Sure you added comments. But anyway for a beginner it is hard to understand.
To be a good explanator it needs two things:
1: a lot of knowledge about programming
and
2: knowledge about how to present it it that it becomes easy to understand for beginners.
This is what I call knowledge about beginners difficulties
you use one variable called "ptr" sure a pointer. Pointer to what? and it seems it's used to point to minimum three different things. So my opinion is:
hide-away all "magic" through using a library that offers functions that are easy to use and provide short examples that are easy to understand explaining the use of the functions.
or use self-explaining names and a new variable for every purpose and very detailed explanations how it works.
These two versions make it easy to understand for beginners

best regards Stefan

You can combine the following functions:

Please note that when using the String object, it had better to use String.reserve() to avoid memory fragmentation

you use one variable called "ptr" sure a pointer. Pointer to what? and it seems it's used to point to minimum three different things

If you want a version with more comments you only have to ask
In the meantime just rename the ptr variable to pointerToPreviouslyParsedData

hide-away all "magic" through using a library that offers functions that are easy to use

You mean like the strtok() function ?

The main confusion when using the strtok() function is the use of NULL after the first time of splitting a string but you can just do it, like using the accepted syntax of other functions, or you can dig deeper. It depends on your goals

I am afraid that you and I are destined to disagree

hzrnbgy:
Do you send the data continuously or one key pair at a time?

A=B C=D E=F G=H ....

or

A=B
-- pause --
C=D
-- pause --
E=F
-- pause --
G=H
-- pause --

Sent on single line.. | diameter=21.21 (apended line feed)

So diameter and height are not on the same line ?

Do you have control over the format of the data being sent ?

UKHeliBob:
It would be even easier if the input string were formatted differently because we don't actually need the text, only the values delimited by a separator character

So let's say that there are 10 key words for parameters and each may have a numeric value. On the end of the serial port is a human, able to entering parameters. So I was intending to have conversation like ...
diameter=24.87
height=.088
speed=23
Begin
Stop
?
... I started to look at the first case, with a following parameter... and here I am stuck...
Thanks

UKHeliBob:
So diameter and height are not on the same line ?

Do you have control over the format of the data being sent ?2

Yes. Just needs to be kind of mnemonic. Dia 32.3 or Diameter 32.3. I just thought a '=' was better than space.
Speed=23 is easier than Spe 23, but both would be fine.

I was taught that strings in C are char arrays null terminated.
So if I get ptr to the parsing location from strchr(), and the pointer to a string element string []
If I then subtract the 2 pointers, shouldnt I get the length?
I thought that is what this line in code I provided did.

index = (strchr(strValue,"=") - strValue);

Will the human be entering the text as well as the numbers ?

All you need at the receiving end is the delimited numbers so a human could be prompted for diameter, height and speed and you would receive 24.87/.088/23 This could then be split into individual variables, most easily elements of an array with a while loop using strtok(). With a bit of forethought the array elements could even be given readable names such as data[DIAMETER], data[HEIGHT] and data[SPEED] for use later in the program with no need to copy them to separate variables. Do you actually need to use the values beyond printing them ?

a separator character like "=" or "/" or ";" will do the job regardless of how long the identifier is.

If it is acceptable that the user will enter the values in a fixed order
always first diameter
always second height
always third speed
etc.
it can be simplified to what UKHeliBob suggested 24.87/.088/23

if you give us an overview of the whole application who is the user? a well trained machinist or changing persons with low knowledge
the meaning of the values? is this a CNC-mill? a spring-bending machine? or whatever
maybe even better fitting solutions can be found

Just to give an example:
if you only use a small set of parameters 5-10 you could use buttons to choose them
or a barcode-scanner or a small keypad entering a number related to the parameter-set.

If the parameters vary in wide borders from diamater 1.45, 1,46, 1,47 up to 100,0 100,01 etc.
your method of entering the numbers fits good.

or something inbetween like som ebuttons to choose the parameter including giving visual feedback on a display
saying "Enter diamater" "Enter height" etc.

best regards Stefan

UKHeliBob:
Will the human be entering the text as well as the numbers ?

Do you actually need to use the values beyond printing them ?

Yes human, I hope. Yes the echo back to serial is just for debug. Intent is once a parameter is accepted a confirming message would be returned so user knows they did not fumble finger. The values will be used later.
I like the idea of data [n] with constants as names.

The first thing to do is to determine what data is to be entered and how. There can be prompts for each data item to be entered one at a time or an experienced used could enter them all at once separated by say a space, but this will be unreliable.

Will all of the values always be entered or would a blank entry be an indicator to use the current value or maybe a default value ?

What data type will the values be and are they all the same ? If floats then how many decimal places are required ? If decimal places are not entered for a float should they default to zeroes ?

What validation of entered data will be applied and where ? On entry of the data or receipt of data for processing ? If the latter then how will the user be informed and will they be required to re-enter all of the data or only the erroneous ones ?

Will the previous or default values be shown to the user to be accepted to save re-entry of data and if so can the previous/default value be edited or adjusted (+ or - key perhaps) or must it be entered again in full ?

What will be the method of editing or re-entering a value before sending the message if the user spots an error in a value entered ?

Sorry for all the questions but from experience you cannot trust a user to do what you ask or want. All of this detail will inform the method used to format the message, transmit it, parse and verify it whatever method is used

I appreciate the help and suggestions. I am not a beginner or newb at programming. I have been programing in various languages off and on for over 30 years. I just don't use C very much, and have never been good with pointers. That said, I am trying to understand why the little bit of code here does not return the following 2 lines to the serial monitor.
diameter=24.34
8
I get -256 instead of 8
What am I doing wrong with the pointers?

Thanks again.

const int MaxChars = 45;     // Size to allocate

char strValue[MaxChars+1] = {"diameter=24.34\n"};   
int index = 0;     //             

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

void loop(){
    Serial.print(strValue);           // Echo string back
    index = (strchr(strValue,"=") - strValue);
    Serial.println(index);
    delay (5000);
 }

Split form a totally unrelated topic

Merged in a misposted reply