ESP8266 Arduino IDE Web Server Using Callbacks

sdkcall

While the Arduino IDE is a convenient and familiar platform to develop ESP8266 Web Server based projects, it does have a significant drawback. That is, once the Web Server is started, it is necessary to poll the server each iteration of the loop() function to check for new connections before processing the request.

Putting the check in a loop with other activities, such as reading sensors could and often does result in significant response delays. And possible loss of communication due to connection timeouts.

A better approach is to use a callback that fires whenever an external client connects to the ESP8266 server. This frees up the sketch loop() from the task of servicing http request using inefficient polling code.

And callbacks is how a Web Server is implemented using the ESP8266 EspressIf SDK. But why is it missing from the Arduino IDE? I suspect it is because that is the way it is done with Arduino-based systems—making it both familiar and perhaps suitable for Arduino code reuse with the ESP8266.

But it is not the best solution for new designs.

So that got me thinking…

Why not customize the typical web server sketch to use the SDK callback schema? After all, it is possible to  call ESP8266 SDK functions from the Arduino IDE. While searching, I could not find any examples of this being done, so I’ve created one myself.

And here it is…

The SDK Server Architecture

The SDK API requires several layers of callbacks in a web server design. Since these callbacks are usable in a sketch without modification, the actual sketch functions included in this example are shown.

First,  the server must be setup in the sketch setup() routine. This initialization code was encapsulated in the following function, “SdkWebServer_Init()”, called in setup():

 
 
  1. void SdkWebServer_Init(int port) {
  2.     LOCAL struct espconn esp_conn;
  3.     LOCAL esp_tcp esptcp;
  4.     //Fill the connection structure, including "listen" port
  5.     esp_conn.type = ESPCONN_TCP;
  6.     esp_conn.state = ESPCONN_NONE;
  7.     esp_conn.proto.tcp = &esptcp;
  8.     esp_conn.proto.tcp->local_port = port;
  9.     esp_conn.recv_callback = NULL;
  10.     esp_conn.sent_callback = NULL;
  11.     esp_conn.reverse = NULL;
  12.     //Register the connection timeout(0=no timeout)
  13.     espconn_regist_time(&esp_conn,0,0);
  14.     //Register connection callback
  15.     espconn_regist_connectcb(&esp_conn, SdkWebServer_listen);
  16.     //Start Listening for connections
  17.     espconn_accept(&esp_conn); 
  18. }

Upon an external TCP connection, the callback “SdkWebServer_listen” is executed:

 
 
  1. void SdkWebServer_listen(void *arg)
  2. {
  3.     struct espconn *pesp_conn = ( espconn *)arg;
  4.     espconn_regist_recvcb(pesp_conn, SdkWebServer_recv);
  5.     espconn_regist_reconcb(pesp_conn, SdkWebServer_recon);
  6.     espconn_regist_disconcb(pesp_conn, SdkWebServer_discon);
  7. }

This functions registers 3 connection-specific callbacks:

  1. SdkWebServer_recv – Called when connection data is received. This callback replaces the polling loop when using the Arduino library.
  2. SdkWebServer_recon – Called when TCP connection is broken.
  3. SdkWebServer_discon-Called when a TCP connection is closed.

The SdkWebServer_recv() function processes http GET and POST requests in a similar manner as the processing in the loop() after an Arduino client connection is detected.

Putting It All Together

In order to contrast the commonly used Arduino Web Server library with the SDK API, I’ve put together an example sketch that supports both web server approaches. It’s on Github:

ESP8266-12 Arduino IDE Web Server Example 

This example provide the same functionality with either server interface. The selection is made prior to building the sketch, using precompilation directives. When possible, the same code is used with both selections. The difference is that the SDK uses callbacks while the Arduino polls for connections.

While no additional hardware is needed to run and test the Web Server, this example assumes the ESP8266 circuit from this post. But the code will work with any ESP8266 circuit, even with no externally connected sensors or controls. But without the ADC multiplexer from the referenced post, the ADC will simply make the same measurement for each multiplexer setting. Which is just fine for the purposes of this example.

Testing the Server From A Web Browser

Since both versions of the Web Server provide the same functionality, the same test can be performed for verification.

You might need to changed the static IP set in this example to match your own LAN subnet. The defaults are currently set to:

IP: 192.168.0.132

Listen Port: 9701

The test url is thus:

192.168.0.132:9701/?request=GetSensors

And the JSON returned from the Web Server should be in the following format:

{
"Ain0":"316.00",
"Ain1":"326.00",
"Ain2":"325.00",
"Ain3":"314.00",
"Ain4":"316.00",
"Ain5":"163.00",
"Ain6":"208.00",
"Ain7":"333.00",
"SYS_Heap":"25408",
"SYS_Time":"26"
}

And if connected, the server will also support changing the LED on/off state:

To turn it on, use the URL:
192.168.0.132:9701/?request=LedOn

And the off URL i:
192.168.0.132:9701/?request=LedOff

Adding server functionality should be obvious after reviewing the sketch.

Conclusions

Using a callback to process Web Server connection requests is far superior to polling. CPU bandwidth is only used in response to client connection events. And with the example framework provided in this post, you can still utilize many of the Arduino sensor libraries without modification.

With a Web Server application, you get the efficiency of the callback with the familiarity of the Arduino IDE.

I hope you find this information useful…

Share This:
Facebooktwittergoogle_plusredditpinterestlinkedintumblrFacebooktwittergoogle_plusredditpinterestlinkedintumblr
Social tagging: >

21 Responses to ESP8266 Arduino IDE Web Server Using Callbacks

  1. krishna says:

    Hello Guys,

    I started working on CoAP client implementation for ESP8266.Is there any sample codes available in the net.

    Please guide me how to start with coap client implementation.

    Thanks in advance,
    Krishna

    • facebook-profile-picture InternetOfHomeThings says:

      You must have read my post "Triple Server" on this blog which includes a COAP server implementation on the Esp8266.

      While I have not developed a COAP client yet for the Esp8266, I would use the library found here to create it.

      https://libcoap.net

  2. Lee says:

    I have tried using your web server example but I am having a lot of compile errors, below is the first one. Is there anything obvious I am doing wrong that may be causing these?

    C:\Program Files\Arduino\libraries\webserver/mem_manager.h:72:40: error: declaration of C function 'void* pvPortMalloc(size_t)' conflicts with

    void *pvPortMalloc( size_t xWantedSize ) ;//ICACHE_FLASH_ATTR;

    ^

    In file included from C:\Documents and Settings\Lee\Local Settings\Application Data\Arduino15\packages\esp8266\hardware\esp8266\2.3.0\cores\esp8266/pgmspace.h:12:0,

    from C:\Documents and Settings\Lee\Local Settings\Application Data\Arduino15\packages\esp8266\hardware\esp8266\2.3.0\cores\esp8266/Arduino.h:240,

    from sketch\web_server.ino.cpp:1:

    • facebook-profile-picture InternetOfHomeThings says:

      Did you use the files from the corrections folder on Github?

      Unfortunately, updates to the Arduino IDE sometimes break code compiles.

      I will try to compile the example again using my current Arduino IDE (Version 1.6.12) and post the updated code, if any, on Github.

  3. Lee says:

    I downloaded the latest zip from github…

    I found I had to comment out:

    //void *pvPortMalloc( size_t xWantedSize ) ;//ICACHE_FLASH_ATTR;
    //void vPortFree( void *pv ) ;//ICACHE_FLASH_ATTR;

    from mem_manager.h in the webserver library as they were re-definitions.

    Also I had to change the definition of a function within the sketch to:

    void * pvPortZalloc(int size, char*, int);

    as it was complaining about not enough parameters.

    • facebook-profile-picture InternetOfHomeThings says:

      Thanks for you comment Lee. You are correct. These changes came about with one of the Arduino IDE version changes but my Github repository was never updated.

      The two files you noted will be updated in the Github repository soon.

  4. Mark Stevens says:

    This is VERY helpful, including Lee's comments. Thanks!

    Dave, I'm trying to adapt your example to a similar application. I am trying to use HTTP GET to poll an ESP8266 for it's "status" as well as send it commands like "trigger/time=10". As I'm looking through the example I'm confused by a few things.

    First, I don't quite get SdkWebServer_recv() vs Server_GetRequest(). I currently have my if…then logic to respond to the GET requests in Server_GetRequest(), but I don't see it being called anywhere.

    Second, I don't quite follow what's going on in SdkWebServer_recv():

    espconn_set_opt(ptrespconn, ESPCONN_REUSEADDR);

    and

    parse_flag = SdkWebServer_savedata(pusrdata, length);
    pURL_Param = (URL_Param *)os_zalloc(sizeof(URL_Param));

    and

    if (os_strcmp(pURL_Param->pParam[0], "request") == 0) {
    // GetSensors is 1 of 3 requests the server currently supports
    if (os_strcmp(pURL_Param->pParVal[0], "GetSensors") == 0) {
    // Execute request & get reply string
    payld = Server_ExecuteRequest(Get_SENSORS);
    SdkWebServer_senddata(ptrespconn, true, (char *)payld.c_str());
    }

    I expect that is parses the text sent via the GET request, but it's a bit cryptic and I'm lost. A bit of help would be much appreciated!

    • Mark Stevens says:

      Well, thanks to Google I found out what os_strcmp() does (sorta) and I think I'm past that. Sorry for posting that, but when I couldn't fine it in the Arduino reference documentation.

      That said, I'm still not sure what "->" does. I was thinking it was an operator that I just didn't know about but I'm now thinking it's just a part of your nomenclature.

  5. Lee says:

    From what I can see, the code makes no attempt to reconnect to wifi if the connection drops unless this happens elsewhere?

    Can I add code to the discon and recon functions to attempt to reconnect by calling util_startWiFi and SdkWebServer_Init again or is there a better way?

  6. Mark Stevens says:

    Also, is the case ONEJSON in jsonEncode really complete or is that just a placeholder? Would the following be correct:

    case ONEJSON:
    *s += "{\r\n";
    jsonAdd(s, key, val);
    *s += "\r\n}";

  7. Ricardo Figueiredo Machado says:

    Good morning my friend!
    I have long time noticed that, but did not know how to compare and thought were due to other bugs. Thank you for your kindness to share their knowledge. No doubt everything I have read your website is one of the best I found. His works are very deep on issues that go unnoticed by most of the others. My sincere congratulations and respect.
    Best Regards
    Ricardo

  8. Lee says:

    Do I need to add some code to check if the WiFi has disconnected and reconnect or does this happen automatically in the Expressif SDK?

  9. Mark Stevens says:

    It took some digging but I got this figured out and working like a charm. For some reason, however, I've started having resets again. I've isolated the code where this happens, but it only happens SOMETIMES. The code is:

    str = (char *)os_strstr(++pbuffer, "&");

    The error I'm getting is:

    Exception (28):
    epc1=0x4000e1e0 epc2=0x00000000 epc3=0x00000000 excvaddr=0x00000001 depc=0x00000000

    ctx: sys
    sp: 3ffffc60 end: 3fffffb0 offset: 01a0

    >>>stack>>>
    3ffffe00: 3ffe880e 3fff1fb4 3ffef7a4 40209eac
    3ffffe10: 40202a84 3fff1fb4 3fff1694 40202a73
    3ffffe20: 3fff169a 00000001 3fff1c0c 401004f4
    3ffffe30: 3fff1ba4 3fff1c54 3fff1c0c 3ffef7a4

    Any idea what this could be? Why does it reset just occasionally?

    • Mark Stevens says:

      Oh, this is in your routine SdkSWebServer_parse_url_params.

    • David H says:

      Mark,
      I have tried this example and get the same exception(28). I have debugged as much as my knowledge allow me and believe it is the line of code above the line you mention.
      os_memcpy(purl_param->pParam[ipar], str, length);
      The "length" sometimes is 198 when it should be the length of the parameter.
      GetSensors = 10, LedOn = 5 LedOff = 6 etc..
      I have found it is very bad if the browser (Chrome) is reloaded.

      The whole sketch works fine if you define it as #define SVR_TYPE SVR_HTTP_LIB and not use the SDK
      If you or anyone has a suggestion please reply.
      To Dave The Author, Thank you very much for posting this example.
      Regards
      Dave H

      • facebook-profile-picture InternetOfHomeThings says:

        While I have not had the chance to complete my investigation of this issue, please check the comments from Bra1nK:

        https://github.com/internetofhomethings/ESP8266-Arduino-Ide-Web-Server-Using-SDK-API/issues/4

        It appears that the problem is occurring as result of a dependency on a specific http GET header. Chrome sends a different header sequence upon refresh when it sends a 'favicon' request. This request does not apparently occur when using other browsers. While the solution Bra1nK works in this case, a better solution would be to use a header that is common to all requests so the exception(28) never pops up again.

        • facebook-profile-picture InternetOfHomeThings says:

          I was able to duplicate the exception (28) error.

          Here is my recommended fix (comment/un-comment):

          //pbuffer = (char *)os_strstr(precv, "Accept:");
          pbuffer = (char *)os_strstr(precv, "Connection:");

  10. Carl Gifford says:

    Dave, Thanks for this example. Complied and ran no problem. I deleted all the Arduino related stuff from the file. A great foundation to build on.
    I'm a bit banger from the 8080 days. Next is to study the actual code and incorporate my custom requirements. I also like your Analong front end multiplexing scheme, we think Alike !

  11. Mark Stevens says:

    Dave, that did fix that problem. Thanks!

    In testing it I noticed another problem, but it's one that can be worked around. If you send over an incomplete JSON string I get the same error. For instance in my code when I am supposed to send over "/?request=GetStatus" but instead send over "/?request" I get the Exception (28) error with reset again.

    This may very well be something specific to my code, but I'm putting it out there as a caution since it's not obvious and there may still be issues with the way that code handle typos in the URL.

    Again, thanks for supplying this…it is super helpful!

    • David Huckett says:

      Hi, Just expanding on what I commented before the data being passed to os_memcopy supplies the wrong length with some browser requests resulting in the memory problem and a crash. I ended up copying the function of the same name out of Daves ESP8266-Triple-Server example and it runs great.
      The code in this version of the function is slightly different and since using it I have not had a crash or exception. void SdkWebServer_parse_url_params(char *precv, URL_Param *purl_param) is the whole function I replaced from Daves GIT https://github.com/internetofhomethings/ESP8266-Triple-Server/blob/master/http_coap_mqtt_server/http_coap_mqtt_server.ino
      Once again Thanks Dave, I have learnt a lot.
      Regards Dave H

      • facebook-profile-picture InternetOfHomeThings says:

        I have also learned more with every new project but have not gone back to change the earlier versions such as this one. Thanks for sharing. Hopefully this will helps someone else in the future. Cheers! Dave

Leave a Reply

Press Ctrl+C to copy the following code.
"