Split a value in 2 that i receive from serial

Hello,

Looking since hours to find a solution, i did a android app with app inventor to be able to communicate with bluetooth to my Arduino with a HC-06, all work fine for the communication.
In my app i have a one button and one slider.
The button send a state of 1 or 0 when click, message send to Arduino is : button=1 or button=0
That easy and work perfect.
But the slider is the issue, it send a value from 0 et 100 depending of the position, so he send "slider=x" x is the number of the slider position between 0 and 100, the Arduino receive it with no problem, but then i don't know how to take only the value after the = and use it.

Here the part of the code :

     if(message=="button=1"){ // If message is button=1 then modeAM=1
        modeAM=1;
        Serial1.println("ModeAM=1");
      }
      else if (message=="modeam=0"){   // If message is button=1 then modeAM=0
        modeAM=0;
        Serial1.println("ModeAM=0");
      }

That the part for the button that work fine not what need to do for the slider.

        if (message=="slider=value_of_the_slider"){  // If message is slider=slider=value_of_the_slider then SliderP=slider=value_of_the_slider
        SliderP=value_of_the_slider;
        Serial1.println("Slider=value_of_the_slider");
      }

So you can see i need to be able to take the value_of_the_slider only and say SliderP=value_of_the_slider

But i can't find the right way to do it, can anybody help me ?
Thanks a lot

I am guessing that you are using Strings. If so, then look at the charAt() indexOf(), substring() and toInt() functions of the String library

EDIT : correct function name

Yes, i using Strings.
Ok thanks, will check that.
If anybody have a example code with similar use, that would be perfect :wink:

try something like this

String b = "button=1";
String s = "slider=100";

void parseCommand(String & c) {
  int e = c.indexOf('=');
  String command = c.substring(0, e);
  String value = c.substring(e + 1);
  Serial.print("Command = ["); Serial.print(command); Serial.println("]");
  Serial.print("value = ["); Serial.print(value); Serial.print("] => ");  Serial.println(value.toInt());
}

void setup() {
  Serial.begin(115200); Serial.println();
  parseCommand(b);
  parseCommand(s);
}

void loop() {}

you can use value.toInt() if you want to get the value as a number instead of a String

this assumes a perfectly formatted command and is not really memory efficient.

Thanks for help, i will try it this way

consider a more generic approach i've used for decades

button0=1
      0  button0
      1  1
button1=0
      0  button1
      0  0
slider=123
      0  slider
    123  123

code

char s [80];

#define MaxTok  10
char *toks [MaxTok];
int   vals [MaxTok];

// -----------------------------------------------------------------------------
int
tokenize (
    char       *s,
    const char *sep )
{
    unsigned n = 0;
    toks [n] = strtok (s, sep);
    vals [n] = atoi (toks [n]);

    for (n = 1; (toks [n] = strtok (NULL, sep)); n++)
        vals [n] = atoi (toks [n]);

    return n;
}

// -----------------------------------------------------------------------------
void dispToks (
    char * toks [])
{
    char s [40];
    for (unsigned n = 0; toks [n]; n++)  {
        sprintf (s, " %6d  %s", vals [n], toks [n]);
        Serial.println (s);
    }
}

// -----------------------------------------------------------------------------
void loop ()
{
    if (Serial.available ())  {
        int n = Serial. readBytesUntil ('\n', s, sizeof(s));
        s [n] = 0;      // terminate string

        Serial.println (s);
        tokenize (s, "=");
        dispToks (toks);
    }
}

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

much better than using the String class for sure !

side note - your tokenize() function should return an unsigned, not an int probably and MaxTok would be better as a const unsigned rather than a #define. (size_t is usually the designated type for this type of work)

The sketch works well.
Output:

Command = [button]
value = [1] => 1
Command = [slider]
value = [100] => 100

sure - but String class would not be my favorite option to handle this.

Instead of reading a value and then splitting it, you could also read two values instead:

#define MAX_NAME 20
#define MAX_VALUE 30
char name[MAX_NAME] = {0};
char value[MAX_VALUE] = {0};

bool receive_name_value()
{
  if (!Serial.available()) return false;
  static byte state = 0;
  static byte buf_idx = 0;
  if (state == 2)
  {
    //New pair
    state = 0;
    buf_idx = 0;
    name[0] = 0;
    value[0] = 0;
  }
  char c = Serial.read();
  if ((c == '=') && (state == 0))
  {
    name[buf_idx] = 0;
    state = 1;
    buf_idx = 0;
  }
  else if (c == '\n')
  {
    if (state == 0) name[buf_idx] = 0;
    else if (state == 1) value[buf_idx] = 0;
    state = 2;
  }
  else if (c != '\r')
  {
    if ((state == 0) && (buf_idx < MAX_NAME - 1)) name[buf_idx++] = c;
    else if ((state == 1) && (buf_idx < MAX_VALUE - 1)) value[buf_idx++] = c;
  }
  return (state == 2);
}

void loop()
{
  if (receive_name_value())
  {
    Serial.print("name=");
    Serial.println(name);
    Serial.print("value=");
    Serial.println(value);
  }
}

EDIT: Made the function non-blocking..

with code posted in #6 and " " separator

vals 1 2 3 4
      0  vals
      1  1
      2  2
      3  3
      4  4

does that actually work?

if you call it once, and there is only 1 byte in the Serial buffer yet, you'll fill up the buffer with -1 (and it's blocking)

if you call it multiple times you reset the strings at each call

If anything is available, the function will block until '\n' is received. The function can be made un-blocking with minor modifications.

EDIT: My bad, edited previous post..

consider a more data driven approach
... also consider using Serial.readBytesUntil())

vals 1 2 3 4
      0  vals
      1  1
      2  2
      3  3
      4  4
char s [80];

int   a, b, c;
float x, y, z;

void myFunc (void)  { Serial.println ("  myFiunc:");  }

// -------------------------------------
typedef void(*Func_t)(void);
enum { None, Int, Func };

struct Vars {
    int         type;
    void *      val;
    const char *label;
} vars [] = {
    { Int,   &a, "a" },
    { Int,   &b, "b" },
    { Int,   &c, "c" },
    { Func,  (void*) myFunc, "myFunc" },
};
const size_t  N_Vars = (sizeof(vars)/sizeof(Vars));

// -------------------------------------
void
varsDisp (void)
{
 // Serial.println (__func__);
    Vars *p = vars;
    for (size_t n = 0; n < N_Vars; n++, p++)  {
        if (Int == p->type)  {
            sprintf (s, "   %6d  %s", *(int*)p->val, p->label); 
            Serial.println (s);
        }
        else if (Int == p->type)  {
            sprintf (s, "   %6s  %s", "Func", p->label); 
            Serial.println (s);
        }
    }
}

// -------------------------------------
enum { OK, Err = -1 };
int
varsSet (
    const char *label,
    const char *valStr)
{
 // Serial.println (__func__);
    Vars *p = vars;
    for (size_t n = 0; n < N_Vars; n++, p++)  {
        if (! strcmp (p->label, label))  {
            if (Int == p->type)
                *(int*)p->val = atoi(valStr);
            else if (Func == p->type)
                ((Func_t) p->val) ();
            return OK;
        }
    }
    return Err;
}

// -----------------------------------------------------------------------------
#define MaxTok  10
char *toks [MaxTok];
int   vals [MaxTok];

// -----------------------------------------------------------------------------
int
tokenize (
    char       *s,
    const char *sep )
{
    unsigned n = 0;
    toks [n] = strtok (s, sep);
    vals [n] = atoi (toks [n]);

    for (n = 1; (toks [n] = strtok (NULL, sep)); n++)
        vals [n] = atoi (toks [n]);

    return n;
}

// -----------------------------------------------------------------------------
void dispToks (
    char * toks [])
{
    char s [40];
    for (unsigned n = 0; toks [n]; n++)  {
        sprintf (s, " %6d  %s.", vals [n], toks [n]);
        Serial.println (s);
    }
}

// -----------------------------------------------------------------------------
void loop ()
{
    if (Serial.available ())  {
        int n = Serial. readBytesUntil ('\n', s, sizeof(s));
        s [n] = 0;      // terminate string

        Serial.println (s);
        n = tokenize (s, " ");
     // dispToks (toks);

        if (! strcmp ("disp", toks [0]))
            varsDisp ();
        else
            varsSet (toks [0], toks [1]);
    }
}

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

Thanks a lot for all the help and different solution, the more generic approach look to be good, will give some try and see how i can get it to work.
Yesterday i find a way to add a spliter so i can get multiple value in one message separate with & here the code i add :

void slipter(String inputString){

      char inputChar[inputString.length()+1] ;
      inputString.toCharArray(inputChar,inputString.length()+1);
 
        // Read each command pair 
        char* command = strtok(inputChar, "&");
        while (command != 0)
        {
            // Split the command in two values
            char* valueCommand = strchr(command, '=');
            if (valueCommand != 0)
            {
                *valueCommand = 0;
                ++valueCommand;         
                  if(String(command) == "slider"){
                    SliderP = String(valueCommand).toInt();
                    
                  }
                  else if(String(command) == "button"){
                  modeAM = String(valueCommand) ;
                    
                 }
            }
            // Find the next command in input string
            command = strtok(0, "&");
        }
}

Look to work fine, will try the other one you all send me that will make me learn a lot, and already make me learn so much :wink:
Thanks to all

If you are using an AVR based MCU you will get in trouble when using the "String" class. With my edited approach in #10, you may use both "&" and "\n" as separator of key-value pairs with a minimal change:

...
else if ((c == '\n') || (c == '&'))
...

My approach uses minimal (statically allocated) memory and does its work on-the-fly and should thus be faster than a read-first-then-process solution.

I use it on a STM32F103C8T6

Ok, good to know, i will try you approach and see if i can get it to work.
I learning a lot, thanks

As you call this function passing the original String by copy, you already duplicated the content once in inputString

Then you duplicate it again in a cString

That’s 3 versions of the same input. Not really memory friendly. strtok() does modify the cString so it depends on your use case wether or not you need to work on a copy and there are ways around this.

In a nutshell 3 versions of the same data is 2 too many :wink:

Ok, and how to change that to not make 2 copy ?
Sorry, i learning, not really sure how to do.
Thanks a lot

staying with Strings you could save one copy easily by declaring your function like this:

void slipter(String & inputString) { //NOTICE THE & before the parameter, it means pass by reference
  ...

when you call the function now, instead of making a copy of the String you'll get the original. You still duplicate it in your inputChar array but at least you saved one copy.

if you want to get rid of that copy, the easiest way would be to go with one of the code that does not use the String class at all.

you could hack your way by modifying the actual buffer that is used by your String, (because we know the String class actually maintains a proper cString underneath) but it's definitely not clean as you are not supposed to mess around with the underlying data of an object (strtok() will replace some chars with a null char) but it would go something like this

void slipter(String & inputString) { //NOTICE THE & before the parameter, it means pass by reference
  char *  inputChar = (char *) inputString.c_str(); // casting a const char * to a char * is not a great idea, undefined behaviour could happen
  ...