Non-blocking ethernet example for Teensy

To me one of the biggest nuisances with Arduino is the blocking behavior of their standard Ethernet library. I use Ethernet a lot in my projects, not to say constantly, and with the default library my application would just be sitting there doing nothing while the Ethernet connection is being set up. This can even take up to a full minute (standard time-out) if no DHCP lease can be obtained.

A pull request from 2013 extended the default library with a non-blocking method. Not-so-surprisingly this was not committed into the official release. Guess the Arduino team was too busy developing bloated products that fill up the inventories of Sparkfun and the likes.

I moved away from AVR hardware quite some time ago and nowadays the Teensy is my development platform of choice. Teensy user Headroom modified said non-blocking library to work with this platform. However, DNS resolving did not work for me. I dug into the library to find the cause and implemented a small workaround. Adding a small delay in the DNSĀ  processing function seemed to fix it (line #273 in Dns.cpp). I am by no means a seasoned programmer, so I will leave finding the root cause of the problem to someone else.

The exact use of the library was not readily apparent. It took some time figuring out how to use it, because between the initially provided example and Headrooms modification some functions changed name.

Below I provide an example sketch to show you how to use it. I also incorporated macadress.h discussed here, which automatically uses the Teensys built-in MAC address for setting up the Ethernet connection. It is tested with Arduino 1.6.8, Teensyduino 1-28b, Teensy 3.1 and 3.2 and the Wiz820io Ethernet shield. It should work with any W5100/5200 based shield.

//Example of a non blocking ethernet request and MAC address retrieval for Teensy 3.x and Wiznet W5100/5200 based shields
//This sketch sets up the Ethernet interface with the Teensys built in MAC address and requests a DHCP lease from the router.
//While the lease is being obtained you can already do other stuff. 
//When a lease has been obtained, the sketch will launch a DNS resolve request (also non-blocking) and make a HTTP GET
//to the resolved IP address.
//See http://www.epyon.be/2016/05/22/non-blocking-ethernet-example-teensy/ for more information

//Make sure you include the NB ethernet library!
//Sketch>Include library>NBethernet
#include <Dhcp.h>
#include <Dns.h>
#include <Ethernet.h>
#include <macaddress.h>

macAddress myMac; //object retrieves mac address of Teensy 3.x or Teensy LC
char server[] = "www.google.be"; //server we want to connect to
EthernetClient client;

boolean _ethernetInitialized = 0;
boolean _clientInitialized = 0;
boolean _inSession = 0;
boolean _waiting = 0;

elapsedMillis sinceCheckedEthernet;
elapsedMillis delayEthernetStart;
elapsedMillis sinceCheckedCon; 

static uint16_t bytes_read; //you can use this to limit the max amount of bytes read

void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  // Oh Teensy y u so fast
  delay(4000);
  Serial.println("Starting");
  // Print MAC address embedded in the Teensy
  Serial.print("My mac is: ");
  Serial.println(myMac);
  // Start of non-blocking Ethernet. doEthernet in loop() will poll status.
  Ethernet.begin(myMac); 
}

void loop() {
  doEthernet();
  /*Here you can have other code doing all kinds of stuff without being blocked by the Ethernet library*/
}

void doEthernet(){
  if(delayEthernetStart >= 500){      // initial power-up start delay to allow router to boot
    if (sinceCheckedEthernet >= 500){ // check every once in a while if we acquired a DHCP lease and if it is still valid
      Serial.println("Checking");
      checkEthernet();
      sinceCheckedEthernet = 0;       
    }
  }
  if(_ethernetInitialized == 1 && !_inSession){               //if Ethernet is initialized and we don't yet have an active connection
    if(_clientInitialized == 0 && sinceCheckedCon >= 5000) {  //try to initiate a connection
      Serial.println("Initializing connection");
      if (!client.initConnection(server, 80)) {               //something went wrong, lets try again in 2000ms
        Serial.println("Initializing connection failed");
      }
      else {
        Serial.println("Connection initialized");             //Yay, we have initialized a connection!
        _clientInitialized = 1;
      }
      sinceCheckedCon = 0;
    }
    if(_clientInitialized && client.finishedConnecting()){    //Continously check if the connection has been established
      if(client.finishedConnecting() == 1){                   //If so, launch our request to the server
        Serial.println("Connected");
        client.println("GET /search?q=arduino HTTP/1.0");
        client.print("Host: ");
        client.println(server);
        client.println("Connection: close");
        client.println();
        _inSession = 1;                                       //We are now in session untill we have received the data
        _clientInitialized = 0;
        bytes_read = 0;
      }
      else{
        _clientInitialized = 0;
        Serial.println("We don't have a connection (yet)");
      }
    }
  }
  if(_inSession){
    // we have a connection, and the GET has been sent.
    // lets consume the response
    if (client.available()) {             //You could add && bytes_read < <number> to limit the amount of bytes to be read
        char c = client.read();
        Serial.print(c);
        bytes_read++;
    }
    //If you have added a max number of bytes to be read, uncomment the following code to flush the rest of the received data
    /*else if (client.available()) {
        client.flush();
    }*/
    if (!client.connected()) {
        Serial.println();
        Serial.println("disconnecting.");
        client.stop();
        _inSession = 0;                   //We have succesfully received all data and now close the active session
    }
  }
}

void checkEthernet(){
  if(Ethernet.initialized()){
    if( Ethernet.maintain()==0){            //Check if we have acquired a DHCP lease
      if (_ethernetInitialized == 0){
        Serial.print("My IP is ");
        Serial.println(Ethernet.localIP());
        _ethernetInitialized = 1;
      }
    }
  }
}

The library launches a DHCP request to the router and periodically checks to see if a lease has been obtained or is still valid. Meanwhile your sketch can do other stuff. Just keep on calling doEthernet() in your main loop(). The example sketch launches a DNS resolve request (also non-blocking) once a valid lease has been obtained and then makes a HTTP GET request to the server.