Converting a String containing delimited Numeric data to an int array.

I have a text file on a micro SD containing the following lines.

[IPADDR=172.22.0.230] [IPMASK=255.255.255.0] [IPGWAY=172.22.0.254] [IPDNS=172.22.0.254]

I can read the lines from the txt file and end up with 4 String variables as follows

ipaddr = "172.22.0.230" mask = "255.255.255.0 gateway = "172.22.0.254" dns = "172.22.0.254"

I am trying to use these values to configure the IP settings on an ESP8266 Wemos D1 mini.

The Wemos device library expects each of these settings to be passed in the form of an array where each octet within the string occupies a line in the array. I believe it requires an integer array for example the IP address would be IP[0] = 172, IP[1] = 22, IP[2] = 0, IP[3] = 230

I have exhausted my programming knowledge in converting the text string 'ipaddr' = "172.22.0.230" into the integer array and my program fails with the error message:

cannot convert 'String' to 'const char*' for argument '1' to 'int atoi(const char*)'

Could someone provide me with a sketch that could do the conversion.

Regards Zen

Step one, drop the String, make it a string.

And I made a small function to do what you want

void ipString2Array(const char* string, byte arr[4]){
  byte si = 0; //string index
  byte bi = 0; //buffer index
  byte ai = 0; //array index
  char buf[4] = "";

  //ip in stringform is max 14 char long
  while(si < 15){
    //if it's an ASCII number and room in the buffer (size 4 - 1 for '\0')
    if(string[si] >= '0' && string[si] <= '9' && bi < 3){
      buf[bi] = string[si];
      bi++;
    }
    //if a dot OR end, parse number
    else if(string[si] == '\0' || string[si] == '.'){
      //terminate string
      buf[bi] = '\0';
      
      //convert to number
      arr[ai] = atoi(buf);

      //(re)set indexes
      ai++;
      bi = 0;

      //exit function if at the end
      if(ai == 4 || string[si] == '\0'){
        return;
      }
    }
    //to next char
    si++;
  }
}

Use it like:

char ipString[] = "172.22.0.230";
byte  ipArray[4];

ipString2Array(ipString, ipArray);

Small note, ipString MUST be null terminated. It is if you do ipString = “blaa” but it might not be if you read it from a file, that’s up to you to check.

I feel this is a lot more elegant

void ipString2ArrayX(const char* string, byte arr[4]) {
  byte ai = 0;
  for (arr[ai] = 0; *string; string++) {
    if (isdigit(*string) && ai <= 3) {
      arr[ai] *= 10;
      arr[ai] += *string - '0';
    } else if (*string == '.') {
      if (++ai == 4) {
        break;
      }
      arr[ai] = 0;
    }
  }
}

Yeah, using a pointer might be more elegant. But you also removed a few checks / safety guards… So I would keep si just to be safe if a non-null terminated string is passed. Not that it will give the right answer but at least the code does not just scans all the memory :stuck_out_tongue:

Although I have to say I do like it indeed more not to use atoi :smiley:

My edit:

void ipString2Array(const char* string, byte arr[4]){
  byte si = 0; //string index
  byte ai = 0; //array index
  
  //reset array
  for(byte i = 0; i < 4; i++){
    arr[i] = 0;
  }
  
  //ip in stringform is max 14 char long
  while(si < 15 && string[si] != '\0'){
    //if it's an ASCII number and room in the buffer (size 4 - 1 for '\0')
    if(isdigit(string[si])){
      arr[ai] *= 10;
      arr[ai] += string[si] - '0';
    }
    //if a dot, next antry
    else if(string[si] == '.'){
      //exit function if at the end
      if(++ai == 4){
        break;
      }
    }
    //to next char
    si++;
  }
}

It is only valid for valid input (and there are plenty zeros in memory…)

Your routine fails on bad input like mine (on “1234.1234.3.4”).

Collecting chars to atoi them is superfluous.
If you like conversion functions you could use strtol and check the delimiters,
all without an extra copy.

Whandall:
I feel this is a lot more elegant

if elegance is what you seek, sscanf() comes dressed for the party:

char* theString = "[IPADDR=17.3.10.230]";
byte myIpAddress[4];

void setup(void)
{
  Serial.begin(9600);
  Serial.println("Hello World");
  sscanf(theString, "[IPADDR=%u.%u.%u.%u]", &myIpAddress[0], &myIpAddress[1], &myIpAddress[2], &myIpAddress[3]);
  for(int i = 0; i < sizeof(myIpAddress); i++)
  {
    Serial.println(myIpAddress[i]);
  }
}

void loop(void){}

That depends on your definition of elegant :D

I would call a fat elephant not elegant, even if it's dressed :D

septillion: I would call a fat elephant not elegant, even if it's dressed :D

but one line of code, that a newbie who has

zen: ...exhausted my programming knowledge...

can look at and come close to understanding what's happening.

BulldogLowell: ``` ... byte myIpAddress[4];

...

sscanf(theString, "[IPADDR=%u.%u.%u.%u]", &myIpAddress[0], &myIpAddress[1], &myIpAddress[2], &myIpAddress[3]); ...

Minor nit - this is going to fail spectacularly. *scanf() routines expect a int * parameter for a %u conversion. Throwing a byte * at them as your example does is not going to end well.

But yeah, I agree that sscanf is definitely the way to go. For all its other benefits, STL still doesn't have anything that comes close to being as concise and performant as scanf and printf.

Have you ever tried doing formatted output using cout? It's hideous.

dgnuff: But yeah, I agree that sscanf is definitely the way to go.

On a PC, I agree. On a micro controller it's still a fat elephant, even when it's dressed.

septillion: On a PC, I agree. On a micro controller it's still a fat elephant, even when it's dressed.

Depends on your definition of a fat elephant. ;)

On a Teensy 2, linking in sscanf adds about 1,800 bytes to the sketch, which out of a total memory budget of 32,000 bytes and change works out to about 5.5% of your total memory available. This also applies to an Arduino Uno, which appears to have the same memory available.

Unless you're pushing real close to the limits, I'd say that's memory well spent.

Now on an Adafruit Trinket, with a budget of 5,300 bytes and change, those same 1,800 bytes are a little over a third of your entire memory. That's a whole different can of worms: in this instance it's going to be a "case by case" decision. Some sketches will take it, some simply won't.

I call adding 1800 bytes to something that in total otherwise would consume 1770 bytes (including all Arduino overhead) a very very obese elephant. You might have "room to spare" but I still think it's a bad habit to piss away memory like that on a micro. And I do find 5,5% memory already a lot. If you have 1MB of program space then okay. But that's already basically a computer :p

And then you still don't have something good because like you said, sscaf() assumes a int*, not a byte*. So it only works correct if the input is known to be less then 256 and the architecture little-endian. So you would still need a code around it making it even a poorly dressed elephant ::)