jeudi 12 mars 2020

ARDUINO + Ethernet ou ESP32 : comparons



ARDUINO + Ethernet ou ESP32 : comparons


Certaines personnes s'imaginent à tort qu'une carte Ethernet est facile à utiliser et que la librairie Ethernet offre les mêmes facilités que la librairie ESP32 ou ESP8266.

Il n'en est rien. Récupérer par exemple les arguments d'une requête en méthode POST n'est pas du tout à la portée de l'amateur.

Mieux vaut être prévenu avant de s'engager dans une voie sans issue.

1. Les deux approches

Cet article a pour but de comparer deux approches dans l'écriture d'un serveur HTTP.

La première solution utilise une carte ARDUINO NANO + carte Ethernet :
La seconde un module WIFI ESP32 :

2. Méthode GET ou POST

La comparaison porte principalement sur la récupération des données d'un formulaire.
Lorsque l'on valide un formulaire la requête envoyée par le navigateur est formée d'une nouvelle URL suivie du caractères '?' puis d'une suite d'arguments nom=valeur séparés par des caractères '&'.

Imaginons un formulaire permettant de saisir une entrée de répertoire. L'URL pourrait ressembler à ceci :

http://form_contacts?name=dupont&age=25&tel=0102034455&email=dupont@orange.fr

Deux méthode d'envoi existent : GET ou POST.

En méthode GET les arguments suivent l'URL et on les voit dans la barre d'adresse du navigateur lorsque l'on valide le formulaire.
Les arguments font partie de l'URL, qui elle-même fait partie de l'entête HTTP.

En méthode POST les arguments ne sont pas visibles dans la barre d'adresse du navigateur lorsque l'on valide le formulaire.
Les arguments sont envoyés après l'entête HTTP. Un double caractère '\n' signale la fin de l'entête et donc le début des arguments.

3. La version ARDUINO + Ethernet

Le sketch est disponible ici :
ethernet.ino

Le sketch permet d'envoyer le formulaire avec la méthode GET ou POST :

#define FORM                  POST
ou
#define FORM                  GET

Avec la classe EthernetServer il faut lire et décortiquer la requête soi-même pour récupérer les informations :
  • méthode : GET ou POST ou autre
  • URL
  • protocole
  • adresse IP du client
  • arguments
  • etc.
Il faut également associer soi-même URL et fonction de traitement.

Des caractères échappés peuvent apparaître dans une URL:
  • %20 : espace
  • %40 : @
  • etc
Ceux-ci doivent être décodés par nos soins. La classe EthernetServer ne prévoit rien à ce sujet.

Parlons de la méthode POST.
Classiquement il faudrait lire l'entête complète pour pouvoir récupérer les arguments, ce qui réclamerait au minimum 500 à 600 octets de mémoire RAM.
Je me suis débrouillé pour que la requête soit lue partiellement, y compris en méthode POST, en gérant les arguments à part, ceci afin de consommer le moins de mémoire RAM possible :

Les variables globales utilisent 846 octets (41%) de mémoire dynamique, ce qui laisse 1202 octets pour les variables locales. Le maximum est de 2048 octets.

Y a t-il des limitations ?
Oui, étant donné le peu de mémoire disponible il faut limiter la taille de certains buffers :

#define REQUEST_MAX           150    // longueur maximale de la requête HTTP
#define URL_MAX                    100   
// longueur maximale d'une URL
#define ARGSTR_MAX              80     // longueur maximale des arguments
#define ARG_MAX                     5       // nombre maximal d'arguments

Ces valeurs limitent fortement les possibilités de notre application.
On pourra adopter une carte ARDUINO MEGA pour plus de souplesse.

Je n'ai pas utilisé la classe String pour des raisons évidentes de fragmentation :
https://riton-duino.blogspot.com/2020/02/arduino-la-fragmentation-memoire.html

4. La version ESP32

Le sketch est disponible ici :
esp32.ino

Je suis parti de l'exemple HelloServer.

Avec la classe WebServer ESP32 tout ce qui a été fait précédemment dans la version ARDUINO + Ethernet est déjà fait.On pourrait faire la même chose, avec la même facilité, avec un ESP8266 et la classe ESP8266WebServer.

Je n'ai pas utilisé de stockage des pages HTML en SPIFFS, afin de pouvoir comparer les deux solutions sur un pied d'égalité.

5. Mode d'emploi

Après le chargement les deux sketches affichent sur le terminal série l'adresse IP sur laquelle il faut se connecter.

Voici les différentes URL que le serveur accepte :

http://xxx.xxx.xxx.xxx/parser : affiche les arguments de l'URL

Exemple : http://xxx.xxx.xxx.xxx/parser?arg1=1234&arg2=4567

Cette requête affichera les informations suivantes :
  • l'entête HTTP
  • adresse et port local
  • adresse et port distant
  • arguments : arg1=1234 et arg2=4567
http://xxx.xxx.xxx.xxx/form : affiche un formulaire :



Après avoir renseigné les champs (l'adresse mail doit comporter un caractère @) et cliqué sur Send la page suivante affiche les informations du formulaire.

On peut imaginer toutes sortes de traitements :
  • stockage en SD 
  • ajout à une base de données
  • etc.
Le serveur ESP32 propose une page de plus :

http://xxx.xxx.xxx.xxx/sensor-form : affiche un formulaire enrichi :
Ce formulaire comporte un ensemble de cases à cocher, de boutons radios et de champs numériques bornés.

Après avoir renseigné les champs (les champs numériques comportent des valeurs minimales / maximales) et cliqué sur Send la page suivante affiche les informations du formulaire.

Un petit exemple pour apprendre à se servir de différents champs "input".

A remarquer : la technique de formatage d'une page WEB avec sprintf().

Il existe beaucoup de documentation sur le WEB :
https://developer.mozilla.org/fr/docs/Web/HTML/Element/Input

6. Comparaison des deux solutions

La version ARDUINO + Ethernet :
  • 1 journée de travail
  • 386 lignes de code
  • complexité importante
La version ESP32 :
  • 30 minutes de travail
  • 122 lignes de code (hors formulaire enrichi)
  • complexité moyenne
Le but était simple : démontrer la richesse de la classe WebServer ESP32 et sa nette supériorité sur la classe EthernetServer.
Je ne pense pas que qui que ce soit aurait la moindre préférence pour la solution Ethernet.

A moins d'être absolument obligé d'utiliser Ethernet, l'ESP32 ou ESP8266 reste la solution idéale.

6.1. Le DHCP

La librairie Ethernet ne permet pas la modification du HostName par contre celui-ci est composé du mot WIZnet + les 3 derniers caractères de l'adresse Mac. Il y a donc moyen d'avoir un HostName différent par carte.

Côté ESP32 une méthode WiFi.setHostname(name) existe. Malheureusement elle ne fonctionne pas.
Une inspection des paquets DHCP avec tcpdump révèle que le HostName n'est pas présent dans la requête.

Il y a une astuce ou plutôt un contournement à connaître. J'ai trouvé l'information ici :  Hostname not sent via DHCP request #2537

  char hostName[12];
  uint8_t mac[6];
  WiFi.mode(WIFI_STA);
  WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE); 

  WiFi.macAddress(mac);
  sprintf(hostName, "Esp32%x%x%x", mac[3], mac[4], mac[5]);
  Serial.printf("setting hostname %s: %d\n", hostName, WiFi.setHostname(hostName));
 

  Serial.print("Connecting to "); Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.printf("%s (%s): connected to %s", WiFi.getHostname(), WiFi.macAddress().c_str(), ssid);
  Serial.print("IP address: "); Serial.println(WiFi.localIP());


Le nom attribué est libre. J'ai choisi ESP32 + les 3 derniers caractères de l'adresse Mac, mais c'est juste mon choix.

Accessoirement il est à noter qu'une LiveBox n'affichera ces changements qu'après redémarrage.

Avec tcpdump (sous Linux) il est facile d'inspecter les paquets DHCP :

$ sudo tcpdump -i eno1 -vvv -s 1500 '((port 67 or port 68) and (udp[38:4] = 0x3c71bf47a5b0))'
tcpdump: listening on eno1, link-type EN10MB (Ethernet), capture size 1500 bytes
13:03:41.012470 IP (tos 0x0, ttl 255, id 0, offset 0, flags [none], proto UDP (17), length 336)
    0.0.0.0.bootpc > 255.255.255.255.bootps: [udp sum ok] BOOTP/DHCP, Request from 3c:71:bf:47:a5:b0 (oui Unknown), length 308, xid 0xf7aa125b, Flags [none] (0x0000)
      Client-Ethernet-Address 3c:71:bf:47:a5:b0 (oui Unknown)
      Vendor-rfc1048 Extensions
        Magic Cookie 0x63825363
        DHCP-Message Option 53, length 1: Discover
        MSZ Option 57, length 2: 1500
        Hostname Option 12, length 11: "Esp3247a5b0"
        Parameter-Request Option 55, length 12:
          Subnet-Mask, Default-Gateway, BR, Domain-Name-Server
          Domain-Name, Netbios-Name-Server, Netbios-Node, Netbios-Scope
          Router-Discovery, Static-Route, Classless-Static-Route, Vendor-Option
        END Option 255, length 0
        PAD Option 0, length 0, occurs 33


eno1 est le nom de l'interface Ethernet de mon PC. On voit bien passer le HostName dans la requête.

7. Liens utiles

Librairie Ethernet :
https://github.com/arduino-libraries/Ethernet

Un autre article sur le sujet ARDUINO + Ethernet:
https://riton-duino.blogspot.com/2019/02/un-web-server-sur-ethernet.html
Cet article a été mis à jour dernièrement. Le serveur accepte maintenant les requêtes POST.


Cordialement
Henri

8. Mises à jour

16/03/2020 : ajout d'un formulaire enrichi (ESP32)
06/04/2020 : ajout DHCP et HostName

Aucun commentaire:

Enregistrer un commentaire