Get IP address from string

Here is a small program with 3 different way of parsing "[color=blue]TRACE_IP=192.168.1.2[/color]"

char cstringToParse[25];
uint8_t ip[4];
unsigned long chrono;

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


  // *******************************************
  //        VERSION WITH strtok() and atoi()
  // *******************************************
  strcpy(cstringToParse, "TRACE_IP=192.168.1.2"); // to simulate receiving from wherever
  chrono = micros();

  char * item = strtok(cstringToParse, ".=");
  uint8_t index = 0;
  while (item != NULL) {
    if ((*item >= '0') && (*item <= '9')) {
      ip[index++] = atoi(item);
    }
    item = strtok(NULL, ".=");
  }

  chrono = micros() - chrono;
  Serial.print(ip[0]); Serial.print(".");// debug
  Serial.print(ip[1]); Serial.print(".");// debug
  Serial.print(ip[2]); Serial.print(".");// debug
  Serial.print(ip[3]);// debug
  Serial.print("   executed in "); Serial.println(chrono);

  // *******************************************



  // *******************************************
  //        VERSION WITH sscanf()
  // *******************************************
  strcpy(cstringToParse, "TRACE_IP=192.168.1.2"); // to simulate receiving from wherever
  chrono = micros();

  sscanf(cstringToParse, "TRACE_IP=%u.%u.%u.%u", ip, ip + 1, ip + 2, ip + 3);

  chrono = micros() - chrono;
  Serial.print(ip[0]); Serial.print(".");// debug
  Serial.print(ip[1]); Serial.print(".");// debug
  Serial.print(ip[2]); Serial.print(".");// debug
  Serial.print(ip[3]);// debug
  Serial.print("   executed in "); Serial.println(chrono);


  // *****************************************************************
  //        VERSION WITH manual parsing (cf post from MarkT)
  // *****************************************************************

  uint8_t idx = 0;
  int intval = 0 ;  // temporary for building up value on the fly
  byte part = 0 ; // index into ip_addr

  strcpy(cstringToParse, "TRACE_IP=192.168.1.2"); // to simulate receiving from wherever
  chrono = micros();

  while ((cstringToParse[idx] != '=') && (cstringToParse[idx] != '\0')) idx++; // find the '='
  if (cstringToParse[idx] != '\0') {
    idx++ ; // skip the '='
    while (true)
    {
      if (cstringToParse[idx] == '.' || cstringToParse[idx] == '\0') { // part separator / end of string
        part++ ;  // skip to next byte
        if (cstringToParse[idx] == 0 || part == 4)  // done if 4 parts parsed or end of string
          break ;
        else  ip[part] = 0 ;
      } else if (cstringToParse[idx] >= '0' && cstringToParse[idx] <= '9') {
        ip[part] = ip[part] * 10 + (cstringToParse[idx] - '0') ; // build up value from decimal digits.
      } else
        break ;
      idx++ ;  // step to next char
    }
  }


  chrono = micros() - chrono;
  Serial.print(ip[0]); Serial.print(".");// debug
  Serial.print(ip[1]); Serial.print(".");// debug
  Serial.print(ip[2]); Serial.print(".");// debug
  Serial.print(ip[3]);// debug
  Serial.print("   executed in "); Serial.println(chrono);

}

void loop() {}

if you run it as is you'll get in the console (set at 115200 bauds)

</sub> <sub>[color=blue]192.168.1.2  executed in 84 192.168.1.2  executed in 388 192.168.1.2  executed in 32 [/color]</sub> <sub>

which is the result of the very same debug code

  chrono = micros() - chrono;
  Serial.print(ip[0]); Serial.print(".");// debug
  Serial.print(ip[1]); Serial.print(".");// debug
  Serial.print(ip[2]); Serial.print(".");// debug
  Serial.print(ip[3]);// debug
  Serial.print("   executed in "); Serial.println(chrono);

--> so the three versions are working and giving the right result (the IP address is detected correctly)

Now you can comment out 2 versions out of the 3 and look at memory / performance impact of each version

Version with manual parsing uses 2100 bytes of program memory and 259 bytes of RAM (I slightly adapted MarkT code from post #10 by using direct array access and removing most error testing to be comparable to the other options) and executes in ~36 microseconds

Version with strtok() and atoi() uses 2230 bytes of program memory and 265 bytes of RAM and executes in ~84 microseconds

Version with scanf() uses 3854 bytes of program memory and 281 bytes of RAM and executes in ~328 microseconds

So you clearly see that by doing things yourself you save the most for everything program memory, RAM and performance, that the C functions strtok() et atoi() will add a bit to the three and scanf() is more costly.

At the same time this comes at the expense of "code complexity", the version with sscanf() is just one line of code, definitely much faster to program and understand, the strtok() version has a few more lines and the manual version "pretty long" so what you gain in code readability / maintainability you loose somewhat in performance and memory impact

if you were to use the String class then memory impact and time performance would be even worse.

So recommendation if you are tight in program memory space and want things to go quick, then go with "manual" parsing (and add comments in the program for maintainability)

Thanks! i'll go with the manual one, but sadly when i added it in i only got an error
error: cannot convert 'String' to 'const char*' for argument '2' to 'char* strcpy(char*, const char*)'

code i am now using:

else if ( currentLine.startsWith("TRACE_IP")) {
      // The server send us the IP address we need to traceroute to.
      // Let's get the IP address from the message
      Serial.print("Starting trace");
      currentLine.remove(0, 9);
      Serial.println(currentLine);
  uint8_t idx = 0;
  int intval = 0 ;  // temporary for building up value on the fly
  byte part = 0 ; // index into ip_addr

  char cstringToParse[25];
  uint8_t ip[4];

  strcpy(cstringToParse, currentLine); // to simulate receiving from wherever
  while ((cstringToParse[idx] != '=') && (cstringToParse[idx] != '\0')) idx++; // find the '='
  if (cstringToParse[idx] != '\0') {
    idx++ ; // skip the '='
    while (true)
    {
      if (cstringToParse[idx] == '.' || cstringToParse[idx] == '\0') { // part separator / end of string
        part++ ;  // skip to next byte
        if (cstringToParse[idx] == 0 || part == 4)  // done if 4 parts parsed or end of string
          break ;
        else  ip[part] = 0 ;
      } else if (cstringToParse[idx] >= '0' && cstringToParse[idx] <= '9') {
        ip[part] = ip[part] * 10 + (cstringToParse[idx] - '0') ; // build up value from decimal digits.
      } else
        break ;
      idx++ ;  // step to next char
    }
  }
  Serial.print(ip[0]); Serial.print(".");// debug
  Serial.print(ip[1]); Serial.print(".");// debug
  Serial.print(ip[2]); Serial.print(".");// debug
  Serial.println(ip[3]);// debug
  
IPAddress IP( ip[0], ip[1], ip[2], ip[3] );
Serial.print(IP);
      ICMPEchoReply echoReply = ping(IP, 1, 4);
      {
    sprintf(buffer,
            ReplyMessage, // Defined in strings.c
            echoReply.data.seq,  // sequence is always 0 when BAD_RESPONSE status
            echoReply.addr[0],
            echoReply.addr[1],
            echoReply.addr[2],
            echoReply.addr[3],
            REQ_DATASIZE,
            millis() - echoReply.data.time, // time is working for both PING and TraceRoute (TLL=0) router response
            
            
            echoReply.ttl,   // TTL equals set TTL when TraceRoute(TLL=0) router response
            echoReply.status); // Equals 0 for Ping and 3 for TraceRoute(TLL=0) router response
    }
    client.print("TRACE_RESULTS"); //Command for the server to know that the next data it receives is the traceroute
    client.print(buffer);
    Serial.println(buffer);
      currentLine = "";
      }
      //}
    }   
  }

currentLine is an instance of the class String (an object, not just a buffer for the chars in memory) , so you want to extract it's memory representation as a c-string there is the the c_str() method you can call on your variable to get direct access to the buffer

so you could do strcpy(cstringToParse, currentLine.c_str()); but that's really bad to use multiple memory buffers. you have things already in memory, work form there and Don't use the String class I'm pretty sure that's what is preventing you form using a UNO

PS/ now you are experienced enough with the forum to use the code tags...

Yea sorry about not using the code tags, forgot them for a sec :confused:
Oh and right now i am working on the UNO, i changed the original plan from having a display on there too to no display. The UNO will just be a zombie that will follow the instructions of the server.

Oh and basicly what you are saying is that is shouldn't use Strings anymore? should i replace it with a Char *?
And if i would do that will this line of code still work? else if ( currentLine.startsWith("TRACE_IP")) {

CrazyVito11:
Oh and basicly what you are saying is that is shouldn't use Strings anymore? should i replace it with a Char *?
And if i would do that will this line of code still work? else if ( currentLine.startsWith("TRACE_IP")) {

Yes I'm saying drop the String class and you'll save probably more than 1.5k of program memory.
of course that won't work if currentLine is now a c-string (a basic array of char with a NULL character at the end)

if you want to use some C functions (careful what we said above is true again, will add to your memory) you can look at the standard c functions in stdlib.h or from string.h and use strncmp() or you can code that yourself manually too by doing a comparison of the memory directly like

 (currentLine[0] == 'T') &&  (currentLine[1] == 'R') &&  (currentLine[2] == 'A') ....

okay, so i tried to change it from a string to a char but now the intire code is dead :confused:
I think this part might now need fixing. I might just go back to using a string if it becomes too complicated since i still have a lot of RAM to spare.

 char inChar;

      while (client.available()) {
        delay(3);  //delay to allow buffer to fill
        if (client.available() > 0) {
          char inChar = client.read();  //gets one byte from serial buffer
          currentLine += inChar; //makes the string readString
        }
      }
      Serial.println(currentLine);

also this is the way i am checking if something is a command:

 else if (strcmp(currentLine, "TRACE_IP") >= 0) {

the reason i am usin >= is because if i would do == for that command it would never work since there is also an IP address after that

yes, if you use a char buffer, you can't build the line doing currentLine += inChar
the delay(3) is also a bad idea

CrazyVito11:

 else if (strcmp(currentLine, "TRACE_IP") >= 0) {

the reason i am usin >= is because if i would do == for that command it would never work since there is also an IP address after that

Bad idea. ("X" is >= "TRACE_IP")

use if (!strncmp(currentLine, "TRACE_IP", 8)) {

indeed as mentioned in #24 it's better to use strncmp()