Extract an embedded int from char*

Hi
Maybe this has been answered several times, but I haven't been able to find an answer that helps me. I'm not very code skilled, more of a DIY enthusiast with good patience. My apologies if double posting or using the wrong forum section.

I have an MCP23017 expansion board hooked up and use MQTT to send commands turning on/off the relays connected to the MCP.

My subscription is created using a wildcard

const char* controllerSubscription = "Controller/+/command";

Everything is working fine - I can receive MQTT commands and turn the relays on/off and publish back a state when successful. A topic I use in my callback may look like this

Controller/MCP23017P0/command

Where the "0" between "P" and "/command" is identifying the MCP pin to get the status change and the integer I want to get my hands on.

My issue is that I would like to have a more elegant code and avoid duplicating it. Sure, I could write 16 if statements using strcmp() and have the exact same code within each section. But, that will take up a lot of space and be cumbersome to maintain when doing changes.

So, I'm thinking that extracting the int "0" (in the example above) could allow me to skip the if section and work with the extracted int and concatenating when needed.

There are 16 pins on the MCP expander, so the int I need to extract can be 0-15. Any help would be appreciated, thanks.

void setup()
{
  Serial.begin(115200);
  while (!Serial);
  Serial.println(extract("Controller/MCP23017P0/command"));
  Serial.println(extract("Controller/MCP23017P15/command"));
}

void loop()
{
}

int extract(char * data)
{
  char * ptr = strtok(data, "P");  //skip to first P
  ptr = strtok(NULL, "P");  //skip to second P
  ptr = strtok(NULL, "/");  //get the data between P and /
  return atoi(ptr);
}

Neat Bob if the MCP does not change.
My attempt was

// https://forum.arduino.cc/t/extract-an-embedded-int-from-char/867683

// download SafeString V4.1.5+ library from the Arduino Library manager or from
// https://www.forward.com.au/pfod/ArduinoProgramming/SafeString/index.html
#include "SafeString.h"

char input0[] = "Controller/MCP23017P0/command";
char input15[] = "Controller/MCP23017P15/command";
char inputQ[] = "Controller/MCP23017Q15/command";


int parsePin(char* cmd) {
  cSFP(sfCmd, cmd); // warps cmd in a SafeString
  int idx = sfCmd.indexOf("/command");
  if (idx < 0) {
    Serial.print(F("Parsing error for ")); Serial.println(cmd);
    return -1;
  }
  sfCmd.removeFrom(idx); // remove the /command
  idx = sfCmd.lastIndexOf('P');
  if (idx < (sfCmd.length() - 3)) { // expecting Pxx at end of input
    Serial.print(F("Parsing error looking for last P in ")); Serial.println(cmd);
    return -1;
  }
  sfCmd.removeBefore(idx + 1); // removes P as well
  // should have pin number left
  int pinNo = -1;
  if (!sfCmd.toInt(pinNo)) {
    Serial.print(F("Invalid pinNo ")); Serial.println(sfCmd);
    return -1;
  } // else
  return pinNo;
}

int pin;
void setup() {
  Serial.begin(9600);
  for (int i = 10; i > 0; i--) {
    Serial.print(' '); Serial.print(i);
    delay(500);
  }
  Serial.println();
  SafeString::setOutput(Serial); // for error /debug msgs

  pin = parsePin(input0);
  if (pin < 0) {
    Serial.println(F("Invalid pinNo "));
  } else {
    Serial.print(F("Parsed pinNo:")); Serial.println(pin);
  }
  pin = parsePin(input15);
  if (pin < 0) {
    Serial.println(F("Invalid pinNo "));
  } else {
    Serial.print(F("Parsed pinNo:")); Serial.println(pin);
  }
  pin = parsePin(inputQ);
  if (pin < 0) {
    Serial.println(F("Invalid pinNo "));
  } else {
    Serial.print(F("Parsed pinNo:")); Serial.println(pin);
  }
}

void loop() {
}

OR

int parsePin(char* cmd) {
  Serial.print("cmd:"); Serial.println(cmd);
  cSFP(sfCmd, cmd); // warps cmd in a SafeString
  cSF(token, 20);
  sfCmd.firstToken(token, '/');
  sfCmd.nextToken(token, '/');
  int idx = token.lastIndexOf('P');   // token should now have M....P..
  token.removeBefore(idx + 1); // removes P as well
  // should have pin number left
  int pinNo = -1;
  if (!token.toInt(pinNo)) {
    Serial.print(F("   ")); Serial.print(token); Serial.println(F(" is not a number"));
    return -1;
  } // else
  sfCmd.nextToken(token, '/'); // make sure this is a command
  if (token != "command") {
    Serial.print(F("   Not a command :")); Serial.println(token);
    return -1;
  }
  return pinNo;
}

which gives

cmd:Controller/MCP23017P0/command
Parsed pinNo:0
cmd:Controller/MCQ23017P15/command
Parsed pinNo:15
cmd:Controller/MCP23017Q15/command
   23017Q15 is not a number
Invalid pinNo 
cmd:Controller/MCP23017P15/data
   Not a command :data
Invalid pinNo 

If all you need is the number after P you can just atoi it, see 6aENv5 - Online C++ Compiler & Debugging Tool - Ideone.com

Thank you for the quick replies. This has made me go mad - such a seemingly easy task has been really difficult. The parsing code suggested is very daunting for an amateur like me. I've been looking at atoi before, but never got it to work. But, after reviewing your example it ended up working perfectly by just adding one simple line of code..

int pinVal = atoi(topic + strlen("Controller/MCP23017P"));

Again, thank you for the very good and helpful replies!

1 Like