Este módulo ESP8266 compatible con Arduino realiza una lectura de los amperes con el consumo de corriente de mi casa, cada un segundo.
En el artículo anterior hay una explicación del hardware, así como enlaces a mi tienda online favorita, Amazon.com, para que vean sus precios y compra.
El ESP8266 almacena el dato obtenido segundo a segundo en un array de 180 valores que va rotando para tener siempre los últimos 3 minutos de consumo y así poder graficarlos.
A su vez, almacena en otro array el promedio de consumo cada 3 minutos, para tener así una segunda gráfica con el consumo de las últimas 9 horas.
El ESP8266 muestra en la pantalla LCD los amperes y watts que va registrando, así como una barra de «consumo de pontencia» que va de 0 a 30 amperes (el máximo que puede leer la pinza amperimétrica que utilizamos como sensor de consumo).
Nuestro ESP8266 tiene un servidor web que nos muestra las gráficas que comentamos al principio. Para ello necesita conectarse a nuestra red WIFI.
Este ESP8266 incluía originalmente una función extra, que se dedicaba a buscar a un segundo ESP8266 que pudiera estar conectado en la red WIFI. Este segundo módulo oficia de concentrador de datos y servidor web, presentando un panorama visual completo de todos los módulos ESP8266 que se encuentran repartidos en mi casa, recopilando distinta información.
Esa parte más compleja del código ha sido anulada ya que formará parte de otro tutorial de Arduino, más adelante.
Detalles de funcionamiento de este proyecto Arduino
Al inicio, el ESP8266 intentará conectarse a nuestra red WIFI.
Si es la primera vez que se enciende, o no la encuentra, cambiará su modo a ACCESS POINT, generando su propio punto de acceso wifi de nombre ELECTROMAIN.
Una vez nos conectemos al ssid ELECTROMAIN, podremos navegar a una página web de configuración, tremendamente sencilla, que se encuentra en http://192.168.4.1
Allí veremos las redes WIFI que el ESP8266 encuentra en su vecindad, para poder verificar que estamos dentro del alcance de nuestro propio router WIFI, y tendremos dos casilleros para ingresar el SSID y la CLAVE para conectarnos.
Una vez ingresados ambos datos, el ESP8266 intentará conectarse a nuestra red WIFI.
Si lo logra, desconectará el ACCESS POINT, y en la pantalla LCD indicará la IP asignada, para que podamos navegar hasta él normalmente.
Si nuestro sistema operativo soporta el protocolo mDNS, también podremos navegar hasta este módulo ESP8266 utilizando la url http://electro.local
Pinza amperimétrica y Arduino
La pinza amperimétrica entregará un resultado de -0.5 a 0.5 volts (1v de amplitud) en relación directa a la corriente que pasa por el cable que está midiendo.
Por ese motivo, utilizaremos un circuito muy sencillo con dos resistencias y un capacitor de 10uf para mover medio volt hacia arriba el retorno de la pinza amperimétrica, y de paso, filtrar un poco su señal (que incluye picos espúreos, originalmente).
La lógica de este pequeñito circuito se explica en la página del proyecto open energy monitor.
Considerar que en el esquema allí presentado, los voltajes de alimentación de nuestro ESP8266 en nuestro caso son 3.3v en vez de 5v. Además la pinza amperimétrica que utilizan como ejemplo entrega otros rangos de voltaje.
Lo importante es que para nuestro caso, el C1 es de 10uf, R1 y R2 son de 10k y 56k respectivamente (cuidado, en el dibujo original se utilizaron valores erróneos).
El conector tipo «spica» de la pinza amperimétrica tiene dos contactos, la punta y la tierra. La punta va conectada al pin del conversor analógico del ESP8266.
La tierra va al pequeño circuito que entrega ese offset de medio volt. La imagen del costado izquierdo fue recortada del diseño de TINKERMAN, para su MOTEINO EMON SHIELD, quien dicho sea de paso me orientó en esta parte de mi proyecto, con esta solución.
Código Fuente de este proyecto Arduino
/* TAREAS PARA ESTE MÓDULO: 1) MEDIR WATTS POR PINZA AMPERIMETRICA 2) ACTUALIZAR EN TIEMPO REAL EN LCD 3) CONTROLAR WIFI 4) (no se hace en este ejemplo) MANDAR PAQUETES CON CONSUMO CADA 10 SEGUNDOS APROX AL ESP8266 "MAESTRO" LCD: 20x4 con serial module lcd.Initialize(0x27, 20, 4) 'el lcd 20x4 es de 5v y usa el 0x27 o 0x3f para conectarse USO DE LA PINZA AMPERIMETRICA: https://bitbucket.org/xoseperez/emonliteesp */ #include <Wire.h> // Asi podemos definir los pines del i2c para el ESP8266 #include <LiquidCrystal_I2C.h> #include "EmonLiteESP.h" #include <ESP8266WiFi.h> // Librería con las funciones básicas para el wifi #include <ESP8266WebServer.h> #include <ESP8266mDNS.h> #define ejeygraf 150 //Altura de las barras en las gráficas via web #define ejexgraf 180 //Ancho de las barras en las gráficas via web const unsigned long millisxhora = 1000 * 60 * 60; const unsigned long millisx25seg = 1000 * 25; const unsigned long millisxseg = 1000; const unsigned int sensorute = A0; // el sensor de corriente va al A0 (punta) y GND (malla) const unsigned int voltaje = 230; //voltaje promedio de la red eléctrica en mi país. LiquidCrystal_I2C lcd(0x27, 20, 4); EmonLiteESP power; MDNSResponder mdns; ESP8266WebServer server(80); WiFiClient client; unsigned long mspasados25seg = 0; //Vamos a llevar cuenta de los milisegundos que van transcurriendo para interrumpir el loop cada 25 segundos unsigned long mspasadosseg = 0; //Vamos a llevar cuenta de los milisegundos que van transcurriendo para interrumpir el loop cada segundo unsigned long mspasadoshora = 0; // Vamos a llevar la cuenta de milisegundos que pasan para contar la hora uint horas = 0; //contador de horas. Al ser INT, 65535 horas sería el máximo ... más de 7 años sin apagarse debería ser suficiente. int watts = 0; //Aqui guarda los watts double amps = 0; //Y guarda los amps int graf[2][ejexgraf]; //Guardo los ultimos 53 minutos de información en watts y las últimas 9 horas byte pos[2]; //Se guarda la posición para recibir el nuevo dato en los arrays de arriba long acumulador = 0; //Voy sumando los valores de los 5 minutos de la gráfica instantánea, asi cuando se llena, saco promedio para guardar un dato mas en la histórica long acumuladorweb = 0; //Acumulador para sumar watts y promediar, enviando valor a servidor uint segundos = 0; //Contador de segundos pasados bool configurandoAP = false; unsigned int currentCallback() // Un callback que le da a la libreria EmonLiteESP de corriente el valor del sensor de corriente { return analogRead(sensorute); } void setup() { Wire.begin(2, 14); //GPIO2 es D4 y GPIO14 es D5 en el ESP8266 lcd.begin(20, 4); lcd.init(); lcd.backlight(); lcd.setCursor(0, 0); lcd.print("SENSOR CORRIENTE 1.0"); mspasadosseg = millis(); //arranco contadores de milisegundos para actuar cada segundo y hora mspasadoshora = millis(); mspasados25seg = millis(); power.initCurrent(currentCallback, 10, 1.0, 30); //ADC 10 bits, escala de voltaje 1V=30 amperes power.setPrecision(2); //dos lugares decimales de precisión //limpio los arrays de los watts y punteros: for (byte i = 0; i < ejexgraf; i++) { graf[0][i] = 0; graf[1][i] = 0; } pos[0] = pos[1] = 0; power.warmup(); // Que la rutina de EmonLite se balancee para arrancar con un valor real power.warmup(); // Lo hago dos veces, porque con una no alcanza. conectarwifi(true); //Activamos wifi (si no detecta red, activa el Access Point de configuración) server.on("/", root); //Configuramos el servidor web para que la página root vaya a la función root server.on("/setap", webconfiguracionguardar); //Y para guardar parametros de wifi en caso de estar configurando server.begin(); } /* ==== Loop ==== */ void loop() { server.handleClient(); //manejo el servidor web mdns.update(); //respondo eventuales peticiones mdns yield(); if ((millis() - mspasadosseg) > millisxseg) //Si ya pasó un segundo ... { mspasadosseg = millis(); actualizodatos(); //actualizo datos del sensor y la pantalla LCD cada segundo //Veo si pasaron 25 segundos, para -si no hay conectividad- reintentar conectar if ((millis() - mspasados25seg) > millisx25seg) { //cada 25 segundos me fijo si la conectividad sigue en pie y además veo si pasó una hora mspasados25seg = millis(); if ((configurandoAP == false) && (WiFi.status() != WL_CONNECTED)) conectarwifi(false); //reintento conexión a la red WIFI salvo que esté en modo AP if ((millis() - mspasadoshora) > millisxhora) { //vemos si pasó una hora mspasadoshora = millis(); horas++; //incremento contador de horas que lleva el ESP8266 encendido } } } } void actualizodatos() { //LEVANTO DATO CRUDO, CORRIJO Y CALCULO WATTS Y AMPS: amps = power.getCurrent(1000); //corrijo por falta de precisión de pinza amperimétrica if (amps < 0.04) { amps = 0; } else if (amps > 30) { amps = 30; } watts = amps * voltaje; //Le muestro watts y amps lcd.clear(); lcd.print(" " + (String)watts + " W / " + (String)amps + " A"); for (int i = 0; i < (amps / 1.55); i++) { lcd.setCursor(i, 1); lcd.write((uint8_t)255); } yield(); lcd.setCursor(0, 2); lcd.print("min<------------>max"); graf[0][pos[0]] = watts; //Guardo los watts acumulador += watts; //guardo en el acumulador para promediar como histórico acumuladorweb += watts; // y guardo en el acumulador para informar al server segundos++; //Incremento contador de valores en acumuladores pos[0]++; //Ya llené el array entero (300 segundos, vuelvo al segundo 0, y guardo el promedio de estos 5 minutos en el histórico) if (pos[0] == ejexgraf) { pos[0] = 0; acumulador = acumulador / ejexgraf; //Hago el promedio graf[1][pos[1]] = acumulador; //guardo un dato más en la graf histórica acumulador = 0; //reseteo acumulador para promedio de nuevos 5 minutos pos[1]++; if (pos[1] == ejexgraf) pos[1] = 0; } } void conectarwifi(bool configurar) { lcd.clear(); lcd.print("Buscando red WIFI"); if (testearwifi()) { activarserver(); } else if (configurar) //Si no pudo conectarse al WIFI, y viene de ser recién encendido o reseteado, lo dejamos configurar un wifi: { activarserverconfig(); } } //Testeo si hay conexión WIFI al SSID / PASSWORD que el ESP8266 tiene guardado bool testearwifi() { for (byte i = 0; i < 20; i++) { if (WiFi.status() == WL_CONNECTED) return true; delay(500); lcd.setCursor(i, 1); lcd.print("."); } return false; } void activarserver() { //EL ESP8266 se conectó al SSID que venia en su memoria, asi que le aviso y arranco el servidor web con nuestra página WiFi.mode(WIFI_STA); //Colocamos al ESP8266 en modo WIFI STATION, asumiendo que ya tiene configurada la SSID y la clave. delay(100); yield(); configurandoAP = false; lcd.clear(); lcd.setCursor(0, 0); lcd.print("Conectado a:"); lcd.setCursor(0, 1); lcd.print((String)WiFi.SSID()); delay(100); lcd.setCursor(0, 2); lcd.print("IP: " + WiFi.localIP().toString()); WiFi.hostname("electro"); if (mdns.begin("electro", WiFi.localIP())) { //Pude activar el mDNS para que -en algunos OS- pueda encontrar el sensor en http://electro.local lcd.setCursor(0, 3); lcd.print("http://electro.local"); } delay(3000); } void activarserverconfig() { configurandoAP = true; WiFi.mode(WIFI_STA); WiFi.disconnect(); delay(100); WiFi.mode(WIFI_AP_STA); //Activamos el modo access point del ESP8266 WiFi.softAP("ELECTROMAIN"); lcd.clear(); lcd.setCursor(0, 0); lcd.print("Configurar WIFI"); lcd.setCursor(0, 1); lcd.print("ssid ELECTROMAIN"); lcd.setCursor(0, 2); lcd.print("IP " + WiFi.softAPIP().toString()); //Le muestro como puede conectarse al AP delay(5000); } //La página web raíz dependerá de si estamos configurando con AP mode el wifi, o mostrando los datos normalmente void root() { if (configurandoAP) { webconfiguracion(); } else { webdatos(); } } //Genera gráfica para la web con divs de diversa altura. void graficoweb(byte grafica) { String retorno = ""; int puntero = 0; //en la grafica web, se usa este puntero para dibujar hacia atrás cada barrita. uint valor,maximo = 0; uint minimo = 8000; //Primero hallo maximo y mínimo en este array delta de 41 valores for (byte i = 0; i<ejexgraf; i++) { if (maximo < graf[grafica][i]) { maximo = graf[grafica][i]; } else if (minimo > graf[grafica][i]) { minimo = graf[grafica][i]; } } //tengo el máximo y el mínimo: Armo max - min en costado izquierdo onda ejey server.sendContent("<div id=e>" + (String)maximo + "<div style=\"height:130px;\"></div>" + (String)minimo + "</div>"); //mando al cliente x web la escala max min vertical maximo = maximo - minimo; // ahora minimo representa CERO y máximo está ajustado a esta nueva escala if (maximo == 0) maximo=1; puntero = pos[grafica]; for (byte i = ejexgraf; i > 0; i--) { //incluso en el primer caso, puntero tiene que ser puntero-1 ya que puntero siempre apunta a proxima posicion "en limpio" a almacenar en los arrays if (puntero > 0) { puntero--; } else { puntero = ejexgraf - 1; } //El dato mas reciente viene en la var puntero: Grafico ese punto y de ahi para atrás en el array. Es un sinfin: Si ya leí valor cero,continúo por el tope valor = graf[grafica][puntero]; valor = valor - minimo; //shifteo el valor al nuevo CERO //Regla de tres contra la altura máxima del Eje y el valor máximo que se va a necesitar graficar valor = (valor * ejeygraf) / (maximo); yield(); retorno = "<div id=b title=" + (String)graf[grafica][puntero] + "w style=\"height:" + (String)valor + ";\"></div>" + retorno; } server.sendContent(retorno); //mando al cliente x web la gráfica } //Envía header y estilos al usuario via web ATENCIÓN, no cierra el style ni el header, eso queda para hacer en cada página, por si hay que agregar mas styles y headers. void arranqueweb() { server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); server.sendHeader("Pragma", "no-cache"); server.sendHeader("Expires", "-1"); server.setContentLength(CONTENT_LENGTH_UNKNOWN); server.sendContent("<html><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /><meta name=\"apple-mobile-web-app-capable\" content=\"yes\" /><meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\" /><style>body {font-family:arial;} h1 {font-size:20px;} h3, hr{clear:both;font-size:16px;} h6 {clear:both;border-top:1px solid #000;font-size:9px;width:100%;text-align:center;}"); //inicializo cabezera página contemplando navegación móvil y aplico font ARIAL y otros detalles } //Envía cierre de página al usuario via web incluyendo pié de página void cierreweb() { server.sendContent("<h6>(C)Enrique Avalle 12/2016 v1</h6></body></html>"); server.client().stop(); } void webconfiguracion() { arranqueweb(); server.sendContent("</style></head><body><h1>Nodo Micro Eléctrico</h1><h2>Configuración WiFi</h2>"); String listassids = ""; int n = WiFi.scanNetworks(); if (n > 0) { //Hay SSDID en la vuelta, genero lista server.sendContent("<h3>Lista de redes WIFI accesibles:</h3>"); server.sendContent("<ol>"); for (int i = 0; i < n; ++i) { server.sendContent("<li>"); server.sendContent(WiFi.SSID(i)); server.sendContent(" ("); server.sendContent((String)WiFi.RSSI(i)); server.sendContent(")"); server.sendContent((WiFi.encryptionType(i) == ENC_TYPE_NONE) ? " " : "*"); server.sendContent("</li>"); } server.sendContent("</ol>"); } server.sendContent("<form method='get' action='setap'><label>SSID: </label><input name='ssid' length=32><input name='pass' length=64><input type='submit'></form>"); cierreweb(); } //Pagina con la información de consumo eléctrico void webdatos() { arranqueweb(); server.sendContent(" .d{float:left;text-align:center;height:80px;width:92;margin:2px 2px 2px 2px;border:1px solid #777;}"); //Rectángulos para datos numericos temp hum y pres server.sendContent(" #v{background-color:#bfb;} #a{background-color:#ffb;} #r{background-color:#fbb;}"); //Fondo verde, amarillo y rojo para los recuadros de temperatura server.sendContent("#x {font-size:12px;margin:9px auto 9px auto;} "); //textito para las etiquetas y unidades de los rectángulos temp, hum y pres server.sendContent("#y {font-size:18px;margin:2px auto 2px auto;font-weight:bold;} "); // textito para los valores de los rectángulos temp, hum y pres server.sendContent(" #g {float:left;margin:10px;width:300px;height:180px;background-color:#eee;}"); // recuadro para cada gráfica y titulo en general server.sendContent(" #c{height:100px;width:275px;margin:0 auto 0 auto;}"); //contenedor para las barras de la gráfica server.sendContent(" #b {width:3px;border-bottom:1px solid blue;display:inline-block;position:relative;background-color:blue;vertical-align:baseline;}"); //barras de cada gráfica server.sendContent(" #e {width:20px;height:150px;text-align:right;padding-right:2px;border-right:1px solid black;font-size:10px;float:left;}"); //costado izq con max y min para cada gráfica IPAddress ip = WiFi.localIP(); server.sendContent("</style></head><body><h1>Nodo Micro Eléctrico</h1><h3>IP " + String(ip[0]) + '.' + String(ip[1]) + '.' + String(ip[2]) + '.' + String(ip[3]) + "</h3>"); //Presento cuadradito de horas de uso: server.sendContent("<div id=v class=d><div id=x>Activo</div><div id=y>" + String(horas / 24) + "</div><div id=x>Días</div></div>"); String color = "v"; if (watts > 3999) { color = "r"; } else if (watts > 1999) { color = "a"; } //Presento cuadradito de Watts: server.sendContent("<div id=" + color + " class=d><div id=x>Consumo</div><div id=y>" + String(watts) + "</div><div id=x>Watts</div></div>"); server.sendContent("<hr>"); //Presento grafica de los últimos CINCO minutos (300 valores): server.sendContent("<h3>Watts reales p/seg últimos 3 minutos:</h3>"); graficoweb(0); server.sendContent("<hr>"); server.sendContent("<h3>Watts promedio c/3 mins. en últimas 9 horas:</h3>"); //Presento gráficas históricas graficoweb(1); cierreweb(); } void webconfiguracionguardar() { lcd.clear(); lcd.setCursor(0, 0); lcd.print("Actualizando WIFI"); String qsid = server.arg("ssid"); String qpass = server.arg("pass"); if (qsid.length() > 0 && qpass.length() > 0) { for (int i = 0; i < qsid.length(); i++) { // Deal with (potentially) plus-encoded ssid qsid[i] = (qsid[i] == '+' ? ' ' : qsid[i]); } for (int i = 0; i < qpass.length(); i++) { // Deal with (potentially) plus-encoded password qpass[i] = (qpass[i] == '+' ? ' ' : qpass[i]); } //WiFi.mode(WIFI_AP_STA); //de nuevo, lo ponemos en modo AP + STATION (esta linea es innecesaria, ya está en modo AP_STA) WiFi.begin(qsid.c_str(), qpass.c_str()); if (testearwifi()) { IPAddress ip = WiFi.localIP(); arranqueweb(); server.sendContent("</style></head><body><h1>Nodo Micro Eléctrico</h1><h3>Conexión a red " + qsid + " exitosa</h3><p>Por favor, espere unos segundos mientras se resetea este nodo y reconéctese a esa red para poder visualizar la información del sistema.<br><br>Podrá hacerlo desde http://electro.local ó si esto no funciona, utilizando la IP asignada: http://" + String(ip[0]) + '.' + String(ip[1]) + '.' + String(ip[2]) + '.' + String(ip[3]) + "</p>"); cierreweb(); lcd.clear(); lcd.setCursor(0, 0); lcd.print("WIFI Actualizado"); delay(900); activarserver(); } else { lcd.clear(); lcd.setCursor(0, 0); lcd.print("ERROR SSID clave"); delay(1000); arranqueweb(); server.sendContent("</style></head><body><h1>Nodo Micro Eléctrico</h1><h3>Falló conexión a red " + qsid + "</h3><p>Por favor intente nuevamente, revisando y escribiendo correctamente el SSID y la clave wifi</p>"); cierreweb(); delay(900); } } else { arranqueweb(); server.sendContent("</style></head><body><h1>Nodo Micro Eléctrico</h1><h3>Error en SSID y clave</h3><p>Por favor intente nuevamente, revisando y escribiendo correctamente el SSID y la clave wifi</p>"); cierreweb(); delay(900); } }
Espero como siempre que este código sirva de ejemplo y ayuda a alguno de ustedes. Me encantará escuchar sus comentarios, y por supuesto, si encuentran una mejor forma de resolver algún aspecto del código, por favor, comenten también así todos podemos aprender!