Arduino Web Server

Follow this tutorial in order to start with the Arduino Web Server.

                             
#0

Chapter #0

The first question could be “What is a web server?“. A web server is an application designed to receive HTTP requests from a client, process them and send a response to the client, using the HTTP protocol. Usually, the reponses are HTML pages, XML content, JSON objects, CSS stylesheets, etc. In this course we will serve an HTML page with buttons to control outputs, text boxes to send data through RS-485, texts with input values, and dynamic content using a little of Javascript.

Chapter #3

This chapter adds a couple of links to set and clear an output.

      1. Add set and clear actions

The easiest way to add simple actions into the web page is to insert anchor HTML elements with associated action paths. The HTTP response from the server should contain these anchors:

client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println();
client.println(
    "<!doctype html>"
    "<html>"
    "<head>"
    "<title>Hello PLC!</title>"
    "</head>"
    "<body>"
    "<h1>Hello PLC!</h1>"
    "<a href='/setOutput'>Set output</a><br>"
    "<a href='/clearOutput'>Clear output</a><br>"
    "</body>"
    "</html>"
    );
client.flush();
      1. Parse the request

Now the sketch should parse the request to set or clear the output depending on the path. To do this, it gets the route from the first line of the request using an String object called path and some of its functions.

Some variables are needed

String header;
bool firstLine = true;

And the character received from the client is appended to the header

if (firstLine) {
  header += c;
}

When the request is complete the sketch parses the header line to get the path

int pos = header.indexOf(' ');
String path = header.substring(pos + 1, header.indexOf(' ', pos + 1));

Remember to update the firstLine variable when the client sends a ‘new line’ (\n) character.

if (c == '\n') {
  emptyLine = true;
  firstLine = false;
} else if (c != '\r') {
  emptyLine = false;
}
      1. Set or clear output depending on the path

The server should set output to HIGH when the path is equal to ‘/setOutput’ and LOW when the path is equal to ‘/clearOutput’.

if (path.compareTo("/setOutput") == 0) {
  // Set output
  digitalWrite(Q0_0, HIGH);
} else if (path.compareTo("/clearOutput") == 0) {
  // Clear output
  digitalWrite(Q0_0, LOW);
}
      1. Final code review

Here is the whole code, with some improvements: send a 404 error for unknown requests, send a redirection response to the main page on set/clear command request and
add the sendMainPage() function to ease the reading and reuse it.

#include <Ethernet2.h>

// Server creation
EthernetServer server(80);

void setup() {
  Serial.begin(9600UL);
  Serial.println("WebServer started");

  // Ethernet initialization
  byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
  byte ip[] = {10, 10, 10, 25};
  Ethernet.begin(mac, ip);

  // Server initialization
  server.begin();
}

void loop() {
  // Wait for clients
  EthernetClient client = server.available();
  if (client) {
    // Parse request
    bool emptyLine = true;
    String header;
    bool firstLine = true;

    while (client.connected()) {
      if (client.available()) {
        char c = client.read();

        if (firstLine) {
          header += c;
        }

        // The request is finished when an empty line is received
        if (emptyLine && (c == '\n')) {
          // Get path from the header line
          int pos = header.indexOf(' ');
          String path = header.substring(pos + 1, header.indexOf(' ', pos + 1));

          // Act depending on path
          if (path.compareTo("/") == 0) {
            sendMainPage(client);
          } else if (path.compareTo("/setOutput") == 0) {
            // Set output
            digitalWrite(Q0_0, HIGH);

            // Redirect response
            redirectToMainPage(client);
          } else if (path.compareTo("/clearOutput") == 0) {
            // Clear output
            digitalWrite(Q0_0, LOW);

            // Redirect response
            redirectToMainPage(client);
          } else {
            // Unrecognized path
            sendNotFound(client);
          }

          client.flush();

          // Close connection
          client.stop();

          break;
        }

        // The request is finished when a blank line is received
        if (c == '\n') {
          emptyLine = true;
          firstLine = false;
        } else if (c != '\r') {
          emptyLine = false;
        }
      }
    }
  }
}

void sendNotFound(EthernetClient &client) {
  // Path not found
  client.println("HTTP/1.1 404 Not Found");
  client.println("Content-Type: text/plain");
  client.println();
  client.println("404 Not Found");
}

void redirectToMainPage(EthernetClient &client) {
  // Redirect to main page
  client.println("HTTP/1.1 303 See Other");
  client.println("Location: /");
  client.println();
}

void sendMainPage(EthernetClient &client) {
  // Send the response
  client.println("HTTP/1.1 200 OK");
  client.println("Content-Type: text/html");
  client.println();
  client.println(
      "<!doctype html>"
      "<html>"
      "<head>"
      "<title>Hello PLC!</title>"
      "</head>"
      "<body>"
      "<h1>Hello PLC!</h1>"
      "<a href='/setOutput'>Set output</a><br>"
      "<a href='/clearOutput'>Clear output</a><br>"
      "</body>"
      "</html>"
      );
}

Chapter #4

This chapter explains how to show an input value updated dynamically in the web page. Obviously it is also possible to show other dynamic values of variables o any other stuff.

      1. Show the input value

An HTML text element is added into the web page, to put the input value. To update it dynamically, it is necessary to add a little of Javascript into the web page. The Javascript updateInputValue() function sends a request to the server to obtain the input value and, when the response is received, it inserts the received text into the input-value HTML element using the getElementById() function and the innerHTML property.

client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println();
client.println(
    "<!doctype html>"
    "<html>"
    "<head>"
    "<title>Hello PLC!</title>"
    "</head>"
    "<body>"
    "<h1>Hello PLC!</h1>"
    "<a href='/setOutput'>Set output</a><br>"
    "<a href='/clearOutput'>Clear output</a><br>"
    "<p>Input value: <span id='input-value'></span></p>"
    "<script>"
    "function updateInputValue() {"
    "  var xhttp = new XMLHttpRequest();"
    "  xhttp.onreadystatechange = function() {"
    "    if (this.readyState == 4 && this.status == 200) {"
    "      document.getElementById('input-value').innerHTML = this.responseText;"
    "    }"
    "  };"
    "  xhttp.open('GET', 'inputValue', true);"
    "  xhttp.send();"
    "}"
    "setInterval(updateInputValue, 1000);"
    "</script>"
    "</body>"
    "</html>"
    );
      1. Process the input value request

The sketch should recognize the /inputValue request, and get the input value using the digitalRead()function and send the response to the client.

// ...
} else if (path.compareTo("/inputValue") == 0) {
  // Get the input value
  int inputValue = digitalRead(I0_1);

  // Send input value
  sendText(client, inputValue == HIGH ? "HIGH" : "LOW");
} else {
//...

It would be pretty good to send a JSON text or any of these sophisticated structured text protocols, and it would be useful for more complex applications, but in this case it is sufficient to send a plain text response.

void sendText(EthernetClient &client, const char *text) {
  client.println("HTTP/1.1 200 OK");
  client.println("Content-Type: text/plain");
  client.println();
  client.println(text);
}
      1. Final code review

#include <Ethernet2.h>

// Server creation
EthernetServer server(80);

void setup() {
  Serial.begin(9600UL);
  Serial.println("WebServer started");

  // Ethernet initialization
  byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
  byte ip[] = {10, 10, 10, 25};
  Ethernet.begin(mac, ip);

  // Server initialization
  server.begin();
}

void loop() {
  // Wait for clients
  EthernetClient client = server.available();
  if (client) {
    // Parse request
    bool emptyLine = true;
    String header;
    bool firstLine = true;

    while (client.connected()) {
      if (client.available()) {
        char c = client.read();

        if (firstLine) {
          header += c;
        }

        // The request is finished when an empty line is received
        if (emptyLine && (c == '\n')) {
          // Get path from the header line
          int pos = header.indexOf(' ');
          String path = header.substring(pos + 1, header.indexOf(' ', pos + 1));

          // Act depending on path
          if (path.compareTo("/") == 0) {
            sendMainPage(client);
          } else if (path.compareTo("/setOutput") == 0) {
            // Set output
            digitalWrite(Q0_0, HIGH);

            // Redirect response
            redirectToMainPage(client);
          } else if (path.compareTo("/clearOutput") == 0) {
            // Clear output
            digitalWrite(Q0_0, LOW);

            // Redirect response
            redirectToMainPage(client);
          } else if (path.compareTo("/inputValue") == 0) {
            // Get the input value
            int inputValue = digitalRead(I0_1);

            // Send input value
            sendText(client, inputValue == HIGH ? "HIGH" : "LOW");
          } else {
            // Unrecognized path
            sendNotFound(client);
          }

          client.flush();

          // Close connection
          client.stop();

          break;
        }

        // The request is finished when a blank line is received
        if (c == '\n') {
          emptyLine = true;
          firstLine = false;
        } else if (c != '\r') {
          emptyLine = false;
        }
      }
    }
  }
}

void sendNotFound(EthernetClient &client) {
  // Path not found
  client.println("HTTP/1.1 404 Not Found");
  client.println("Content-Type: text/plain");
  client.println();
  client.println("404 Not Found");
}

void redirectToMainPage(EthernetClient &client) {
  // Redirect to main page
  client.println("HTTP/1.1 303 See Other");
  client.println("Location: /");
  client.println();
}

void sendMainPage(EthernetClient &client) {
  // Send the response
  client.println("HTTP/1.1 200 OK");
  client.println("Content-Type: text/html");
  client.println();
  client.println(
      "<!doctype html>"
      "<html>"
      "<head>"
      "<title>Hello PLC!</title>"
      "</head>"
      "<body>"
      "<h1>Hello PLC!</h1>"
      "<a href='/setOutput'>Set output</a><br>"
      "<a href='/clearOutput'>Clear output</a><br>"
      "<p>Input value: <span id='input-value'></span></p>"
      "<script>"
      "function updateInputValue() {"
      "  var xhttp = new XMLHttpRequest();"
      "  xhttp.onreadystatechange = function() {"
      "    if (this.readyState == 4 && this.status == 200) {"
      "      document.getElementById('input-value').innerHTML = this.responseText;"
      "    }"
      "  };"
      "  xhttp.open('GET', 'inputValue', true);"
      "  xhttp.send();"
      "}"
      "setInterval(updateInputValue, 1000);"
      "</script>"
      "</body>"
      "</html>"
      );
}

void sendText(EthernetClient &client, const char *text) {
  client.println("HTTP/1.1 200 OK");
  client.println("Content-Type: text/plain");
  client.println();
  client.println(text);
}

Chapter #5

Once the basic actions are implemented, it is also possible to create more complex ones, as the RS-232/RS-485/RS-422 communications are. This chapter will explain you how to send RS-485 data from the web application, but it is also possible to show received data from the RS-485.

      1. Add the RS-485 field

The first step could be to add an HTML form to send the RS-485 data to the PLC. In this case it is added an HTML input field to type alphanumeric data. When the send button is clicked, the sendRS485()function is called. This function still does not exist! It will be created in the next step.

<form action='javascript:void(0)' onsubmit='sendRS485()'>
  Data: <input type='text' pattern='[a-zA-Z0-9]+' title='Alphanumeric string' value='' id='rs485Data'>
  <input type='submit' value='Send'>
</form>
      1. Javascript send function

The sendRS485() Javascript function gets the data from the HTML input field and sends it integrated into the URL of the HTTP request: /rs485/<data>.

function sendRS485() {
  var el = document.getElementById('rs485Data');
  if (el) {
    var xhttp = new XMLHttpRequest();
    xhttp.open('GET', '/rs485/' + el.value, true);
    xhttp.send();
  }
}
      1. Parse the request

The RS-485 data is contained into the request URL. The sketch should parse it to obtain the data to send. When the URL starts with the ‘/rs485/’ string, it is considered to be an RS-485 data request to be sent through RS-485, so the sketch extracts the beginning of the URL to get the RS-485 data. As the client ingnores the response, only an OK text is sent.

  // ...
} else if (path.startsWith("/rs485/")) {
  // Get data from the request
  String data = path.substring(7);

  // Send a response
  sendText(client, "OK");
  // ...
      1. Set up RS-485

Before sending data through RS-485 it is mandatory to start the driver using the RS485.begin()function into the setup(). This function is available after including the RS485.h library.

#include <RS485.h>

To initializa the RS-485 you MUST set the serial rate. In this case the rate is set to 9600bps.

// ...
RS485.begin(9600);
// ...
      1. Send data through RS-485

The RS485.write() function is called to send data through the RS-485.

  // ...
} else if (path.startsWith("/rs485/")) {
  // Get data from the request
  String data = path.substring(7);

  // Send data through RS-485
  RS485.write(data.c_str());

  // Send a response
  sendText(client, "OK");
} else {
  // ...
      1. Final code review

#include <RS485.h>
#include <Ethernet2.h>

// Server creation
EthernetServer server(80);

void setup() {
  Serial.begin(9600UL);
  Serial.println("WebServer started");

  // RS-485 initialization
  RS485.begin(9600);

  // Ethernet initialization
  byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
  byte ip[] = {10, 10, 10, 25};
  Ethernet.begin(mac, ip);

  // Server initialization
  server.begin();
}

void loop() {
  // Wait for clients
  EthernetClient client = server.available();
  if (client) {
    // Parse request
    bool emptyLine = true;
    String header;
    bool firstLine = true;

    while (client.connected()) {
      if (client.available()) {
        char c = client.read();

        if (firstLine) {
          header += c;
        }

        // The request is finished when an empty line is received
        if (emptyLine && (c == '\n')) {
          // Get path from the header line
          int pos = header.indexOf(' ');
          String path = header.substring(pos + 1, header.indexOf(' ', pos + 1));

          // Act depending on path
          if (path.compareTo("/") == 0) {
            sendMainPage(client);
          } else if (path.compareTo("/setOutput") == 0) {
            // Set output
            digitalWrite(Q0_0, HIGH);

            // Redirect response
            redirectToMainPage(client);
          } else if (path.compareTo("/clearOutput") == 0) {
            // Clear output
            digitalWrite(Q0_0, LOW);

            // Redirect response
            redirectToMainPage(client);
          } else if (path.compareTo("/inputValue") == 0) {
            // Get the input value
            int inputValue = digitalRead(I0_1);

            // Send input value
            sendText(client, inputValue == HIGH ? "HIGH" : "LOW");
          } else if (path.startsWith("/rs485/")) {
            // Get data from the request
            String data = path.substring(7);

            // Send data through RS-485
            RS485.write(data.c_str());

            // Send a response
            sendText(client, "OK");
          } else {
            // Unrecognized path
            sendNotFound(client);
          }

          client.flush();

          // Close connection
          client.stop();

          break;
        }

        // The request is finished when a blank line is received
        if (c == '\n') {
          emptyLine = true;
          firstLine = false;
        } else if (c != '\r') {
          emptyLine = false;
        }
      }
    }
  }
}

void sendNotFound(EthernetClient &client) {
  // Path not found
  client.println("HTTP/1.1 404 Not Found");
  client.println("Content-Type: text/plain");
  client.println();
  client.println("404 Not Found");
}

void redirectToMainPage(EthernetClient &client) {
  // Redirect to main page
  client.println("HTTP/1.1 303 See Other");
  client.println("Location: /");
  client.println();
}

void sendMainPage(EthernetClient &client) {
  // Send the response
  client.println("HTTP/1.1 200 OK");
  client.println("Content-Type: text/html");
  client.println();
  client.println(
      "<!doctype html>"
      "<html>"
      "<head>"
      "<title>Hello PLC!</title>"
      "</head>"
      "<body>"
      "<h1>Hello PLC!</h1>"
      "<a href='/setOutput'>Set output</a><br>"
      "<a href='/clearOutput'>Clear output</a><br>"
      "<p>Input value: <span id='input-value'></span></p>"
      "<form action='javascript:void(0)' onsubmit='sendRS485()'>"
      "Data: <input type='text' pattern='[a-zA-Z0-9]+' title='Alphanumeric string' value='' id='rs485Data'>"
      "<input type='submit' value='Send'>"
      "</form>"
      "<script>"
      "function updateInputValue() {"
      "  var xhttp = new XMLHttpRequest();"
      "  xhttp.onreadystatechange = function() {"
      "    if (this.readyState == 4 && this.status == 200) {"
      "      document.getElementById('input-value').innerHTML = this.responseText;"
      "    }"
      "  };"
      "  xhttp.open('GET', 'inputValue', true);"
      "  xhttp.send();"
      "}"
      "setInterval(updateInputValue, 1000);"
      "function sendRS485() {"
      "  var el = document.getElementById('rs485Data');"
      "  if (el) {"
      "    var xhttp = new XMLHttpRequest();"
      "    xhttp.open('GET', '/rs485/' + el.value, true);"
      "    xhttp.send();"
      "  }"
      "}"
      "</script>"
      "</body>"
      "</html>"
      );
}

void sendText(EthernetClient &client, const char *text) {
  client.println("HTTP/1.1 200 OK");
  client.println("Content-Type: text/plain");
  client.println();
  client.println(text);
}