En la anterior entrada hemos visto como construir una regleta wifi a partir de unos pocos componentes, queda todavía unos cuantos pasos para hacerla funcionar. En esta entrada veremos como programar el controlador ESP8266 con el entorno de Arduino de forma que la regleta sea capaz de pedir a un servidor externo los comandos de activación y desactivación del relé y enviar a la vez la temperatura y la humedad.
Para poder transferir el programa necesitaremos un convertidor usb – serie que disponga de las señales DTR y RTS. Se puede hacer con uno de 3 hilos pero es más incómodo ya que no es capaz de poner automáticamente desde el entorno la placa en modo programación y lo tendremos que hacer a mano a base de puentes entre pines. Los basados en el chip CP2102 son muy baratos (entorno a 1€)
En primer lugar debemos instalar la última versión del entorno de desarrollo de Arduino (1.6.5 en este momento).
Lo podemos descargar desde este enlace:
https://www.arduino.cc/en/Main/Software
Cuando acabemos de instalar el software abrimos las preferencias y añadimos http://arduino.esp8266.com/stable/package_esp8266com_index.json en el cuadro de «Additional Boards Manager»
Ahora que tenemos las herramientas vamos a definir el funcionamiento básico del programa:
- Al empezar nos conectamos a una red wifi conocida almacenada en la eeprom
- Si hay un error de conexión con la red wifi, creamos un punto de acceso local y nos ponemos en modo servidor a la espera de la configuración correcta de ssid y password.
- Conectamos un «socket» al servidor remoto que nos dará las instrucciones
- Cada cierto tiempo leemos la temperatura y la humedad y se la enviamos al servidor (indicando a este que estamos vivos), el servidor nos responde con el estado deseado del relé
- Si hay un error de conexión con el servidor, lo primero es apagar el relé por seguridad, lo segundo reintentar la conexión. Si el problema es de conexión al wifi reiniciamos la placa para permitir la reconfiguración manual de la placa.
Para los pasos 1 y 2 tenemos un buen ejemplo en el siguiente enlace:
https://github.com/chriscook8/esp-arduino-apboot
Lo vamos a simplificar un poco dejandolo así:
#include <ESP8266WiFi.h> #include <WiFiClient.h> #include <EEPROM.h> #define DBG_MSG(txt) Serial.print("("+String(millis())+") - ");Serial.println(txt) // Initialize DHT sensor // NOTE: For working with a faster than ATmega328p 16 MHz Arduino chip, like an ESP8266, // you need to increase the threshold for cycle counts considered a 1 or 0. // You can do this by passing a 3rd parameter for this threshold. It's a bit // of fiddling to find the right value, but in general the faster the CPU the // higher the value. The default for a 16mhz AVR is a value of 6. For an // Arduino Due that runs at 84mhz a value of 30 works. // This is for the ESP8266 processor on ESP-01 float humidity, temp_c; unsigned long previousMillis = 0; // will store last temp was read const long interval = 15000; // interval at which to read sensor const char* host = "#####.###"; int i; WiFiClient client; const int httpPort = ###; WiFiServer server(80); const char* ssid = "REGLETA_WF"; void setup() { // Utilizamos la señal GPIO2 como salida para el relé pinMode(0,INPUT); pinMode(2, OUTPUT); digitalWrite(2,HIGH); // Inicializamos el sensor DHT11 humidity=999.99; temp_c=999.99; Serial.begin(115200); while (1) { EEPROM.begin(512); delay(10); Serial.println(); Serial.println(); Serial.println("INICIO ... "); // read eeprom for ssid and pass Serial.println("Leyendo de la EEPROM ssid"); String esid; for (int i = 0; i < 32; ++i) { int c=EEPROM.read(i); if (!c) break; esid += char(c); } Serial.print("SSID: "); Serial.println(esid); Serial.println("Leyendo password"); String epass = ""; for (int i = 32; i < 96; ++i) { int c=EEPROM.read(i); if (!c) break; epass += char(c); } Serial.print("PASS: "); Serial.println(epass); if ( esid.length() > 0 ) { WiFi.begin(esid.c_str(), epass.c_str()); if ( testWifi()) { // Comprobar la conexión Serial.println("WiFi conectado"); Serial.println("DIR. IP: "); Serial.println(WiFi.localIP()); // Conectamos con el Servidor remoto client.connect(host, httpPort); return; } } // La conexión no ha sido posible, entra en modo local para permitir configurar la red setupAP(); } } int testWifi(void) { int t = 0; Serial.println("Esperando conexión"); while ( (t++) < 30 ) { if (WiFi.status() == WL_CONNECTED) return(1); delay(500); Serial.print(WiFi.status()); //WL_NO_SHIELD = 255,WL_IDLE_STATUS = 0,WL_NO_SSID_AVAIL = 1,WL_SCAN_COMPLETED = 2,WL_CONNECTED = 3,WL_CONNECT_FAILED = 4,WL_CONNECTION_LOST = 5,WL_DISCONNECTED = 6 } Serial.println("Problemas de conexión"); return(0); } void launchWeb() { Serial.println(""); Serial.println("IPs"); Serial.println(WiFi.localIP()); Serial.println(WiFi.softAPIP()); // Start the server server.begin(); Serial.println("Server started"); int b = 20; int c = 0; while(!web_serv()); } void setupAP(void) { WiFi.softAP(ssid); delay(100); Serial.println("softap"); Serial.println(""); launchWeb(); Serial.println("over"); } int web_serv() { // Espera tener un cliente disponible WiFiClient client = server.available(); if (!client) { return(0); } Serial.println(""); Serial.println("Nuevo cliente"); // Lee el mensaje del cliente String req = client.readStringUntil('\r'); // Separa el path "GET /path HTTP/1.1" int addr_start = req.indexOf(' '); int addr_end = req.indexOf(' ', addr_start + 1); if (addr_start == -1 || addr_end == -1) { Serial.print("Petición incorrecta: "); Serial.println(req); return(0); } req = req.substring(addr_start + 1, addr_end); Serial.print("Petición: "); Serial.println(req); client.flush(); String s; if (req == "/") { IPAddress ip = WiFi.softAPIP(); String ipStr = String(ip[0]) + '.' + String(ip[1]) + '.' + String(ip[2]) + '.' + String(ip[3]); s = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<!DOCTYPE HTML>\r\n<html>Hello from ESP8266 at "; s += ipStr; s += "<p>"; s += "<form method='get' action='a'><label>SSID: </label><input name='ssid' length=32><p>PASS: <input name='pass' length=64><p><input type='submit'></form>"; s += "</html>\r\n\r\n"; Serial.println("Sending 200"); } else if ( req.startsWith("/a?ssid=") ) { // /a?ssid=blahhhh&pass=poooo Serial.println("Borrando eeprom"); for (int i = 0; i < 96; ++i) { EEPROM.write(i, 0); } String qsid; qsid = req.substring(8,req.indexOf('&')); Serial.println(qsid); Serial.println(""); String qpass; qpass = req.substring(req.lastIndexOf('=')+1); Serial.println(qpass); Serial.println(""); Serial.println("Escribiendo ssid:"); for (int i = 0; i < qsid.length(); ++i) { EEPROM.write(i, qsid[i]); Serial.print("Escrito: "); Serial.println(qsid[i]); } Serial.println("Escribiendo pass:"); for (int i = 0; i < qpass.length(); ++i) { EEPROM.write(32+i, qpass[i]); Serial.print("Escrito: "); Serial.println(qpass[i]); } EEPROM.commit(); s = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<!DOCTYPE HTML>\r\n<html>ESP8266 "; s += "ACK "; s += req; s += "<p> Guardando en EEPROM se reiniciará para intentar la conexión</html>\r\n\r\n"; delay(100); ESP.restart(); return(1); } else { s = "HTTP/1.1 404 Not Found\r\n\r\n"; Serial.println("Sending 404"); } client.print(s); Serial.println("Respuesta enviada"); return(0); }
La primera vez que se ejecuta no encontrará nada útil en la eeprom y al dar error de conexión se pondrá en modo softAP «WiFi.softAP(ssid);» arrancamos un servidor web básico con dos campos de texto y un botón de confirmación para configurar ssid y contraseña de la red donde queremos conectarnos. Cuando tenemos esta información la guardamos en la eeprom y reiniciamos para empezar de nuevo.
Si todo va bien conectamos a internet a través del wifi configurado «:
WiFi.begin(esid.c_str(), epass.c_str());
Y a continuación creamos un enlace como cliente con el servidor remoto (debemos configurar correctamente el host y el puerto que usemos en lugar de los corchetes):
const char* host = "#####.###"; const int httpPort = ###; client.connect(host, httpPort);
Salimos del setup y continuamos en el ciclo infinito del programa principal loop()
Para la lectura del sensor DHT11, podemos usar la librería de Adafruit : https://github.com/adafruit/DHT-sensor-library pero yo he optado por hacerme mi propia función por los problemas de inestabilidad que me daba la librería. La comunicación es muy sencilla y está bastante bien explicada en el datasheet
void gettemperature() { int j,k0,k1,th,tl,rh,rl,crc,auxcrc; pinMode(0,OUTPUT); digitalWrite(0,LOW); delay(18); // Nivel bajo Serial.println("Inicio trama"); pinMode(0,INPUT); k1=0;while ((digitalRead(0)) && ((k1++)<200)); Serial.println(k1);// Espera 0 k0=0;while ((!digitalRead(0)) && ((k0++)<200)); Serial.println(k0);// Espera 1 k1=0;while ((digitalRead(0)) && ((k1++)<200)); Serial.println(k1);// Espera 0 rh=rl=th=tl=crc=0; for (j=0;j<8;j++) { k0=0;while ((!digitalRead(0)) && ((k0++)<200)); Serial.println(k0);// Espera 1 - Coge el tiempo de trigger (si es < -> 0 si es > es un 1 k1=0;while ((digitalRead(0)) && ((k1++)<200)); Serial.println(k1);// Espera 0 rh<<=1; if (k1>k0) rh|=1; } for (j=0;j<8;j++) { k0=0;while ((!digitalRead(0)) && ((k0++)<200)); Serial.println(k0);// Espera 1 - Coge el tiempo de trigger (si es < -> 0 si es > es un 1 k1=0;while ((digitalRead(0)) && ((k1++)<200)); Serial.println(k1);// Espera 0 rl<<=1; if (k1>k0) rl|=1; } for (j=0;j<8;j++) { k0=0;while ((!digitalRead(0)) && ((k0++)<200)); Serial.println(k0);// Espera 1 - Coge el tiempo de trigger (si es < -> 0 si es > es un 1 k1=0;while ((digitalRead(0)) && ((k1++)<200)); Serial.println(k1);// Espera 0 th<<=1; if (k1>k0) th|=1; } for (j=0;j<8;j++) { k0=0;while ((!digitalRead(0)) && ((k0++)<200)); Serial.println(k0);// Espera 1 - Coge el tiempo de trigger (si es < -> 0 si es > es un 1 k1=0;while ((digitalRead(0)) && ((k1++)<200)); Serial.println(k1);// Espera 0 tl<<=1; if (k1>k0) tl|=1; } for (j=0;j<8;j++) { k0=0;while ((!digitalRead(0)) && ((k0++)<200)); Serial.println(k0);// Espera 1 - Coge el tiempo de trigger (si es < -> 0 si es > es un 1 k1=0;while ((digitalRead(0)) && ((k1++)<200)); Serial.println(k1);// Espera 0 crc<<=1; if (k1>k0) crc|=1; } Serial.println(rh); Serial.println(rl); Serial.println(th); Serial.println(tl); Serial.println(crc); auxcrc=rh+rl+th+tl; auxcrc&=0xff; if (auxcrc==crc) { Serial.println("OK"); temp_c=float(th); humidity=float(rh); } else Serial.println("NO OK"); Serial.println("Fin trama"); }
Y por fin el bucle principal que es muy simple:
void loop() { delay(2000); gettemperature(); delay(0); if (client.connected()) { String aux_cad="#E?"; if (humidity<=100 && humidity>=0 && temp_c>=0 && temp_c<=100) aux_cad=aux_cad+String(humidity)+"&"+String(temp_c); client.print(aux_cad); delay(0); client.setTimeout(1000); String line = client.readStringUntil('\r'); delay(0); if (line.length()>0) { Serial.println(line); if (line.charAt(0)=='0') i=0; else if (line.charAt(0)=='1') i=1; else if ((line.charAt(0)=='9') && (line.length()>3)) { if (line.charAt(3)=='0') i=0; if (line.charAt(3)=='1') i=1; } if (i) {digitalWrite(2,LOW);Serial.println("ON");} else {digitalWrite(2,HIGH);Serial.println("OFF");} client.flush(); } else { Serial.println("No hay respuesta"); } } else { client.stop(); digitalWrite(2,HIGH);Serial.println("OFF"); Serial.println("connection failed"); if (WiFi.status()!=WL_CONNECTED) { ESP.restart(); } delay(0); client.connect(host, httpPort); delay(0); } }
–
Leemos cada 2 segundos el estado del sensor y enviamos por el socket abierto un mensaje de estado con la temperatura y humedad codificadas, la respuesta del servidor nos indica si activar o desactivar el relé.
Si se pierde la conexión intentamos reconectar y sino reiniciamos.
Con el programa terminado sólo falta conectar al ordenador el convertidor usb-serie y del convertidor al esp8266 realizamos las siguientes conexiones
Para evitar problemas de alimentación yo dejo conectados los 3.3V y la masa al convertidor cc/cc de la regleta y uno la mas del usb/serie con la del módulo. El resto de las señales como en el dibujo.
Seleccionamos en Arduino la placa GENERIC ESP8266 Module, el puerto serie del convertidor y el resto por defecto y transferimos el programa.
Si todo va bien al reiniciar el módulo aparece una nueva red wifi «REGLETA_WF» a la que nos conectamos. En la dirección http://192.168.4.1 saldrá una página donde podemos configurar ssid y password de la red wifi que queramos.
Una vez configurado el módulo se reinicia y si ponemos un servidor en la dirección y puerto configurado veremos llegar los mensajes del módulo cada 2 segundos.
En la próxima entrada veremos como programar el servidor en python para disponer de una página web desde cualquier parte del mundo que controle la regleta.
1 ping