Neste tutorial, vamos criar uma interface web para controlar 8 relés individualmente. Este tutorial pode ser aplicado a qualquer microcontrolador com uma ligação WiFi ou Ethernet. Terá de ter o cuidado de modificar o esquema de ligação e o código de acordo com a sua utilização.
Este tutorial dá continuidade ao projeto Controlar 8 relés utilizando um ESP32 e o monitor série.
Hardware
- NodeMCU 32S
- Prancheta
- Cabo de ligação
- Módulo de 8 relés
- 74hc595 registo de deslocação
Princípio
No tutorial anterior, vimos como controlar 8 relés de forma independente utilizando o monitor série do IDE Arduino. Quando está disponível uma ligação à Internet, como no NodeMCU ESP32, pode transformar o seu microcontrolador num servidor e alojar uma página Web. Esta página web pode ser utilizada como uma interface gráfica para controlar o seu projeto. Vamos criar uma página Web, acessível através da rede local, com botões para ativar os relés.
Código
Vamos pegar no código do tutorial anterior e combiná-lo com o código para criar uma interface Web para o ESP32 NodeMCU. Na página Web, vamos criar uma tabela com 16 botões que correspondem às acções “Ativar” e “Reiniciar” dos 8 relés. Para visualizar determinadas acções, vamos acrescentar um insert para visualizar as mensagens do 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(); }
Quando a página Web estiver a funcionar, tudo o que precisamos de fazer é gerir os pedidos do browser na função 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"); } } }
Abaixo encontra-se o código completo que integra a gestão da página web e o registo de turnos visto nos artigos anteriores. Não se esqueça de atualizar as variáveis ssid e password com os valores da sua rede.
//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(); }
Resultados
Quando o código tiver sido carregado no microcontrolador, introduza o endereço IP apresentado no monitor de série (neste caso, 192.168.1.64) no seu navegador Web. Deve ser apresentada a seguinte página.
Pode então fechar os relés com os botões “Ativar” e abri-los com os botões “Repor” correspondentes.
Não hesite em deixar-nos um comentário para nos dar a sua opinião, feedback e sugestões de melhoria para este tutorial.