getting arduino's public IP [SOLVED]

Hello,

I was trying Saiko's sketch in hope to finally have a mean to be continuesely informed of my Arduino's public IP. Unfortunately, after having returned the (correct) IP few times, now it returns a long preamble before the IP (see below) or it closes the connection write away. I there a sure mean to get the IP and only the IP? The code is almost identical to Saiko's

HTTP/1.1 200 OK
Server: Cowboy
Connection: close
Content-Type: text/plain
Vary: Origin
Date: Thu, 24 May 2018 15:37:37 GMT
Content-Length: 12
Via: 1.1 vegur

//zoomkat 9-22-12
//saiko 26-2-15 using ipify.com
//simple client test
//for use with IDE 1.0.1
//with DNS, DHCP, and Host
//open serial monitor and send an e to test
//for use with W5100 based ethernet shields



#include <SPI.h>
#include <Ethernet.h>

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; //physical mac address
IPAddress myIP   (10, 0, 0, 4);
IPAddress netDNS (10, 0, 0, 138);
IPAddress gateway(10, 0, 0, 138);
IPAddress subnet (255, 255, 255, 000);

char serverName[] = "api.ipify.org"; // zoomkat's test web page server
EthernetClient client;

//////////////////////

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

  Ethernet.begin(mac, myIP, netDNS, gateway, subnet);
  Serial.println(F("My ip is: ")); Serial.println(Ethernet.localIP());
  
  Serial.println("Get ip from ipify"); // so I can keep track of what is loaded
  Serial.println("Send an e in serial monitor to test"); // what to do to test
}

void loop(){
  // check for serial input
  if (Serial.available() > 0) //if something in serial buffer
  {
    byte inChar; // sets inChar as a byte
    inChar = Serial.read(); //gets byte from buffer
    if(inChar == 'e') // checks to see byte is an e
    {
      Serial.println("calling sendGET");
      sendGET(); // call sendGET function below when byte is an e
    }
  }  
} 

//////////////////////////

void sendGET() //client function to send/receive GET request data.
{
  if (client.connect(serverName, 80)) {  //starts client connection, checks for connection
    Serial.println("connected");
    client.println("GET / HTTP/1.0"); //download text
    client.println("Host: api.ipify.org");
    client.println(); //end of get request
  } 
  else {
    Serial.println("connection failed"); //error message if no client connect
    Serial.println();
  }
  bool clientConnected,clientAvailable;
  while((clientConnected=client.connected()) && !(clientAvailable=client.available())) delay(1); //waits for data
  Serial.print("client connected ");Serial.print(clientConnected); Serial.print(" clientAvailable "); Serial.println(clientAvailable);
  while (client.connected() || client.available()) { //connected or data available
    char c = client.read(); //gets byte from ethernet buffer
    Serial.print(c); //prints byte to serial monitor 
  }

  Serial.println();
  Serial.println("disconnecting.");
  Serial.println("==================");
  Serial.println();
  client.stop(); //stop client

}
char serverName[] = "api.ipify.org"; // zoomkat's test web page server

That's as far as I read. That is NOT zoomkat's server...

I know that. Yet I pasted the code as is. Maybe what's meant is that's had been taken from a Zookat page

You must read the HTTP header in a buffer line by line. HTTP header ends with "\r\n" alone

  • On the first line, check the HTTP status code 200 (pos 9, len 3)
  • if you find "Content-Lenght:" get the size of the data (it's the size of your IP to read after the header.
  • Finish to read the header
  • Read the number of character got before -> you have your IP.

Below a sample code I write for a GPRS module to get all data from a HTTP request

void HTTP_request(const char *host, unsigned short port, const char *uri)
{
  char buff[128];
  size_t  cb, cbdata = 0;

  if (at_net_connect(TCP, host, port))
  {
    cb = sprintf(buff, "GET %s HTTP/1.1\r\nHost: %s\r\n\r\n", uri, host);
    Serial.print(buff);

    if (GPRSShield.cip_send(buff)) {
      do {  // Read the header line by line
        cb = at_recv((byte *) buff, sizeof(buff) - 1, AT_RESP_LINE);
        buff[cb] = 0;
        Serial.write(buff, cb);

        if (strncasecmp(buff, "content-length:", 15) == 0) {  // we have a data length
          cbdata = atoi(&buff[16]);
        }
      } while (strcmp(buff, "\r\n") != 0);

      while (cbdata) { // Read the data by block of "sizeof(buff)" characters
        cb = at_recv((byte *) buff, sizeof(buff), AT_RESP_RAW);
        Serial.write(buff, cb);
        cbdata -= cb;
      }
    }
    at_net_close();
  }
}

Hi Xartefact,
I was happy to see your reply and even more so to find out that it addresses the very subject. Honestly, I am sure that this has a great importance for many users. I read many posts about getting the ip and or coding an updater for various dns services but found no robust solution. I'll use your recommendations and report here any progress on this subject. Thanks again.
Guy

Glad it can help you. I join the code to receive data on a serial port by line(s) or by lenght.

/*!
 * @function   at_recv
 * @abstract   Read max len bytes from GPRS module or a define number of lines
 * @param      buff         String to write
 * @param      len          The maximum number of characters to be read
 * @param      clf       	Number of line to read (AT_RESP_RAW -1 ro read 'len'  characters )
 * @param      timeout      Response timeout
 */
size_t at_recv(byte *buff, size_t len, int clf, unsigned long timeout)
{
	unsigned long rxstart = millis();
	int rxlen = 0, rxclf = 0;
	do {
		if (xserial->available()) {
			byte c = xserial->read();
			buff[rxlen++] = c;
			if(rxlen == len) break;  // break when buffer length is reached
			if (c == 10) rxclf++;
			if(rxclf == clf) break;  // break when number of lf is reached
		} else delay(10);
	} while ((millis() - rxstart) < timeout); // stop when timeout occured

	return rxlen;
}

Hi xartefact,

Honestly, your code is much too sophisticated -for me- given my coding skill.

Anyway, here is a sketch which seems to be doing the job well, based on your description of the server's behavior. The code could have been much shorter without all the validity checks BUT it can certainly be improved!!.

I intend to use this code in my project and send myself an email whenever the IP changes so I can access my Arduino server, as well as other machines in my home LAN, e.g. for reconfiguring the router itself.

// example sketch for getting the public IP address from some servers
// this can be used to send an email whenever the IP changes allowing access to the arduino server or to any server on same home LAN
//credits to Surfertim, xartefact, Saiko, TolpuddleSartre who helped specifically here

#define sp Serial.print
#define spln Serial.println
#include <SPI.h>
#include <Ethernet.h>
IPAddress myIP   (10, 0, 0, 4);
IPAddress netDNS (10, 0, 0, 138);
IPAddress gateway(10, 0, 0, 138);
IPAddress subnet (255, 255, 255, 000);
byte mac[]={0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; 
const int serverPort=80;
const char * serverName[] = {"api.ipify.org", "myip.dnsdynamic.com","bot.whatismyipaddress.com"};
EthernetClient client;
char genBuf[200];
//////////////////////

void setup(){
  Serial.begin(38400); 
  Ethernet.begin(mac, myIP, netDNS, gateway, subnet);
  Serial.println(F("My ip is: ")); Serial.println(Ethernet.localIP());
}

void loop(){
  IPAddress ip;
  IPAddress prevIP (1,2,3,4);
  IPAddress nullIP (0,0,0,0);
  while (true){
    Serial.println("type an e to test");
    while(Serial.available()==0); //or every, say 5 minutes
    byte inChar=Serial.read();
    if(inChar=='e'){
      spln("got an e, Calling sendget");
      ip=(sendGET());sp("returned ip ");spln(ip);
      if(ip==nullIP) spln("nok");
      else if(ip==prevIP) spln("no change");
      else spln("IP changed");
      prevIP=ip;//sp(prevIP);
    }
  }
} 

//////////////////////////

IPAddress sendGET(){
  bool returnCode=true, okFound=false, lenFound=false, emptyFound=false, plausibleLen=false;
  unsigned int genBufIndex=0;
  int cnxTimer = 0;
  int payloadLen;
  const char okPhrase[]="HTTP/1.1 200 OK";
  const char lenPhrase[]="Content-Length: ";
  const char emptyPhrase[]="\r\n";
  const char minIPstring[]="1.2.3.4";
  const char maxIPstring[]="100.100.100.100";
  const byte minIPlen=sizeof minIPstring;
  const byte maxIPlen=sizeof maxIPstring;
  char fromLine;
    if (client.connect(serverName[2], serverPort)) {  //starts client connection, checks for connection
    client.println("GET / HTTP/1.0"); //download text
    client.print("Host: ");client.println(serverName[2]);
    client.println(); //end of get request
    memset(genBuf, '\0', sizeof(genBuf));
    while (client.connected() && returnCode) {
      while (client.available()) {
        cnxTimer = 0; //
        fromLine = client.read();
        if (genBufIndex < (sizeof(genBuf) - 1))
          genBuf[genBufIndex++]=fromLine;
        if(fromLine=='\n'){
          if(strstr(genBuf,okPhrase)){
            okFound=true; 
          }
          else if(strstr(genBuf,lenPhrase)){
            lenFound=true;
            char c1=genBuf[strlen(lenPhrase)+0], c0=genBuf[strlen(lenPhrase)+1], cx=genBuf[strlen(lenPhrase)+2];
            plausibleLen=isDigit(c1)&&(!isdigit(cx));
            payloadLen=isdigit(c0)?10*(c1-'0')+c0-'0':c1-'0';
            plausibleLen=plausibleLen&&(payloadLen>=minIPlen)&&(payloadLen<=maxIPlen);
          }
          else if(!strcmp(genBuf,emptyPhrase)){
            //spln("empty found");
            emptyFound=true;
          }
          else{;}
          memset(genBuf, '\0', sizeof(genBuf));genBufIndex=0;
        }
      }
      delay(1); cnxTimer++;
      if (cnxTimer > 20000) { //>10"
        sp(F("Server won\'t close the cnx"));
        returnCode = false;
      }
    }
    if(returnCode)spln("cnx closed by server");else sp("cnx timed out");
  }
  else {returnCode = false; 
    spln("client failed to connect");
  }
  client.stop();
  if(returnCode && okFound && lenFound && emptyFound && plausibleLen){
    char *IPdigit;
    byte v[4];
    int i=0;
    for (IPdigit=strtok(genBuf, "."); IPdigit != NULL;IPdigit=strtok(NULL, ".")) v[i++]=atoi(IPdigit);
    IPAddress ip (v[0], v[1], v[2], v[3]);
    return ip;
  }
  else{IPAddress ip (0,0,0,0); return ip;}
}