Servidor web asíncrono con el módulo ESP8266

Este ejemplo básico de un servidor web asíncrono para usarlo en el esp8266 cargándolo desde el Sketch de arduino gracias a que con un flasheo del firmware podemos reprogramarlo como si de un atmega328p se tratase, y olvidándonos de los tediosos comandos AT como hacemos aqui Flashear Esp8266, fucK AT-commands usaremos el sensor DHT22 que es mucho más preciso que su archienemigo el DHT11, necesitaremos un conversor serial como el que contiene el arduino por medio de los pines tx/rx si no tendríamos que comprar un conversor usb-serial ttl muy económicos.

Estas son breves característica de ambos sensores:

image

DHT-22 pinout

image

Fuente regulable MB102

Usaremos esta fuente de 3.3v | 5v con un transformador externo de 2000mAh más salida de 12vc. Se ve en la fuente de poder MB102, el jumper que nos permite obtener 3.3v, se puede comprobar con un multímetro con puntas de caiman, y o agujas.

Un transformador de menos de 6.5vdc no servirá para la MB102

image

> Sin Fritzing

Pero esto sirve como vista final.

  • Una resistencia de 10Kohm( muchos usan 4.7k ) como pull-up entre el DATA y VCC(3.3v)

  • La GND la obtengo de la fuente de poder.

image

Escribiendo el código del servidor web asíncrono (AsynchronousWebServer)

Usaremos las librerías siguientes:

#include <ESP8266WiFi.h> (1)
#include <ESPAsyncTCP.h> (2)
#include <ESPAsyncWebServer.h>
#include <ArduinoJson.h> (3)
#include <DHTStable.h> (4)
1 Para la conexión wifi (ESP8266WiFi)
2 Para el servidor asincrono así como la ESPAsyncWebServer
3 Creación del json (ArduinoJson) para la api Rest
4 Donde <DHTStable.h> es una lib más actual, y del sensor de humedad y temperatura <DHTStable.h>
#define DHT22_PIN 4 (1)
DHTStable DHT;
1 Definimos las variables para usar el pin del sensor DHT22
AsyncWebServer server(8081); (1)
1 Definimos el puerto del servidor en el 8081, tengo los otros típicos en uso XD
//Endpoints
const char* TEMPERATURES = "/async-esp8266/api/v1/dht22/temperatures"; (1)
const char* HUMIDITIES = "/async-esp8266/api/v1/dht22/humidities"; (2)
const char* LED_STATUS = "/async-esp8266/api/v1/led"; (3)
1 Endpoints de temperatura
2 Endpoints de humedad
3 Endpoints para cambiar el estado del led

Configuración de red wifi, colocamos nombre y password.

const char* ssid = "Tu red";
const char* password = "misuperhax0rpassword";
//esperando por conexión
while (WiFi.status() != WL_CONNECTED) { (1)
  delay(1000);
  Serial.println("Connecting to WiFi..");
}
1 While que se intentara conectar a la red wifi, y notificara por la consola.
void loop(void) {
  DHT.read22(DHT22_PIN); (1)
  t = DHT.getTemperature();
  h = DHT.getHumidity();
  Serial.print("Humidity: ");
  Serial.print(h);
  Serial.println(HUMIDITY);
  Serial.print("Temperature: ");
  Serial.print(t);
  Serial.println(TMP);
  delay(2000); (2)
}
1 Aqui la función loop() necesaria para hacer la lectura del sensor,
2 Colocar un delay de 2 segundos

la función initRoute simplemente saluda con un texto al visitar la url principal /

//Hello world!
void initRoute() {
   server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
        request -> send(OK_HTTP, TEXT_PLAIN, "Welcome webserver Esp8266");
   });
}

Endpoint de temperatura

/async-esp8266/api/v1/dht22/temperatures
//Temperaturesº
void temperatures() {
  server.on(TEMPERATURES, HTTP_GET, [](AsyncWebServerRequest * request) {
    String json;
    StaticJsonDocument<300> doc;
    doc["sensor"] = "DHT-22";
    doc["type"] = "AM2302";
    doc["temperature"] = t;
    serializeJson(doc, json);
    request -> send(OK_HTTP, APPLICATION_JSON, json);
  });
}
image

Endpoint de humedad

/async-esp8266/api/v1/dht22/humidities
//Humidities%
void humidities() {
  server.on(HUMIDITIES, HTTP_GET, [](AsyncWebServerRequest * request) {
    String json;
    StaticJsonDocument<300> doc;
    doc["sensor"] = "DHT-22";
    doc["type"] = "AM2302";
    doc["humidity"] = h;
    serializeJson(doc, json);
    request -> send(OK_HTTP, APPLICATION_JSON, json);
  });
}
image

La función handeLED la usaremos para ver el estado del led, prender y apagarlo, lo típico actualizaremos en el pin 2 a HIGH o LOW, dependiendo el query parameters.

// On/off Led
void handleLED() { (1)
  //Show led status
  server.on(LED_STATUS, HTTP_GET, [](AsyncWebServerRequest * request) {
    ledstatus = digitalRead(LED_PIN);
    if (ledstatus == HIGH) {
      //status is ON
      request -> send(OK_HTTP, TEXT_PLAIN, "Status ON");
    } else {
      //status is Off
      request -> send(OK_HTTP, TEXT_PLAIN, "Status OFF");
    }
  });

  //async-esp8266/api/v1/led?id=2&newstatus=on //ON
  //async-esp8266/api/v1/led?id=2&newstatus=off //OFF
  server.on(LED_STATUS, HTTP_PUT, [](AsyncWebServerRequest * request) {
    String id = request-> arg("id"); (2)
    String newstatus = request-> arg("newstatus"); (3)
    if (id.toInt() == LED_PIN && newstatus.equalsIgnoreCase("on")) {
      Serial.println("PIN HIGH");
      digitalWrite(LED_PIN, HIGH);
      request -> send(OK_HTTP, TEXT_PLAIN, oK);
    } else if (id.toInt() == LED_PIN && newstatus.equalsIgnoreCase("off")) {
      Serial.println("PIN LOW");
      digitalWrite(LED_PIN, LOW);
      request -> send(OK_HTTP, TEXT_PLAIN, oK);
    } else {
      request -> send(400, TEXT_PLAIN, "Bad Request");
    }
  });
}
1 Función para manejar el control del led, se debe factorizar un poco más, dividir mejor en otras funciones
2 Obtenemos el id del led
3 Por medio del estatus, apagamos o lo prendemos.

Código completo

Endpoints

Operation URI

GET

/async-esp8285/api/v1/dht22

GET

/async-esp8285/api/v1/dht22/temperatures

GET

/async-esp8285/api/v1/dht22/humidities

PUT

/async-esp8285/api/v1/led

query-parameters ON

query-parameters OFF

led?id=2&newstatus=on

led?id=2&newstatus=off

/**
 *
 * Make simple Asynchronous webserver using DHT22 for log temperature and humidity.
 *
 */
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <ArduinoJson.h>
#include <DHTStable.h>
#define DHT22_PIN 4

DHTStable DHT;

AsyncWebServer server(8081);// Create a asynchronous webserver object that listens for HTTP request on port 8081

int led_status = 0;
float t = 0;
float h = 0;

const int LED_PIN = 2;
const int OK_HTTP = 200;

//Endpoints
const char* DHT22 = "/async-esp8285/api/v1/dht22";
const char* TEMPERATURES = "/async-esp8285/api/v1/dht22/temperatures";
const char* HUMIDITIES = "/async-esp8285/api/v1/dht22/humidities";
const char* LED = "/async-esp8285/api/v1/led";

const char* DHT_22 = "DHT-22";
const char* AM2302 = "AM2302";
const char* CENTIGRADE = "ºC";
const char* PERCENTAGE = "%";
const char* TEXT_PLAIN = "text/plain";
const char* APPLICATION_JSON = "application/json";
const char* ON = "ON";
const char* OFF = "OFF";
const char* ID = "id";
const char* NEW_STATUS = "status";
const char* SENSOR = "sensor";
const char* TYPE = "type";
const char* TEMPERATURE = "temperature";
const char* HUMIDITY = "humidity";

//Wifi network credentials
const char* ssid = "Filomena 2.4";
const char* password = "zAHF4n+MMHniN*G"; //aqui les dejo mi contraseña para que se conecten, wifi free :)

void setup(void) {
  Serial.begin(115200);
  pinMode(LED_PIN, OUTPUT);
  delay(10);

  Serial.println('\n');
  WiFi.begin(ssid, password);

  //wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi..");
  }

  Serial.println('\n');
  Serial.print("Connected to ");
  Serial.println(WiFi.SSID());
  Serial.print("IP address:\t");
  Serial.println(WiFi.localIP());

  initRoute();
  temperatures();
  humidities();
  dht22();
  handleLED();
  handleNotFound();

  server.begin();// Actually start the server
  Serial.println("HTTP server started");
}

void loop(void) {
  DHT.read22(DHT22_PIN);
  t = DHT.getTemperature();
  h = DHT.getHumidity();
  Serial.print("Humidity: ");
  Serial.print(h);
  Serial.println(PERCENTAGE);
  Serial.print("Temperature: ");
  Serial.print(t);
  Serial.println(CENTIGRADE);
  delay(2000);
}

//Welcome webserver Esp8266!!!
void initRoute() {
  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
    request -> send(OK_HTTP, TEXT_PLAIN, "Welcome webserver Esp8266");
  });
}

//Temperaturesª
void temperatures() {
  server.on(TEMPERATURES, HTTP_GET, [](AsyncWebServerRequest * request) {
    String json;
    StaticJsonDocument<300> doc;
    doc[SENSOR] = DHT_22;
    doc[TYPE] = AM2302;
    doc[TEMPERATURE] = t;
    serializeJson(doc, json);
    request -> send(OK_HTTP, APPLICATION_JSON, json);
  });
}

//Humidities%
void humidities() {
  server.on(HUMIDITIES, HTTP_GET, [](AsyncWebServerRequest * request) {
    String json;
    StaticJsonDocument<300> doc;
    doc[SENSOR] = DHT_22;
    doc[TYPE] = AM2302;
    doc[HUMIDITY] = h;
    serializeJson(doc, json);
    request -> send(OK_HTTP, APPLICATION_JSON, json);
  });
}

//DHT22 humidities% and temperaturesª
void dht22() {
  server.on(DHT22, HTTP_GET, [](AsyncWebServerRequest * request) {
    String json;
    StaticJsonDocument<300> doc;
    doc[SENSOR] = DHT_22;
    doc[TYPE] = AM2302;
    doc[TEMPERATURE] = t;
    doc[HUMIDITY] = h;
    serializeJson(doc, json);
    request -> send(OK_HTTP, APPLICATION_JSON, json);
  });
}

// On/off Led
void handleLED() {
  //Show led status
  server.on(LED, HTTP_GET, [](AsyncWebServerRequest * request) {
    led_status = digitalRead(LED_PIN);
    if (led_status == HIGH) {
      String json;
      StaticJsonDocument<300> doc;
      doc[ID] = LED_PIN;
      doc[NEW_STATUS] = ON;
      serializeJson(doc, json);
      request -> send(OK_HTTP, APPLICATION_JSON, json);
    } else {
      String json;
      StaticJsonDocument<300> doc;
      doc[ID] = LED_PIN;
      doc[NEW_STATUS] = OFF;
      serializeJson(doc, json);
      request -> send(OK_HTTP, APPLICATION_JSON, json);
    }
  });
  //async-esp8266/api/v1/led?id=2&status=on //ON
  //async-esp8266/api/v1/led?id=2&status=off //OFF
  server.on(LED, HTTP_PUT, [](AsyncWebServerRequest * request) {
    String id = request-> arg(ID);
    String new_status = request-> arg(NEW_STATUS);
    if (id.toInt() == LED_PIN && new_status.equalsIgnoreCase(ON)) {
      Serial.println("PIN HIGH");
      digitalWrite(LED_PIN, HIGH);
      String json;
      StaticJsonDocument<300> doc;
      doc[ID] = LED_PIN;
      doc[NEW_STATUS] = ON;
      serializeJson(doc, json);
      request -> send(OK_HTTP, APPLICATION_JSON, json);
    } else if (id.toInt() == LED_PIN && new_status.equalsIgnoreCase(OFF)) {
      Serial.println("PIN LOW");
      digitalWrite(LED_PIN, LOW);
      String json;
      StaticJsonDocument<300> doc;
      doc[ID] = LED_PIN;
      doc[NEW_STATUS] = OFF;
      serializeJson(doc, json);
      request -> send(OK_HTTP, APPLICATION_JSON, json);
    } else {
      request -> send(400, TEXT_PLAIN, "Bad Request");
    }
  });
}

//404
void handleNotFound() {
  //Error 404 not found
  server.onNotFound([](AsyncWebServerRequest * request) {
    request->send(404, TEXT_PLAIN, "Sorry, 404 not found.");
  });
}

// -- EOF --

Con respecto a la seguridad

Seria muy buena idea añadir autenticación y certificados ssl o redireccion via https forzada con un reverse-proxy nginx, así no tenemos que poner tanto código en el micro.