Finding the index of a symbol in unsigned char and extracting contents?

I have an Arduino function that must take a byte type as an argument.

    // payload is A0:433.33

    void someFunction(byte* payload) {
    
        char* pos;
        pos = strstr(payload, ":");
        // want to eventually end up with a variable containing 'A0'
        // and another containing '433.33'

    }

The payload will be something like A0:433.22

I want to extract A0 and 433.22, put them into variables, etc

The following code is what I've tried to find the position of the :. It doesn't work because strstr only works on const char*, not unsigned char* (the compiler gives me an error). I was going to use that index position to extract whatever is between 0 and the index of : (A0), and whatever is between the index of : and the total length of the array (433.22).

I also don't know what I would use to "extract" partials from an unsigned char*. With strings you would probably just use substr. Stack Overflow says I should just cast this as a string, but apparently strings should always be avoided with Arduino.

fuzzybabybunny:
I have an Arduino function that must take a byte type as an argument.

    void someFunction(byte* payload) {

No, it takes a pointer to a byte.

Is it a properly NULL terminated 'C' string? If so, strtok() will do the job (with proper casting).

Are you sure it gives you an ERROR and not a WARNING?

sketch_sep22a.ino: In function 'void someFunction(byte*)':
sketch_sep22a.ino:7:28: warning: invalid conversion from 'byte* {aka unsigned char*}' to 'const char*' [-fpermissive]
   pos = strstr(payload, ":");
                            ^
In file included from Arduino.h:25:0,
                 from sketch_sep22a.ino.cpp:1:
string.h:557:14: note: initializing argument 1 of 'char* strstr(const char*, const char*)'
 extern char *strstr(const char *, const char *) __ATTR_PURE__;
              ^
sketch_sep22a.ino:6:9: warning: variable 'pos' set but not used [-Wunused-but-set-variable]
   char* pos;
         ^

In this thread of yours I never mentioned strstr.

Why did you start a new thread?

To expand on answer #1, if you use strtok() to get the bit before the colon then you know how long that string is. Increment the pointer to payload by the length of the first string plus the null terminator to point to the bit after the colon.

char buf1[10]; // payload before colon
char buf2[10]; // payload after colon


void setup()
{
  Serial.begin(9600);
  someFunction((byte *)"A0:433.33");

  Serial.println(buf1);
  Serial.println(buf2);

}

void loop()
{

}

// payload is A0:433.33

void someFunction(byte* payload) {

  // copy up to colon
  strcpy(buf1, strtok(payload, ":"));

  // move past colon and copy rest of payload
  strcpy(buf2, payload + strlen(buf1) + 1);


  // want to eventually end up with a variable containing 'A0'
  // and another containing '433.33'

}

Solution with strchr(). Posted as an alternative and to show how to cast a byte array to a character array.

void setup()
{
  Serial.begin(115200);
  
  // received payload
  byte *payload = (byte*)"A0:433.33";
  
  // pointer to value in payload; will be 'calculated'
  char *val;

  // find colon; note the casting to a character pointer
  val = strchr((char*)payload, ':');
  // if colon found
  if (val != NULL)
  {
    // replace colon by null terminator
    *val = '\0';
    // let val point to the actual value
    val++;

    // print the 'label'; again note the cast to a character pointer
    Serial.println((char*)payload);
    // print the value
    Serial.println(val);
  }
  else
  {
    Serial.println("Could not  find colon");
  }
}

void loop()
{
}

I'll take the easy way out and let strtok() do the work:

void someFunction(byte *);

void setup() {
  const char dog[] = "A0:433.33";
  
  Serial.begin(115200);
  delay(2000);

  someFunction((byte *)dog);
}

void loop() {
}

void someFunction(byte *payload) {
  char *ptr, tempBuffer[25];
  strcpy(tempBuffer, (const char *)payload);  // strtok changes the string, so make a local copy
  ptr = strtok(tempBuffer, ":");
  Serial.print("First Token = ");Serial.println(ptr);
  ptr = strtok(NULL, ":");
  Serial.print("Second Token = ");Serial.println(ptr);
}

gfvalvo:
I'll take the easy way out and let strtok() do the work:

void someFunction(byte *);

void setup() {
  const char dog[] = "A0:433.33";
 
  Serial.begin(115200);
  delay(2000);

someFunction((byte *)dog);
}

void loop() {
}

void someFunction(byte *payload) {
  char *ptr, tempBuffer[25];
  strcpy(tempBuffer, (const char *)payload);  // strtok changes the string, so make a local copy
  ptr = strtok(tempBuffer, ":");
  Serial.print("First Token = ");Serial.println(ptr);
  ptr = strtok(NULL, ":");
  Serial.print("Second Token = ");Serial.println(ptr);
}

I'm getting weird trailing 0's and S's now. Here is my full code:

/*
    This code connects the Wemos D1 Mini Pro to Cloud MQTT
*/

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include "ESP8266Ping.h"
#include "config.h"

/* WiFi and PubSub */

WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0;
char msg[50];
int value = 0;

/* Serial Read Parsing for recvWithStartEndMarkers() */

const byte numChars = 32;
char receivedChars[numChars];
boolean newData = false;

/* Relay Pins */

//int relayPin1 = D0;
//int relayPin2 = D1;
//int relayPin3 = D2;
//int relayPin4 = D3;
//int relayPin5 = D4;
//int relayPin6 = D5;
//int relayPin7 = D6;
//int relayPin8 = D7;

void setup() {
  Serial.begin(115200);

  // setting pinMode and initial states
//  static const uint8_t digitalPins[] = {D0, D1, D2, D3, D4, D5, D6, D7};
//  for (int i = 0; i < 8; i++) { //or i <= 4
//    pinMode(digitalPins[i], OUTPUT);
//    digitalWrite(digitalPins[i], HIGH);
//  }

  delay(10);
  setup_wifi();
  client.setServer(MQTT_SERVER, 15272);
  client.setCallback(callback);
}

void loop() {

  if (!client.connected()) {
    reconnect();
  }
  client.loop();
  
  recvWithStartEndMarkers(); //adds characters to receivedChars array
  Serial.print("payload (receivedChars) on the Arduino to be published to MQTT: ");
  Serial.println(receivedChars);
  client.publish("hydro1", receivedChars);
  newData = false;
  delay(2000);

}

void setup_wifi() {

  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(WIFI_SSID);

  WiFi.begin(WIFI_SSID, WIFI_PASS);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

  // pings a server to make sure internet is alive
  bool ret = Ping.ping("www.google.com");
  Serial.print("Pinging Google result: ");
  Serial.println(ret);

  delay(2000);
}

void callback(const char* topic, byte* payload, unsigned int length) {

  char buf1[10]; // payload before colon
  char buf2[10]; // payload after colon

  if (strcmp(topic, "hydro1") == 0) {
    Serial.print("payload received in callback from MQTT server: ");
    Serial.println((const char*) payload);
    char *ptr, tempBuffer[25];
    strcpy(tempBuffer, (const char *)payload);  // strtok changes the string, so make a local copy
    ptr = strtok(tempBuffer, ":");
    Serial.print("First Token = ");
    Serial.println(ptr);
    ptr = strtok(NULL, ":");
    Serial.print("Second Token = ");
    Serial.println(ptr);
  }

}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect("ajxfcvag", MQTT_USER, MQTT_PASS)) {
      Serial.println("connected");
      // ... and resubscribe
      client.subscribe("hydro1");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

/* Serial Read Parsing */

// adds characters to receivedChars
void recvWithStartEndMarkers() {

  static boolean recvInProgress = false;
  static byte ndx = 0;
  char startMarker = '<';
  char endMarker = '>';
  char rc;

  // if (Serial.available() > 0) {
  while (Serial.available() > 0 && newData == false) {
    rc = Serial.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;
    }
  }
}

Here is the output:

.
.
Pinging Google result: 1
Attempting MQTT connection...connected
payload (receivedChars) on the Arduino to be published to MQTT: A3:0.00
payload received in callback from MQTT server: A3:0.00.00fcvag
First Token = A3
Second Token = 0.00.00fcvag
payload (receivedChars) on the Arduino to be published to MQTT: A4:0.00
payload received in callback from MQTT server: A4:0.00.00fcvag
First Token = A4
Second Token = 0.00.00fcvag
payload (receivedChars) on the Arduino to be published to MQTT: A5:433.00
payload received in callback from MQTT server: A5:433.00.00vag
First Token = A5
Second Token = 433.00.00vag

Wondering if you have some kind of pointer abuse or buffer overrun going on. Note the line:

if (client.connect("ajxfcvag", MQTT_USER, MQTT_PASS)) {

Since callback() takes both a pointer an a length I'm guessing the contents of 'payload' is not null-terminated.

Try using strncpy() with the supplied length. You may have to explicitly add the terminator.

johnwasser:
Since callback() takes both a pointer an a length I'm guessing the contents of 'payload' is not null-terminated.

OP, From Reply #1:

gfvalvo:
Is it a properly NULL terminated 'C' string?

Looks like payload isn't null terminated.

Not quite sure how to add it to the end.

void callback(const char* topic, byte* payload, unsigned int length) {

  byte* nullTermPayload[16];

  for(int i = 0; i < length; i++){
    nullTermPayload[i] = payload[i];
  }
  nullTermPayload[length] = "\0";

};

gives

WemosD1MiniCloudMQTT.ino: In function 'void callback(const char*, byte*, unsigned int)':
WemosD1MiniCloudMQTT:102: error: invalid conversion from 'byte {aka unsigned char}' to 'byte* {aka unsigned char*}' [-fpermissive]
     nullTermPayload[i] = payload[i];
                                   ^
WemosD1MiniCloudMQTT:104: error: invalid conversion from 'const char*' to 'byte* {aka unsigned char*}' [-fpermissive]
   nullTermPayload[length] = "\0";
                           ^
exit status 1

And this

void callback(const char* topic, byte* payload, unsigned int length) {

  char nullTermPayload[16];

  for(int i = 0; i < length; i++){
    nullTermPayload[i] = payload[i];
  }
  nullTermPayload[length] = "\0";

};
In function 'void callback(const char*, byte*, unsigned int)':
WemosD1MiniCloudMQTT:104: error: invalid conversion from 'const char*' to 'char' [-fpermissive]
   nullTermPayload[length] = "\0";
                           ^
exit status 1
invalid conversion from 'const char*' to 'char' [-fpermissive]

I think I finally got it:

void callback(const char* topic, byte* payload, unsigned int length) {
  
  // create array large enough to hold null terminated payload
  char nullTermedPayload[length+2];
  // prep by clearing
  memset(nullTermedPayload, 0, sizeof(nullTermedPayload));
  // copy payload to nullTermPayload, this adds null terminator automatically
  memcpy(nullTermedPayload, payload, length);
    
  // NOTE: cannot do if(topic == "hydro1"), and in strcmp, 0 is a match. 1 is not a match.
  if (strcmp(topic, "hydro1") == 0) {

    Serial.print("payload received in callback from MQTT server: ");
    Serial.println((const char*) payload);
    Serial.print("length of the message payload: ");
    Serial.println(length);
    Serial.print("nullTermedPayload: ");
    Serial.println((const char*) nullTermedPayload);
    Serial.print("length of the nullTermedPayload: ");
    Serial.println(sizeof(nullTermedPayload));
    
    char *firstToken, *secondToken, tempBuffer[25];

    // strtok mutates the char array given it!!!
    strcpy(tempBuffer, (const char *)nullTermedPayload);

    firstToken = strtok(tempBuffer, ":");
    Serial.print("First Token = ");
    Serial.println(firstToken);

    secondToken = strtok(NULL, ":");
    Serial.print("Second Token = ");
    Serial.println(secondToken);
  }

}

But I'm still getting that weird trailing numbers and "vag" from if (client.connect("ajxfcvag", MQTT_USER, MQTT_PASS)) { when I directly println the payload. But when I output payload as HEX the trailing numbers and vag don't show up.

payload (receivedChars) on the Arduino to be published to MQTT: T1:20.25
payload received in callback from MQTT server: T1:20.25.250vag
length of the message payload: 8
nullTermedPayload: T1:20.25
length of the nullTermedPayload: 10
First Token = T1
Second Token = 20.25
payload (receivedChars) on the Arduino to be published to MQTT: A0:93.00
payload received in callback from MQTT server: A0:93.00.000vag
length of the message payload: 8
nullTermedPayload: A0:93.00
length of the nullTermedPayload: 10
First Token = A0
Second Token = 93.00

Payload is (more than likely) not null terminated so you can't print it directly. If it was null terminated, you would not have to go through the exercise of using memset and memcpy.

Further

    Serial.println(sizeof(nullTermedPayload));

This will print the size of the buffer nullTermedPayload (always two more than length). The below will print the actual length of the content and should match the length argument.

    Serial.println(strlen(nullTermedPayload));

sterretje:
Payload is (more than likely) not null terminated so you can't print it directly. If it was null terminated, you would not have to go through the exercise of using memset and memcpy.

Further

    Serial.println(sizeof(nullTermedPayload));

This will print the size of the buffer nullTermedPayload (always two more than length). The below will print the actual length of the content and should match the length argument.

    Serial.println(strlen(nullTermedPayload));

I guess I'm confused on how the trailing numbers and v-a-g from my client.connect method are making their way into the end of the payload when I attempt to print it?

It's a leftover from a previous use of payload.

Below a simple code to demonstrate what basically happens in the library

char payload[256] = "12345vag";

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

void loop()
{
  int cnt = 0;
  while(cnt<5)
  {
    if(Serial.available() > 0)
    {
      payload[cnt]=Serial.read();
      cnt++;
    }
  }
  Serial.println(payload);
}

A 256 byte payload buffer, initially filled with 12345vag. A while loop that reads data; in this case exactly 5 characters from serial monitor (set serial monitor up not to send line endings).

Enter some text in the serial monitor (e.g. 56789); the 5 received characters will overwrite the first part of the payload but not the rest. The result is 56789vag.