Bonjour,
J’ai réalisé un petit projet serveur WEB permettant de piloter des relais, pour manipuler et comprendre le langage de programmation arduino.
J’ai pris comme base de départ un projet de J-M-L trouvé sur ce forum : Exemple d'usage du shield Ethernet pour faire un serveur interactif
Je le remercie car ça m’a permis de mettre un pied à l’étrier. J’ai essayé de modifier tout ça à ma sauce. Hormis la partie gestion serveur NTP que j’ai récupéré tel quel d’un exemple librairie, j’ai programmé les modifications et ajouts par moi-même pour comprendre tout ça.
Le code fonctionne bien.
Je vous le présente car j’aimerai avoir des avis et des retours, est-il lisible, y-a-t-il des coquilles, des choses qui peuvent le rendre instable, des erreurs par rapport aux conventions de programmation, peut-on l’écrire autrement, l'optimiser, peut-on le rendre plus léger, moins gourmand en mémoire vive… Bref, tout avis qui pourrait me faire progresser.
Merci à vous tous sur ce forum car tous vos topics, échanges et partages sont extrêmement utiles pour des novices comme moi.
Merci pour votre avis, bonne journée à tous.
/******************************************************************************************
Programme de communication Serveur Arduino / Client Navigateur WEB
Celui-ci permet d'afficher :
L'heure actuelle via un serveur NTP
3 bouton de changement d'état pour 3 pins sorties, avec affichage de l'état actuel de chaque sortie
1 formulaire horaire de mise à 1 et 1 formulaire horaire de mise à 0 pour chaque pin de sortie, avec affichage des horaires appliqués. Mise à 0 des sorties programmée en hard chaque 24h à minuit.
1 bouton pour mettre toutes les sorties à 1
1 bouton pour mettre toutes les sorties à 0
1 bouton pour effectuer un RESET du arduino en hard (câblage sortie pin A0 sur la commande d'un transistor alimentant la pin RESET)
Un enregistrement des états de pins et des valeurs de programmation horaire est également effectué dans l'EEPROM
afin de conserver les données et revenir aux états présents lors d'une coupure d'alimentation du arduino.
Synoptique de fonctionnement :
setup()
loop()
horloge NTP()
progHoraireAction()
priseEnChargeClient()
priseEnChargeClient()
priseEnChargeCommande()
analyseCommande()
enregistrementEEPROM()
envoieReponse()
*******************************************************************************************/
#include <SPI.h>
#include <Ethernet.h>
#include <EEPROM.h>
#include <TimeLib.h>
#include <EthernetUdp.h>
// déclaration du serveur arduino
byte mac[] = {0x00, 0xAA, 0xBB, 0xCC, 0xDE, 0x02};
IPAddress ip(192, 168, 0, 200);
EthernetServer webServer(80);
// déclaration du tableau enregistrant la requête url pour l'analyse (+1 pour le trailing '\0'
const byte MaxCommand = 30;
char urlCommand[MaxCommand + 1];
// déclaration du serveur NTP (On se connecte à un serveur NTP pour récupérer la date et l'heure : envoie d'une requête UDP au serveur sur le port 123
// et récupération de la réponse UDP du serveur sur le port local 8888 défini en dessous
IPAddress timeServer(192, 168, 0, 6); // addresse du serveur NTP (ici l'ordinateur du réseau interne configuré en serveur NTP)
const int timeZone = 2; // faisceau horaire Europe centrale
EthernetUDP Udp;
unsigned int localPort = 8888;
// Tableaux des éléments à identifier dans le tableau urlCommand durant l'analyse de la requête
const char * labelsOfInterest[] = {"ECL", "SON", "PLA", "TAL", "TEX", "RES"};
const unsigned int maxLabelsOfInterest = sizeof(labelsOfInterest) / sizeof(char*);
const char * labelsHoraire[] = {"EON", "EOF", "SON", "SOF", "PON", "POF"};
const unsigned int maxLabelsHoraire = sizeof(labelsHoraire) / sizeof(char*);
int horaire[6]; //tableau d'enregistrement des horaires
// déclaration des PINS
const byte ECLPin = 5;
const byte SONPin = 6;
const byte PLAPin = 7;
const byte RESPin = A0;
void setup()
{
// Pour utiliser le bouton RESET ARDUINO du programme, il faut connecter une pin (ici A0) à la commande d'un transistor alimentant en 5v la pin Reset du arduino.
// La pin Reset fait un reboot du arduino lorsqu'elle est à 0. Il faut donc mettre la pin de commande transistor à 1 tant qu'on ne souhaite pas rebooter.
// Attention, si il y a un problème avec ce cablage et que la pin RESET se trouve à 0, le arduino va rebooter en continu dès sa mise sous tension.
pinMode(RESPin, OUTPUT);
digitalWrite(RESPin, HIGH);
// Déclaration des pins en sortie et initialisation en fonction de leur état enregistré dans l'EEPROM avant la dernière coupure d'alimentation du arduino.
pinMode(ECLPin, OUTPUT);
digitalWrite(ECLPin, EEPROM.read(0));
pinMode(SONPin, OUTPUT);
digitalWrite(SONPin, EEPROM.read(1));
pinMode(PLAPin, OUTPUT);
digitalWrite(PLAPin, EEPROM.read(2));
Serial.begin(9600);
Ethernet.begin(mac, ip); // Initialise la connexion ethernet
webServer.begin(); // démarre l'écoute du serveur sur le réseau
Serial.print(F("*\n-> Le serveur est démarré et a comme adresse : "));
Serial.println(Ethernet.localIP());
Udp.begin(localPort);
Serial.println("waiting for sync UDP");
setSyncProvider(getNtpTime);
Serial.println(F("état EEPROM"));
for (int i = 0; i < 3; i++) {
Serial.println((EEPROM.read(i)));
}
}
/**** GESTION DE L'HORLOGE ****/
time_t prevDisplay = 0; // instant où l'horloge est initialisé
void horlogeNTP()
{
if (timeStatus() != timeNotSet) {
if (now() != prevDisplay) { //update the display only if time has changed
prevDisplay = now();
}
}
}
/**** COMMUNICATION AVEC LE SERVEUR NTP ****/
const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets
time_t getNtpTime()
{
while (Udp.parsePacket() > 0) ; // discard any previously received packets
Serial.println("Transmit NTP Request");
sendNTPpacket(timeServer);
uint32_t beginWait = millis();
while (millis() - beginWait < 1500) {
int size = Udp.parsePacket();
if (size >= NTP_PACKET_SIZE) {
Serial.println("Receive NTP Response");
Udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer
unsigned long secsSince1900;
// convert four bytes starting at location 40 to a long integer
secsSince1900 = (unsigned long)packetBuffer[40] << 24;
secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
secsSince1900 |= (unsigned long)packetBuffer[43];
return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
}
}
Serial.println("No NTP Response :-(");
return 0; // return 0 if unable to get the time
}
// send an NTP request to the time server at the given address
void sendNTPpacket(IPAddress &address)
{
// set all bytes in the buffer to 0
memset(packetBuffer, 0, NTP_PACKET_SIZE);
// Initialize values needed to form NTP request
// (see URL above for details on the packets)
packetBuffer[0] = 0b11100011; // LI, Version, Mode
packetBuffer[1] = 0; // Stratum, or type of clock
packetBuffer[2] = 6; // Polling Interval
packetBuffer[3] = 0xEC; // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
packetBuffer[12] = 49;
packetBuffer[13] = 0x4E;
packetBuffer[14] = 49;
packetBuffer[15] = 52;
// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
Udp.beginPacket(address, 123); //NTP requests are to port 123
Udp.write(packetBuffer, NTP_PACKET_SIZE);
Udp.endPacket();
}
/**** GESTION EEPROM ****/
void enregistrementEEPROM()
{
EEPROM.update (0, (digitalRead(ECLPin)));
EEPROM.update (1, (digitalRead(SONPin)));
EEPROM.update (2, (digitalRead(PLAPin)));
Serial.println(F("état EEPROM"));
for (int i = 0; i < 3; i++) {
Serial.println(EEPROM.read(i));
}
}
/**** GESTION DES PROGRAMMATIONS HORAIRES ****/
void progHoraireAction()
{
if (((100 * hour()) + minute()) != 0) { //Allumage et extinction des sorties en fonction de l'heure
//ECLAIRAGE
if (horaire[0] == ((100 * hour()) + minute())) digitalWrite(ECLPin, HIGH);
if (horaire[1] == ((100 * hour()) + minute())) digitalWrite(ECLPin, LOW);
//SONORISATION
if (horaire[2] == ((100 * hour()) + minute())) digitalWrite(SONPin, HIGH);
if (horaire[3] == ((100 * hour()) + minute())) digitalWrite(SONPin, LOW);
//PLAYER
if (horaire[4] == ((100 * hour()) + minute())) digitalWrite(PLAPin, HIGH);
if (horaire[5] == ((100 * hour()) + minute())) digitalWrite(PLAPin, LOW);
}
else { // Extinction totale à minuit
digitalWrite(ECLPin, LOW);
digitalWrite(SONPin, LOW);
digitalWrite(PLAPin, LOW);
}
}
/**** INTERPRETATION DE L'URL ENVOYE PAR LE CLIENT NAVIGATEUR WEB AU SERVEUR ARDUINO ****/
void analyseCommande()
{
char * item, * label, * action, * heure, * minutes;
int labelIndex;
Serial.print("urlCommand ");
for (int i = 0; i < MaxCommand + 1; i++) {
Serial.print(urlCommand[i]);
}
item = strchr(urlCommand, '%'); // On cherche un % dans urlCommand si il y a
if (item == NULL) { // Il n'y a pas de % dans urlCommand, on cherche une demande d'action
label = strtok(urlCommand, "=");
action = strtok(0, "H");
for (int i = 0; i < maxLabelsOfInterest; i++) { // On compare le label présent avant le = dans urlCommand avec ceux entrés dans le tableau global
if (!strcmp(labelsOfInterest[i], label)) {
labelIndex = i;
break;
}
}
switch (labelIndex) {
case 0: // ECLAIRAGE
if (!strcmp(action, "ON")) digitalWrite(ECLPin, HIGH); // On compare action à ON, si les deux sont égals la fonction strcmp renvoie un 0, on met la pin à 1
else if (!strcmp(action, "OFF")) digitalWrite(ECLPin, LOW);
break;
case 1: // SONORISATION
if (!strcmp(action, "ON")) digitalWrite(SONPin, HIGH);
else if (!strcmp(action, "OFF")) digitalWrite(SONPin, LOW);
break;
case 2: // PLAYER
if (!strcmp(action, "ON")) digitalWrite(PLAPin, HIGH);
else if (!strcmp(action, "OFF")) digitalWrite(PLAPin, LOW);
break;
case 3 : //ALLUMAGE TOTAL
if (!strcmp(action, "TOTAL+ON")) {
digitalWrite(ECLPin, HIGH);
digitalWrite(SONPin, HIGH);
digitalWrite(PLAPin, HIGH);
}
break;
case 4 :
if (!strcmp(action, "TOTAL+OFF")) {
digitalWrite(ECLPin, LOW);
digitalWrite(SONPin, LOW);
digitalWrite(PLAPin, LOW);
}
break;
case 5 : //RESET arduino
if (!strcmp(action, "RESET+ARDUINO")) digitalWrite(RESPin, LOW); //met la pin A0 à 0 donc la pin reset à 0
delay(2);
digitalWrite(RESPin, HIGH); //remet la pin A0 à 1 donc la pin reset à 1
break;
}
}
else { // Il y a un % dans urlCommand, on cherche un horaire
label = strtok(urlCommand, "=");
heure = strtok(0, "%");
minutes = strtok(0, "H");
for (int i = 0; i < maxLabelsHoraire; i++) {
if (!strcmp(labelsHoraire[i], label)) {
horaire[i] = (100 * atoi(heure)) + atoi(minutes + 2);
break;
}
}
}
Serial.println("tableau horaires");
for (int i = 0; i < 6; i++) {
Serial.println(horaire[i]);
}
Serial.print(F("état ECL "));
Serial.println(digitalRead(ECLPin));
Serial.print(F("état SON "));
Serial.println(digitalRead(SONPin));
Serial.print(F("état PLA "));
Serial.println(digitalRead(PLAPin));
enregistrementEEPROM();
}
/**** REPONSE DU SERVEUR ARDUINO AU CLIENT NAVIGATEUR WEB ****/
void envoieReponse(EthernetClient & client) // La fonction prend un client en argument
{
//entête de réponse standard, infos pour le navigateur
client.println(F("HTTP/1.1 200 OK")); // type du HTML + réponse 200=réussite
client.println(F("Content-Type: text/html")); //type de fichier et encodage des caractères
client.println(F("Connection: close")); // fermeture de la connexion quand toute la réponse sera envoyée
client.println(F("Refresh: 30")); // rafraichit la page toutes les 30s
client.println();
client.println(F("<!DOCTYPE HTML>"));
// Construction de la page HTML
client.println(F("<html><head><link rel='icon' href='data:,'><title>PLANETE BLEUE</title><style>body {text-align:center;}</style></head>")); // <link rel='icon' href='data:,'> est une commande pour supprimer la requête favicon du navigateur
client.println(F("<body bgcolor='#00979C'>")); // page backgroud color
client.println(F("<h1>COMMANDES PLANETE BLEUE</h1><hr>"));
client.println(F("<br />"));
//Horloge
byte h = hour();
byte m = minute();
byte s = second();
byte d = day();
byte ms = month();
if (h < 10) client.print('0');
client.print(hour());
client.print(F(":"));
if (m < 10) client.print('0');
client.print(minute());
client.print(F(":"));
if (s < 10) client.print('0');
client.print(second());
client.print(F("<br />"));
if (d < 10) client.print('0');
client.print(day());
client.print(F("/"));
if (ms < 10) client.print('0');
client.print(month());
client.print(F("/"));
client.print(year());
client.print(F("<br /><br />"));
// bouton ECL
client.print(F("<h2>ECLAIRAGE</h2>"));
if (digitalRead(ECLPin) == LOW) {
client.print(F("<body><p style='color:red;'>ETAT ACTUEL : OFF</p><form action='?' method='get'><input type='submit' name='ECL' value='ON'></form></body><br />"));
}
else {
client.print(F("<body><p style='color:green;'>ETAT ACTUEL : ON</p><form action='?' method='get'><input type='submit' name='ECL' value='OFF'></form></body><br />"));
}
client.print(F("Horaires de fonctionnement<br />"));
if ((horaire[0] / 100) < 10) client.print('0');
client.print((horaire[0] / 100));
client.print(F(":"));
if ((horaire[0] % 100) < 10) client.print('0');
client.print((horaire[0] % 100));
client.print(F("<form><label for='ECL'>Allumage </label><input type='time' id='ECL' name='EON' min='00:00' max='24:00' required><input type='submit' value='validation'></form>"));
if ((horaire[1] / 100) < 10) client.print('0');
client.print((horaire[1] / 100));
client.print(F(":"));
if ((horaire[1] % 100) < 10) client.print('0');
client.print((horaire[1] % 100));
client.print(F("<form><label for='ECL'>Extinction</label><input type='time' id='ECL' name='EOF' min='00:00' max='24:00' required><input type='submit' value='validation'></form><br />"));
// bouton SON
client.print(F("<h2>SONORISATION</h2>"));
if (digitalRead(SONPin) == LOW) {
client.print(F("<body><p style='color:red;'>ETAT ACTUEL : OFF</p></body>"));
client.print(F("<form action='?' method='get'><input type='submit' name='SON' value='ON'></form><br />"));
}
else {
client.print(F("<body><p style='color:green;'>ETAT ACTUEL : ON</p></body>"));
client.print(F("<form action='?' method='get'><input type='submit' name='SON' value='OFF'></form><br />"));
}
client.print(F("Horaires de fonctionnement<br />"));
if ((horaire[2] / 100) < 10) client.print('0');
client.print((horaire[2] / 100));
client.print(F(":"));
if ((horaire[2] % 100) < 10) client.print('0');
client.print((horaire[2] % 100));
client.print(F("<form><label for='SON'>Allumage </label><input type='time' id='SON' name='SON' min='00:00' max='24:00' required><input type='submit' value='validation'></form>"));
if ((horaire[3] / 100) < 10) client.print('0');
client.print((horaire[3] / 100));
client.print(F(":"));
if ((horaire[3] % 100) < 10) client.print('0');
client.print((horaire[3] % 100));
client.print(F("<form><label for='SON'>Extinction</label><input type='time' id='SON' name='SOF' min='00:00' max='24:00' required><input type='submit' value='validation'></form><br />"));
// bouton PLAY
client.print(F("<h2>PLAYER</h2>"));
if (digitalRead(PLAPin) == LOW) {
client.print(F("<body><p style='color:red;'>ETAT ACTUEL : OFF</p></body>"));
client.print(F("<form action='?' method='get'><input type='submit' name='PLA' value='ON'></form><br />"));
}
else {
client.print(F("<body><p style='color:green;'>ETAT ACTUEL : ON</p></body>"));
client.print(F("<form action='?' method='get'><input type='submit' name='PLA' value='OFF'></form><br />"));
}
client.print(F("Horaires de fonctionnement<br />"));
if ((horaire[4] / 100) < 10) client.print('0');
client.print((horaire[4] / 100));
client.print(F(":"));
if ((horaire[4] % 100) < 10) client.print('0');
client.print((horaire[4] % 100));
client.print(F("<form><label for='PLA'>Allumage </label><input type='time' id='PLA' name='PON' min='00:00' max='24:00' required><input type='submit' value='validation'></form>"));
if ((horaire[5] / 100) < 10) client.print('0');
client.print((horaire[5] / 100));
client.print(F(":"));
if ((horaire[5] % 100) < 10) client.print('0');
client.print((horaire[5] % 100));
client.print(F("<form><label for='PLA'>Extinction</label><input type='time' id='PLA' name='POF' min='00:00' max='24:00' required><input type='submit' value='validation'></form><br /><hr><br />"));
// bouton allumage totale
client.print(F("<form action='?' method='get'><input type='submit' name='TAL' value='TOTAL ON'></form><br />"));
// bouton extinction totale
client.print(F("<form action='?' method='get'><input type='submit' name='TEX' value='TOTAL OFF'></form><br />"));
// bouton Reset arduino
client.print(F("<form action='?' method='get'><input type='submit' name='RES' value='RESET ARDUINO'></form><br /><br />"));
client.print(F("APRES UN RESET RECHARGER LA PAGE DU NAVIGATEUR AVEC L'ADDRESSE IP ARDUINO"));
client.println(F("</center></body></html>"));
}
/**** PRISE EN CHARGE DE LA REQUETE URL DU CLIENT NAVIGATEUR WEB ****/
void priseEnChargeCommande(EthernetClient & client)
{
// Si une commande est bien enregistrée dans urlCommand alors on l'analyse et, dans tout les cas on envoie une réponse
if (strlen(urlCommand) != 0) {
analyseCommande();
}
envoieReponse(client);
}
/**** COMMUNICATION ENTRE CLIENT NAVIGATEUR WEB ET SERVEUR ARDUINO ****/
void priseEnChargeClient()
{
boolean urlCommandFound = false;
char httpHeader[MaxCommand + 1];
byte httpHeaderIndex = 0;
httpHeader[0] = '\0';
urlCommand[0] = '\0';
EthernetClient client = webServer.available(); // Regarde si un client est connecté et attend une réponse de la part du serveur arduino
if (client) { // Si un client est là...
boolean currentLineIsBlank = true;
while (client.connected()) { // Tant que le client est connecté
if (client.available()) { // A-t-il des choses à dire ?
char c = client.read(); // on lit l'url qu'il envoie pour savoir ce qu'il veut
//Serial.print(c);
if (!urlCommandFound) {
httpHeader[httpHeaderIndex++] = c;
httpHeader[httpHeaderIndex] = '\0';
if (httpHeaderIndex > MaxCommand - 1) httpHeaderIndex = MaxCommand - 1;
}
if (c == '\n' && currentLineIsBlank) { // Une requête HTTP se termine par une ligne vide
priseEnChargeCommande(client);
delay(5); // Donne le temps au client de prendre les données
client.stop(); // Ferme la connexion avec le client
break;
}
if (c == '\n') {
currentLineIsBlank = true; // démarre une nouvelle ligne
if (!strncmp("GET /? ", httpHeader, 6)) {
strcpy(urlCommand, (httpHeader + 6)); // permet de se débarrasser du "GET /? " de début de requête url
char * firstSpacePtr = strchr(urlCommand, ' ');
if (firstSpacePtr) *firstSpacePtr = '\0'; // supprime l'espace entre le corps de la requête et le http...
urlCommandFound = true;
} else {
httpHeaderIndex = 0;
httpHeader[0] = '\0';
}
} else if (c != '\r') {
currentLineIsBlank = false; // un nouveau caractère est présent sur la ligne actuelle, on ignore '\r'
}
}
}
}
}
void loop()
{
horlogeNTP();
progHoraireAction();
priseEnChargeClient();
}