Tag: HTTP

Advanced webserver in Arduino IDE

Advanced webserver in Arduino IDE

After getting the basic webserver working, I wanted to get a more complex setup, different pages and parameters in the url so that you can make you device do things. So basically it is an upgrade of the previous instance but with three functions added.

The function uriRequested returns the complete url you requested, including parameters. The function pageRequested returns only the page name you requested and finally giveUrlParam gives you the value of the parameter you requested.

github-mark-32px Available on GitHub with more comment in the code. Direct download.

Let’s look at the code:

//
// The ESP8266WiFi library is needed
//
#include <ESP8266WiFi.h>

//
// If a requested paramter is not found, this is what is returned
//
#define PARAMNOTFOUND "XxXxXxX"

//
// Please fill in your SSID and password
//
const char* ssid = "YourSSID";
const char* password = "YourPassword";

//
// Initialize the webserver
//
WiFiServer server(80);

void setup() {
  //
  // Initialize serial output
  //
  Serial.begin(115200);
  Serial.print("Connecting to ");
  Serial.println(ssid);
  //
  // Connect to the wifi and wait for a connection
  //
  WiFi.begin(ssid,password);
  while (WiFi.status()!=WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  //
  // Start the webserver
  //
  server.begin();
  delay(500);
  //
  // Show the IP of the server in serial monitor
  //
  Serial.println("Web server running. The IP is:");
  Serial.println(WiFi.localIP());
}

//
// Given the full request string, return just the URI
//
String uriRequested (String reqstr) {
  byte p = reqstr.indexOf("T /")+2;
  return reqstr.substring(p,reqstr.indexOf(" ",p));
}

//
// Given the full request string, return the requested page
//
String pageRequested (String reqstr) {
  String t = uriRequested(reqstr);
  if (t.indexOf("?")>=0) {
    t = t.substring(0,t.indexOf("?"));
  }
  return t;
}

//
// Given the full request string, return the value of the parameter requested
// or PARAMNOTFOUND
//
String giveUrlParam (String reqstr, String search) {
  String t = uriRequested(reqstr);
  if (t.indexOf("?")>=0) {
    t = "&"+t.substring(t.indexOf("?")+1);
    int p = t.indexOf("&"+search+"=");
    if (p==-1) {
      return PARAMNOTFOUND;
    } else {
      p = t.indexOf("=",p)+1;
      t = t.substring(p,t.indexOf("&",p));
      return t;
    }
  } else {
    return PARAMNOTFOUND;
  }
}

void loop() {
  WiFiClient client = server.available();
  if (client) {
    //
    // A web client is asking for a page
    //
    Serial.println("Page requested");
    boolean lastLineBlank = true;
    String requestString = String(100);
    while (client.connected()) {
      //
      // We need to read the request of the client
      //
      if (client.available()) {
        char c = client.read();
        if (requestString.length()<100) {
          requestString = requestString+c;
        }
        if (c=='\n' && lastLineBlank) {
          //
          // We have read the request of the client. Now send the correct page
          //
          Serial.println(requestString);
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/html");
          client.println("Connection: close");
          client.println();
          client.println("<!DOCTYPE HTML>");

          //
          // pr will contain the name of the page
          //
          String pr = pageRequested(requestString);
          if (pr=="/index.php" || pr=="/") {
            client.println("<html>");
            client.println("<head></head><body>");
            client.println("<h1>You requested the main page</h1>");
            client.println("</body></html>");
          } else if (pr=="/other.php") {
            client.println("<html>");
            client.println("<head></head><body>");
            client.println("<h1>You requested the other page</h1>");
            pr = giveUrlParam(requestString,"id");
            //
            // In this page we demo that you can see if someone has put
            // the id param in the requested url and if so, what the value is
            //
            if (pr==PARAMNOTFOUND) {
              client.println("You did not want to send your id");
            } else {
              client.println("You gave your id which is <b>"+pr+"<br>");
            }
            client.println("</body></html>");
          } else {
            client.println("<html>");
            client.println("<head></head><body>");
            client.println("<h1>You requested an unknown page</h1>");
            client.println("</body></html>");
          }
          break;
        }
        if (c=='\n') {
          lastLineBlank = true;
        } else if (c!='\r') {
          lastLineBlank = false;
        }
      }
    }
    delay(1);
    client.stop();
    Serial.println("Page send.");
  }
}

The code above should be, with the help of the comments, self explanatory. Feel free to use and modify. If you have questions, please post them below.

Advertisements
ESP Webserver with Arduino IDE

ESP Webserver with Arduino IDE

I previously shared my implementation of an HTTP server on the EPS8266 based on LUA. Now, with all the work that I’ve been doing with the Arduino IDE, I wanted to be able to do it in the Arduino code as well.

github-mark-32px Available on GitHub with more comment in the code. Direct download.

Below the code for a minimal web server:

//
// Include the ESP8266WiFI library
//
#include <ESP8266WiFi.h>

//
// Fill in your own SSID and password
//
const char* ssid = "YourSSID";
const char* password = "YourPassword";

//
// Initialize the WiFiServer
//
WiFiServer server(80);

void setup() {
  //
  // Initialize serial output
  //
  Serial.begin(115200);
  Serial.print("Connecting to ");
  Serial.println(ssid);
  //
  // Start the wifi and wait for a connection
  //
  WiFi.begin(ssid,password);
  while (WiFi.status()!=WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  //
  // Start the webserver
  //
  server.begin();
  delay(500);
  //
  // Show the IP of the server in serial monitor
  //
  Serial.println("Web server running. The IP is:");
  Serial.println(WiFi.localIP());
}

void loop() {
  WiFiClient client = server.available();
  if (client) {
    //
    // A web client is asking for a page
    //
    Serial.println("New client");
    boolean blank_line = true;
    while (client.connected()) {
      //
      // We need to read the request of the client
      //
      if (client.available()) {
        char c = client.read();
        if (c == '\n' && blank_line) {
          //
          // We have read the request of the client. Now send the page
          //
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/html");
          client.println("Connection: close");
          client.println();
          client.println("<!DOCTYPE HTML>");
          client.println("<html>");
          client.println("<head></head><body><h1>Test</h1></body></html>");  
          break;
        }
        if (c == '\n') {
          blank_line = true;
        } else if (c!= '\r') {
          blank_line = false;
        }
      }
    }  
    delay(1);
    client.stop();
    Serial.println("Client disconnected.");
  }
}

It is not totally minimal as you can remove the references to Serial. The code is mostly self explanatory but a short summary. In the setup you connect to the wifi ssid with the given password. You wait for it to be connected. After that, you start the webserver which was previously defined at port 80, wait a brief moment and share it’s IP on the serial monitor.

In the loop function we wait for a client to appear after which we read what it wants until we get a double blank line (marked by character \n) which means the request has ended. After that we send him the page. Remember to send the lines HTTP/1.1 etc because at least iOS requires this (see my previous post on this).

The next post I will take a look at a more complex webserver where we send different pages and look at the GET parameters.

iOS, NodeMCU, HTTP server

iOS, NodeMCU, HTTP server

When I created my first HTTP server on my NodeMCU, I was using my iPhone as a hotspot. Since it was on the same network, I tried browsing to my NodeMCU with my iPhone. It did not work. I got a blank page and thought I did something wrong on my NodeMCU.

After lots of changes to the code I decided to use my laptop to browse to the NodeMCU and see if at least some of the code was being send. Guess what? No problems, page showed nicely, buttons worked, total success. So why not on my iPhone? After lots of digging I have found the problem. It turns out iOS is not as forgiving as my Firefox is.

My code was simple:

----------------------
--                  --
--  httpserver.lua  --
--                  --
----------------------

-- check whether server is already started
if srv~=nil then
  srv:close()
end

-- create webserver
srv = net.createServer(net.TCP)
srv:listen(80,function(conn)
  conn:on("receive",function(cn,request)
    print(request)

    -- the webpage
    cn:send("Hello world")
    cn:close()
    collectgarbage()
  end) 
end)

I added tags but nothing fixed it until I added something very basic at the beginning of the buffer send to the requestor:

----------------------
--                  --
--  httpserver.lua  --
--                  --
----------------------

-- check whether server is already started
if srv~=nil then
  srv:close()
end

-- create webserver
srv = net.createServer(net.TCP)
srv:listen(80,function(conn)
  conn:on("receive",function(cn,request)
    print(request)

    -- the webpage
    local buf = "HTTP/1.1 200 OK\r\nContent-type: text/html\r\n"
    buf = buf.."Connection: close\r\n\r\n"
    buf = buf.."<h1>Hello world</h1>"
    buf = buf.."This is a test\n"
    cn:send(buf)
    cn:close()
    collectgarbage()
  end) 
end)

Apparently, if you don’t tell the iOS browser that the content is text/html, it just does not show it.

HTTP server on NodeMCU

HTTP server on NodeMCU

To interact with our NodeMCU, we could use an HTTP server. The Hello world version of this is quite simple:

srv = net.createServer(net.TCP)
srv:listen(80,function(conn) 
  conn:on("receive",function(cn,request) 
    print(request) 
    cn:send("Hello world")
    cn:close();
    collectgarbage();
  end) 
end)

Upload and to your NodeMCU and execute it. If you now go to the IP of your NodeMCU, you should see Hello world.
The code is relatively straight forward. You create a TCP server which listens on port 80

If you want to try different things with your code and you execute it again, you will get the error message only one tcp server allowed if you have not restarted the NodeMCU since the last time you executed the code. Instead of restarting, executing the following:

srv:close()

This will stop the current server and you can execute your file again to restart it. Since LUA on the NodeMCU keeps variables even after the program has stopped we can create a more resilient version this way:

----------------------
--                  --
--  httpserver.lua  --
--                  --
----------------------

-- check whether server is already started
if srv~=nil then
  srv:close()
end

-- create webserver
srv = net.createServer(net.TCP)
srv:listen(80,function(conn)
  conn:on("receive",function(cn,request)
    print(request)

    -- the webpage
    cn:send("Hello world")
    cn:close()
    collectgarbage()
  end) 
end)

Next step, actually interact with the NodeMCU. For this we need to be able to send it requests of some sort. For testing I connected two leds to the NodeMCU, port 2 and port 3. I want to use the webserver to control the leds. Let’s first post the code which I will explain below it:

-----------------------
--                   --
--  httpserver2.lua  --
--                   --
-----------------------

-- 1:set variables and initial GPIO states
led1 = 2
led2 = 3
gpio.mode(led1,gpio.OUTPUT)
gpio.mode(led2,gpio.OUTPUT)
gpio.write(led1,gpio.LOW)
gpio.write(led2,gpio.LOW)

-- 2:check whether server is already started
if srv~=nil then
  srv:close()
end

-- 3:create webserver
srv = net.createServer(net.TCP)
srv:listen(80,function(conn)
  conn:on("receive",function(cn,request)
    local buf = ""

    -- 4:extra path and variables
    local _,_,method,path,vars = string.find(request,"([A-Z]+) (.+)?(.+) HTTP")
    if(method==nil) then
      _, _, method, path = string.find(request,"([A-Z]+) (.+) HTTP")
    end

    -- 5:extract the variables passed in the url
    local _GET = {}
    if (vars~=nil) then
      for k,v in string.gmatch(vars,"(%w+)=(%w+)&*") do
        _GET[k] = v
      end
    end

    -- 6:the webpage
    buf = buf.."

Demo server

”     buf = buf..”Led 1 Turn on ”     buf = buf..”Turn off
”     buf = buf..”Led 2 Turn on ”     buf = buf..”Turn off
”     — 7:the actions     if (_GET.pin==”L1On”) then       gpio.write(led1,gpio.HIGH)     elseif (_GET.pin==”L1Off”) then       gpio.write(led1,gpio.LOW)     elseif (_GET.pin==”L2On”) then       gpio.write(led2,gpio.HIGH)     elseif (_GET.pin==”L2Off”) then       gpio.write(led2,gpio.LOW)     end — 8:send the data and close the connection     cn:send(buf)     cn:close()     collectgarbage()   end) end)

Section 1 is straight forward, set variables for the ports of led 1 and 2 and turn off both leds.
In section 2 we close the current server if it is started to be followed by section 3 where the new http server is created, both described above.

Section 4 is where we take the request information send by the user and using pattern matching, extract the path and the variables (if any). In section 5 we put the variables in a LUA table, again using pattern matching.

Section 6 is the webpage. In this case we did not check the path variable. Regardless of what page you request, you will always get the same page.

Finally, section 7 is where we perform the GPIO actions based on the request and section 8 is where the data is send to the user and the connection is closed.

HTTP GET on the NodeMCU

HTTP GET on the NodeMCU

Well, after toying with it and getting it to work, I wanted it to be able to do something, obviously, and one of the first things I wanted it to do is to be able to grab a webpage and if that works, get it to do some sort of Web API or RESTful call.

The code for getting a webpage turned out to be relatively simple:

conn = net.createConnection(net.TCP, false) 
conn:on("receive", function(conn, answer) print(answer) end)
conn:connect(80,"23.23.215.91")
conn:send("GET / HTTP/1.1\r\n"
  .."Host: maker.ifttt.com\r\n"
  .."Connection: keep-alive\r\nAccept: */*\r\n\r\n")

This is simple if you know the IP. However if you want to connect to a domain (which of course makes much more sense) the code becomes a little more complex because the NodeMCU has to do a DNS lookup and only after it has done that should you send the GET etc strings so these should only be send after a successful connection (thanks to a fellow blogger who wrote on this). This gives the following code:

conn = net.createConnection(net.TCP, false) 
conn:on("receive", function(conn, answer) print(answer) end)
conn:on("connection", function(cn, answer)
  cn:send("GET / HTTP/1.1\r\n"
  .."Host: maker.ifttt.com\r\n"
  .."Connection: keep-alive\r\nAccept: */*\r\n\r\n")
end)
conn:connect(80,"maker.ifttt.com")

Well, now that we can grab a webpage, the next step will be to do some sort of RESTful call which is quite simple. So let’s jump ahead and make it a little more complex. I want to do a RESTful call to IFTTT, more specifically the Maker channel which is created specifically for IoT (internet of things) type devices.

(If you are not familiar with IFTTT, you should visit their website. In short, it’s a web utility that allows you to create conditional things to happen, IF This Than That).

I will be using a IFTTT recipe to send a message to Pushover which will send a message to my phone.

ip, nm, gw = wifi.sta.getip()
thisNode = 1
conn = net.createConnection(net.TCP, false) 
conn:on("receive", function(conn, answer) print(answer) end)
conn:on("connection", function(cn, answer)
  cn:send("GET /trigger/NodeMCUBoot/with/key/--yourkey--?"
  .."value1="..thisNode.."&value2="..ip.." HTTP/1.1\r\n"
  .."Host: maker.ifttt.com\r\n"
  .."Connection: keep-alive\r\n"
  .."Accept: */*\r\n\r\n")
end)
conn:connect(80,"maker.ifttt.com")

Now whenever this code is executed, I get a pushover message saying
IFTTT: Maker: NodeMCU 1 is alive and has IP 10.1.2.3

This of course will be integrated in my init.lua so that whenever a NodeMCU is booted, I get a message, to not only tell me it’s alive, but also what it’s IP is. In one of my next posts I will explain on how to wait for an assigned IP and than combine everything init.lua.