En este tutorial, crearemos una interfaz web para controlar 8 relés individualmente. Este tutorial es una continuación del proyecto Piloto 8 relés utilizando un ESP32 y el monitor de serie.
Este tutorial se puede aplicar a cualquier microcontrolador con conexión Wifi o Ethernet. Tendrá que tener cuidado de modificar el esquema de conexión y el código para que se ajuste a su uso.
Hardware
- NodeMCU 32S
- Breadboard
- Jumper cable
- Módulo de 8 relés
- Registro de desplazamiento 74hc595
Principio
En el tutorial anterior, vimos cómo accionar 8 relés de forma independiente utilizando el monitor serie de Arduino IDE. Cuando se dispone de una conexión a Internet, como en el ESP32 NodeMCU, es posible convertir el microcontrolador en un servidor y alojar una página web. Esta página web puede utilizarse como interfaz gráfica de usuario para impulsar su proyecto. Crearemos una página web, accesible a través de la red local, con botones para activar los relés.
Código
Tomaremos el código del tutorial anterior y lo combinaremos con el código para crear una Interfaz Web para el ESP32 NodeMCU. En la página web, crearemos una tabla que contenga 16 botones correspondientes a las acciones «Activar» y «Reiniciar» de los 8 relés. Para visualizar algunas acciones, añadimos un inserto para mostrar los mensajes del microcontrolador.
void webpage(WiFiClient client) { //Send webpage to browser
client.println("<!DOCTYPE HTML>");
client.println("<html>");
client.println("<head>");
client.println("<title> AranaCorp </title>");
client.println("<meta name='apple-mobile-web-app-capable' content='yes' />");
client.println("<meta name='apple-mobile-web-app-status-bar-style' content='black-translucent' />");
client.println("<meta charset='UTF-8'>");
client.println("");
client.println("");
client.println("");
client.println("</head>");
client.println("<body> ");
client.println("<div id='page'>");
client.println("<div id='content'>");
client.println("<hr/><hr>");
client.println("<h1><center> AranaCorp - Relay Controller Web Interface </center></h1>");
client.println("<hr/><hr>");
client.println("<br><br><div id='m' class='box'><center><table>");
client.println("<tr><td>Console </td><td><input id='senM1' class='sensor' value='" + String(console) + "' readonly></input></td></tr> </table>");
client.println(" <table><tr><td>Relay 0</td>");
client.println(" <td><a href='/light0on' class='button activate'> Activate </a>");
client.println(" <a href='/light0off' class='button reset'> </a></td><td>Relay 1</td>");
client.println(" <td><a href='/light1on' class='button activate'> Activate </a>");
client.println(" <a href='/light1off' class='button reset'> Reset </a></td><td>Relay 2</td>");
client.println(" <td><a href='/light2on' class='button activate'> Activate </a>");
client.println(" <a href='/light2off' class='button reset'> Reset </a></td><td>Relay 3</td>");
client.println(" <td><a href='/light3on' class='button activate'> Activate </a>");
client.println(" <a href='/light3off' class='button reset'> Reset </a></td></tr><tr><td>Relay 4</td>");
client.println(" <td><a href='/light4on' class='button activate'> Activate </a>");
client.println(" <a href='/light4off' class='button reset'> Reset </a></td><td>Relay 5</td>");
client.println(" <td><a href='/light5on' class='button activate'> Activate </a>");
client.println(" <a href='/light5off' class='button reset'> Reset </a></td><td>Relay 6</td>");
client.println(" <td><a href='/light6on' class='button activate'> Activate </a>");
client.println(" <a href='/light6off' class='button reset'> Reset </a></td><td>Relay 7</td>");
client.println(" <td><a href='/light7on' class='button activate'> Activate </a>");
client.println(" <a href='/light7off' class='button reset'> Reset </a></td></tr>");
client.println("</table></center></div>");
client.println("</div><footer><div><p style='text-align:left;float:left;padding:0px;margin:0px;'>© Copyright 2020 AranaCorp. All rights reserved</p><p style='float:right;padding:0px;margin:0px;'><a style='color:#3aaa35;' href='https://www.aranacorp.com/fr/evidence'>www.aranacorp.com</a><p></div></footer></div></body></html>");
delay(1);
client.stop();
}
Una vez que la página web es funcional, todo lo que tenemos que hacer es manejar las peticiones del navegador en la función loop().
void loop() {
WiFiClient client = server.available();
if (client) {
if (client.connected()) {
String request = "";
if (client.available()) {
request = client.readStringUntil('\r');
if (request != "") {
client.print( header );
handleRequest(request);
webpage(client);//Return webpage
}
}
}
}
}
void handleRequest(String request) { /* function handleRequest */
////Handle web client request
String pwmCmd;
//Digital Ouputs
for (int i = 0; i < 16; i++) {
if (request.indexOf("/light" + String(i) + "on") > 0) {
Serial.print("Relay n° "); Serial.print(i); +Serial.println(" ON ");
setRegisterPin(i, 0);
writeRegisters();
console = String("Relay n°" + String(i) + " ON");
}
if (request.indexOf("/light" + String(i) + "off") > 0) {
Serial.print("Relay n° "); Serial.print(i); +Serial.println(" OFF ");
setRegisterPin(i, 1);
writeRegisters();
console = String("Relay n°" + String(i) + " OFF");
}
}
}
A continuación encontrará el código completo que integra la gestión de la página web así como el registro de turnos visto en los artículos anteriores. No olvides actualizar las variables ssid y password con los valores de tu red.
//Libraries
#include <WiFi.h>//https://www.arduino.cc/en/Reference/WiFi
//Constants
#define number_of_74hc595s 2
#define numOfRegisterPins number_of_74hc595s * 8
#define SER_Pin 25
#define RCLK_Pin 33
#define SRCLK_Pin 32
//Variables
boolean registers[numOfRegisterPins];
char* ssid = "*****";
char* password = "*****";
String nom = "ESP32";
//Objects
WiFiServer server(80);
WiFiClient client;
String header = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n";
String console = "System initialized";
void setup() {
//Init Serial USB
Serial.begin(115200);
Serial.println(F("Initialize System"));
//Init ESP32Wifi
Serial.print("Connecting to "); Serial.println(ssid);
WiFi.begin(ssid, password);
// Connect to Wifi network.
while (WiFi.status() != WL_CONNECTED)
{
delay(500); Serial.print(F("."));
}
server.begin();
Serial.println();
Serial.println(F("ESP32Wifi initialized"));
Serial.print(F("IP Address: "));
Serial.println(WiFi.localIP());
//Init register
pinMode(SER_Pin, OUTPUT);
pinMode(RCLK_Pin, OUTPUT);
pinMode(SRCLK_Pin, OUTPUT);
clearRegisters();
writeRegisters();
delay(500);
}
void loop() {
WiFiClient client = server.available();
if (client) {
if (client.connected()) {
String request = "";
if (client.available()) {
request = client.readStringUntil('\r');
if (request != "") {
client.print( header );
handleRequest(request);
webpage(client);//Return webpage
}
}
}
}
}
void handleRequest(String request) { /* function handleRequest */
////Handle web client request
String pwmCmd;
//Digital Ouputs
for (int i = 0; i < 16; i++) {
if (request.indexOf("/light" + String(i) + "on") > 0) {
Serial.print("Relay n° "); Serial.print(i); +Serial.println(" ON ");
setRegisterPin(i, 0);
writeRegisters();
console = String("Relay n°" + String(i) + " ON");
}
if (request.indexOf("/light" + String(i) + "off") > 0) {
Serial.print("Relay n° "); Serial.print(i); +Serial.println(" OFF ");
setRegisterPin(i, 1);
writeRegisters();
console = String("Relay n°" + String(i) + " OFF");
}
}
}
void webpage(WiFiClient client) { //Send webpage to browser
/* client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println("");*/
client.println("<!DOCTYPE HTML>");
client.println("<html>");
client.println("<head>");
client.println("<title> AranaCorp </title>");
client.println("<meta name='apple-mobile-web-app-capable' content='yes' />");
client.println("<meta name='apple-mobile-web-app-status-bar-style' content='black-translucent' />");
client.println("<meta charset='UTF-8'>");
//client.println("<meta http-equiv='refresh' content='1'>");
client.println("");
client.println("<style>");
client.println("");
client.println(" body {");
client.println(" font-size:100%;");
client.println(" color:white;");
client.println(" background-color: #111111;");
client.println(" margin:0px;");
client.println(" } ");
client.println(" ");
client.println(" #page {");
client.println("width:100%;");
client.println(" position: relative;");
client.println(" min-height: 99vh;");
client.println("}");
client.println("");
client.println("#content {");
client.println(" padding-bottom: 2.5rem; /* Footer height */");
client.println("}");
client.println("");
client.println("footer {");
client.println(" background-color:black;");
client.println(" position: absolute;");
client.println(" bottom: 0;");
client.println(" width: 100%;");
client.println(" height: 2.5rem; /* Footer height */");
client.println("}");
client.println(" ");
client.println(" h1 {color: #3AAA35;}");
client.println(" p { text-align:center; }");
client.println("");
client.println(" table {");
client.println(" width=80%;");
client.println("margin: 40px 40px 40px 40px;");
client.println(" }");
client.println(" tr{");
client.println("border: 1px solid black;");
client.println("padding: 20px 20px 20px 20px;");
client.println(" }");
client.println(" td{");
client.println("padding: 10px 10px 10px 10px;");
client.println(" }");
client.println(" .wrapper {");
client.println("display: grid;");
client.println("/*grid-template-columns: 1fr 1fr 1fr;*/");
client.println("");
client.println("margin: 60px 20px 60px 20px;");
client.println("}");
client.println(".box {");
client.println(" border-radius: 10px;");
client.println(" border: 2px solid white;");
client.println(" font-size: 150%;");
client.println(" /*height: 120px;*/");
client.println(" margin: 5px 5px 5px 5px;");
client.println("}");
client.println("");
client.println(".button {");
client.println(" background-color: #111111; /* Green */");
client.println(" border: none;");
client.println(" color: white;");
client.println(" padding: 16px 32px;");
client.println(" text-align: center;");
client.println(" text-decoration: none;");
client.println(" display: inline-block;");
client.println(" font-size: 16px;");
client.println(" margin: 4px 2px;");
client.println(" transition-duration: 0.4s;");
client.println(" cursor: pointer;");
client.println(" border-radius: 12px;");
client.println("}");
client.println("");
client.println(".activate {");
client.println(" color: white; ");
client.println(" border: 2px solid #3AAA35;");
client.println("}");
client.println("");
client.println(".activate:hover {");
client.println(" background-color: #3AAA35;");
client.println(" color: white;");
client.println("}");
client.println("");
client.println(".reset { ");
client.println(" color: white; ");
client.println(" border: 2px solid #f44336;");
client.println("}");
client.println("");
client.println(".reset:hover {");
client.println(" background-color: #f44336;");
client.println(" color: white;");
client.println("}");
client.println("");
client.println(" .sensor {");
client.println(" background-color: #ffffff;");
client.println(" border: none;");
client.println(" color: black;");
client.println(" padding: 16px 32px;");
client.println(" text-align: center;");
client.println(" text-decoration: none;");
client.println(" display: inline-block;");
client.println(" font-size: 16px;");
client.println(" margin: 4px 2px;");
client.println(" transition-duration: 0.4s;");
client.println(" cursor: pointer;");
client.println(" border-radius: 2px;");
client.println("}");
client.println("");
client.println(".status {");
client.println(" color: white; ");
client.println(" border: 5px solid #3AAA35;");
client.println(" border-radius: 12px;");
client.println("}");
client.println("");
client.println("");
client.println("@media (min-width: 1050px){ /*screen and*/");
client.println(" .wrapper {");
client.println("grid-template-columns: repeat(2, 1fr);");
client.println(" }");
client.println(" ");
client.println("@media (min-width: 1500px){");
client.println(" .wrapper {");
client.println("grid-template-columns: repeat(3, 1fr);");
client.println(" }");
client.println("}");
client.println("");
client.println(" </style>");
client.println("");
client.println("");
client.println("</head>");
client.println("<body> ");
client.println("<div id='page'>");
client.println("<div id='content'>");
client.println("<hr/><hr>");
client.println("<h1><center> AranaCorp - Relay Controller Web Interface </center></h1>");
client.println("<hr/><hr>");
client.println("<br><br><div id='m' class='box'><center><table>");
client.println("<tr><td>Console </td><td><input id='senM1' class='sensor' value='" + String(console) + "' readonly></input></td></tr> </table>");
client.println(" <table><tr><td>Relay 0</td>");
client.println(" <td><a href='/light0on' class='button activate'> Activate </a>");
client.println(" <a href='/light0off' class='button reset'> Reset </a></td><td>Relay 1</td>");
client.println(" <td><a href='/light1on' class='button activate'> Activate </a>");
client.println(" <a href='/light1off' class='button reset'> Reset </a></td><td>Relay 2</td>");
client.println(" <td><a href='/light2on' class='button activate'> Activate </a>");
client.println(" <a href='/light2off' class='button reset'> Reset </a></td><td>Relay 3</td>");
client.println(" <td><a href='/light3on' class='button activate'> Activate </a>");
client.println(" <a href='/light3off' class='button reset'> Reset </a></td></tr><tr><td>Relay 4</td>");
client.println(" <td><a href='/light4on' class='button activate'> Activate </a>");
client.println(" <a href='/light4off' class='button reset'> Reset </a></td><td>Relay 5</td>");
client.println(" <td><a href='/light5on' class='button activate'> Activate </a>");
client.println(" <a href='/light5off' class='button reset'> Reset </a></td><td>Relay 6</td>");
client.println(" <td><a href='/light6on' class='button activate'> Activate </a>");
client.println(" <a href='/light6off' class='button reset'> Reset </a></td><td>Relay 7</td>");
client.println(" <td><a href='/light7on' class='button activate'> Activate </a>");
client.println(" <a href='/light7off' class='button reset'> Reset </a></td></tr>");
client.println("</table></center></div>");
client.println("</div><footer><div><p style='text-align:left;float:left;padding:0px;margin:0px;'>© Copyright 2020 AranaCorp. All rights reserved</p><p style='float:right;padding:0px;margin:0px;'><a style='color:#3aaa35;' href='https://www.aranacorp.com/fr/evidence'>www.aranacorp.com</a><p></div></footer></div></body></html>");
delay(1);
client.stop();
}
//SR
void clearRegisters() { /* function clearRegisters */
//// Clear registers variables
for (int i = numOfRegisterPins - 1; i >= 0; i--) {
registers[i] = HIGH;
}
}
void writeRegisters() { /* function writeRegisters */
//// Write register after being set
digitalWrite(RCLK_Pin, LOW);
for (int i = numOfRegisterPins - 1; i >= 0; i--) {
digitalWrite(SRCLK_Pin, LOW);
digitalWrite(SER_Pin, registers[i]);
digitalWrite(SRCLK_Pin, HIGH);
}
digitalWrite(RCLK_Pin, HIGH);
}
void setRegisterPin(int index, int value) { /* function setRegisterPin */
////Set register variable to HIGH or LOW
registers[index] = value;
}
void printRegisters() { /* function clearRegisters */
//// Clear registers variables
for (int i = 0; i < numOfRegisterPins; i++) {
Serial.print(registers[i]); Serial.print(F(" ,"));
}
Serial.println();
}
Resultado
Una vez cargado el código en el microcontrolador, introduzca la dirección IP que aparece en el monitor serie (aquí 192.168.1.64) en su navegador web. Debería aparecer la siguiente página.

A continuación, puede cerrar los relés con los botones «Activar» y abrirlos con los correspondientes botones «Reiniciar».

No dudes en dejarnos un comentario para darnos tu opinión, comentarios e ideas para mejorar este tutorial.