Dans ce tutoriel, nous allons créer une interface web permettant de piloter 8 relais individuellement. Ce tutoriel peut être appliqué à n’importe quel microcontrôleur avec une connexion Wifi ou Ethernet. Il faudra faire attention à modifier le schéma de connexion et le code pour qu’ils correspondent à votre usage.
Ce tutoriel fait suite au projet Pilotez 8 relais à l’aide d’un ESP32 et du moniteur série.
Matériel
- NodeMCU 32S
- Breadboard
- Jumper cable
- Module 8 relais
- Registre à décalage 74hc595
Principe
Nous avons vu, dans le tutoriel précédent, comment piloter 8 relais indépendamment à l’aide du moniteur série de l’IDE Arduino. Lorsqu’une connexion internet est disponible, comme sur le NodeMCU ESP32, il est possible de transformer votre microcontrôleur en serveur et d’héberger une page web. Cette page web peut servir d’interface graphique pour piloter votre projet. Nous allons créer une page Web, accessible via le réseau local, avec des boutons permettant d’activer les relais.
Code
Nous allons reprendre le code du tutoriel précédent et le combiner au code permettant de créer une Interface Web pour le NodeMCU ESP32. Sur la page Web, nous allons créer un tableau contenant 16 boutons correspondant aux actions « Activate », et « Reset » des 8 relais. Afin de visualiser certaines actions, nous ajoutons un encart afin d’afficher des messages provenant du microcontrôleur.
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(); }
Une fois la page web fonctionnelle, il ne nous reste plus qu’à gérer les requêtes du navigateur dans la fonction 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"); } } }
Vous trouverez ci-dessous, le code complet intégrant la gestion de la page web ainsi que du registre à décalage vue dans les articles précédents. N’oubliez pas de mettre à jour les variables ssid et password avec les valeurs de votre réseau.
//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(); }
Résultat
Une fois le code chargé dans le microcontrôleur, entrez l’adresse IP qui s’affiche dans le moniteur série (ici 192.168.1.64), dans votre navigateur web. La page suivante devrait s’afficher.
Vous pouvez ensuite fermer les relais avec les boutons « Activate » et les ouvrir avec les boutons « Reset » correspondants.
N’hésitez pas à nous laisser un commentaire pour nous donner votre avis, vos retours et pistes d’améliorations pour ce tutoriel.