/ esp8266

ESP8266: part II

This is the part II of my ESP8266 series.

In my last post I've discussed my first steps with the ESP8266 "IoT" module. My plan is to create a series of posts until I finished a project (which will be later explained), describing my whole experience with it: from the baby steps until the project is finished. And today we're going wireless!

The problem and our goal

Problem?

I won't go into details about which modes the ESP8266 module supports (client and AP?) or how it works regarding WiFi. I want to discuss something high-level this time: how to interact with it. For that, let's consider the following:

We need a device that connects to your wireless network and gets an IP address from DHCP. Such device must also listen for commands, which will then cause it to perform some I/O on its pins. Those commands can arrive at any protocol: it doesn't really matter, as long as we can code something to understand it and a client to send the data. That's our goal right now.

Connecting to the wireless network automatically is essential. As I said before, we don't want to have any serial communication on the device, and debugging will be done with the built-in LED. Once connected, we can communicate with it. Choosing the correct protocol is both a matter of performance vs. complexity: we don't want the device to take forever processing a command, but we also don't want to code over a thousand lines only to blink a LED.

Sending data to a network-enabled device is nothing new. There are many methods of doing this, but I'd like to discuss only two right now, the ones that I have experience with: an UDP-based protocol and HTTP (TCP). The first one was my first experience with the Ethernet Shield for an Arduino: by encoding the data in a very specific format, I was able to send commands to the Arduino to change the values of its outputs. The packet was really small (less than 4 bytes), but that's actually an advantage: I was able to flood the damn thing with lots of sequential data, allowing me to use most of the power of the AVR's CPU for doing what I want and not decoding huge chunks of data. That worked pretty well, actually. The last time I disassembled the project it was still working with that (as far as I remember, at least).

The second option is what I like to call the "new hacker" friendly option. Everybody knows HTTP and can play with it. So if we use it as the communication protocol on the board, people can send commands to it over well known methods. You can even use curl for that if you want. But, of course, there are a few downsides on this: TCP is more complex than UDP, which requires more CPU power to process the packet headers and payloads. HTTP is also string-based protocol (aka pretty much everything is a string), so that requires a lot of conversions to integers and other C types that we can easily deal with. On a very small CPU with pretty much no power whatsover, that can be an issue. However, we're playing the ESP8266, which has an 80Mhz ARM CPU on it! I'm pretty sure we can deal with that overhead - and in case everything goes wrong, we can always switch back to our old UDP-based protocol, right?

Are we there yet?

But wait, there's more!

Ok, so now we have a protocol: HTTP. We also have our wireless network SSID and password to connect to, so the connection and communication are done. Now comes the tricky part: remember when I said we need 3 pins for this project? Well, we need to choose those pins - now.

Let's take a look at the pinout for the ESP8266 chip:

ESP8266 pins
(Source: http://www.esp8266.com/wiki/doku.php?id=esp8266_gpio_pin_allocations)

As you can see, here it has way more pins than my board. Well, that's because 1) you can't use all pins since some are for internal connections (with the flash, for example), and 2) they didn't expose us all of them (yey...). Originally, as we know already, we have only GPIO0 and GPIO2 as free pins. But now we also need a third one. For that, we'll use pin 3 - U0RXD_U. There is a good reason for using this pin and not pin 1 (U0TXD_U): the built-in LED is attached to the TX of our UART, so that you can see it blinking when data is being transmitted over serial. But we also want that LED, so we can't have that pin for our data and for the LED. Therefore, pin 3 is our choice.

Also, we plan to use those 3 pins as PWM ones. That means that, with Arduino, we'll use the analogWrite function, which automatically sets the width of the pulse for us. The maximum value is, by default, the PWMRANGE constant, which is 1023. It can be changed, but we won't do that right now. Baby steps! :-)

Let's code it!

Your coding is bad and you should feel bad

Ok, now we have everything we wanted to. So let's code! We need something that, on the boot process, connects to our wireless network, fires up a HTTP server and listen for requests. For each HTTP request, it reads the arguments on it and changes the I/O accordingly. The address for commands will be /set and can have the arguments red, green and blue, which changes the outputs of the pins for each color. Pretty easy code:

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>

// The pins!
#define PIN_R 0 // GPIO0
#define PIN_G 2 // GPIO2
#define PIN_B 3 // UART TX

// Our wireless config: SSID and password.
// (no, that is not my wireless network, sorry)
const char* ssid = "Heeeeey WiFi";
const char* pass = "nevergonnagiveyouup!";

// Our HTTP server. Creates an instance of it on port 80.
ESP8266WebServer server(80);

// HTTP handler for the requests to "/set".
void handleSet() {
  int red = -1, green = -1, blue = -1;

  // Process each argument looking for our colors. If found,
  // cast it to int.
  for (uint8_t i = 0; i < server.args(); i++){
    if (server.argName(i) == "red") {
      red = server.arg(i).toInt();
    } else if (server.argName(i) == "green") {
      green = server.arg(i).toInt();
    } else if (server.argName(i) == "blue") {
      blue = server.arg(i).toInt();
    }
  }

  // If something change, write that value on the correct output.
  if (red != -1 || green != -1 || blue != -1) {
    if (red != -1) analogWrite(PIN_R, red);
    if (green != -1) analogWrite(PIN_G, green);
    if (blue != -1) analogWrite(PIN_B, blue);

    // 200 OK since something changed.
    server.send(200, "text/plain", "Ok.");
  } else {
    // 500 Internal error since something was missing.
    server.send(500, "text/plain", "Unknown args.");
  }
}

// Setup.
void setup() {
  // Built-in LED is an output and it's on (LOW = on here).
  pinMode(BUILTIN_LED, OUTPUT);
  digitalWrite(BUILTIN_LED, LOW);

  // Our other pins are also outputs.
  pinMode(PIN_R, OUTPUT);
  pinMode(PIN_G, OUTPUT);
  pinMode(PIN_B, OUTPUT);

  // Time to go wireless!
  WiFi.begin(ssid, pass);

  // Let's blink our built-in LED while the connection is not established.
  // (this is *amazing* for the debugging it - seriously!)
  while (WiFi.status() != WL_CONNECTED) {
    delay(250);
    digitalWrite(BUILTIN_LED, !digitalRead(BUILTIN_LED));
  }

  // We are online, so let's make the built-in LED on.
  digitalWrite(BUILTIN_LED, LOW);

  // Sets our handler for the path "/".
  // Let's just output 200 OK here for now.
  server.on("/", []() {
    server.send(200, "text/plain", "I'm alive \\o/");
  });

  // Sets our handler for 404 requests (page not found).
  // Returning 404 is more than enough for us.
  server.onNotFound([]() {
    server.send(404, "text/plain", "Not found.");
  });

  // Sets our handler for the path "/set" to our handleSet function.
  server.on("/set", handleSet);

  // Fire up the HTTP server!
  server.begin();
}

// Looping...
void loop() {
  // Handle new clients on our HTTP server.
  server.handleClient();
}

Once we've built that code (which takes almost 70% of the storage space, btw) and uploaded it, we'll see the ESP8266 blink its built-in blue LED. That means it's connecting to your wireless network. After a few seconds, it will be done with it and will keep on: we're now up and running! So let's play with it: by using our browser (or curl, wget or even netcat) we can send commands to it (to the /set path). Ah, and since IP address was defined by DHCP, I needed to go to my router's administration page[1] and get it from there: 172.16.1.155. Here are some examples of URLs we can access to set the values on the outputs:

  • http://172.16.1.155/set?red=1023 (full red [2])
  • http://172.16.1.155/set?red=512&blue=512 (half red, half blue)
  • http://172.16.1.155/set?red=0&blue=512&green=1023 (no red, half blue, full green)

Nice, huh? We now have a ESP8266 which changes the PWM values of its outputs according to HTTP requests. How cool is that? Imagine that 10 years ago :-)

Yey!

Anyway, that's it for now. The next post will describe the project itself and why I still didn't finish it (shame on me). Enjoy it and good luck!


Previous part | Next part


  1. That's not completely necessary. On UDP, for example, we can list on broadcast. Keeping HTTP will require another protocol for automatically finding the device on the network (a sort of discovery protocol) or even a static IP address (either on it or by DHCP reservations). ↩︎

  2. Somehow I could also set 1024 as the value for it, even though the max value should be 1023. I'm still trying to figure it out though. ↩︎