Différences entre versions de « Arduino W5100 web server »

De The Linux Craftsman
Aller à la navigation Aller à la recherche
Ligne 1 : Ligne 1 :
= Partie électronique =
+
=Introduction=
== Le composant ==
+
Il faut prendre connaissance du [[Arduino_W5100_intro | module W5100]] et s'assurer que le module possède une configuration de [[Arduino_W5100_OSI3 |niveau 3 OSI]].
Le Shiled Ethernet embarque un contrôleur Wiznet 5100 qui est plus fiable que son homologue le contrôleur ENC28J60. Il existe principalement en shield mais peut également se trouver en platine.
 
 
 
{| align=center
 
|-
 
|[[Fichier:W5100_shield_uno_mega.jpg|centré|250px]]
 
|[[Fichier:W5100_shield_nano.jpg|centré|350px]]
 
|[[Fichier:W5100_platine.jpg|centré|200px]]
 
|-
 
|align=center| ''Wiznet 5100'' version shield pour Mega/Uno
 
|align=center| ''Wiznet 5100'' version shield pour Nano
 
|align=center| ''Wiznet 5100'' version platine
 
|}
 
Les versions shield sont intéressantes car elles embarquent également un lecteur de carte micro SD qui, dans notre cas de figure, va nous servir à héberger les pages du site Web.
 
 
 
== Le montage ==
 
Le montage suivant requiert le shield, un câble Ethernet qui le relie à un switch et un ordinateur relié au même switch.
 
{| align=center
 
|-
 
|[[Fichier:W5100_shield_on_mega.jpg|centré|350px]]
 
|[[Fichier:W5100_shield_on_uno.jpg|centré|300px]]
 
|[[Fichier:W5100_shield_on_nano.jpg|centré|300px]]
 
|-
 
|align=center| Shield ''Wiznet 5100'' sur un Mega
 
|align=center| Shield ''Wiznet 5100'' sur un Uno
 
|align=center| Shield ''Wiznet 5100'' sur un Nano
 
|}
 
 
 
N'oubliez pas que la communication entre l'Arduino et le shield se fait a travers certaines broches qu'il ne faut donc pas utiliser !
 
[[Fichier:W5100_shield_port_use.jpg|centré|600px]]
 
 
 
Comme vous pouvez le constater, le protocole SPI ([https://fr.wikipedia.org/wiki/Serial_Peripheral_Interface Serial Port Interface]) est utilisé ici pour communiquer avec la carte SD et le contrôleur Ethernet (SPI = pas en simultané).
 
 
 
= Partie logicielle =
 
== Les librairies ==
 
Les librairies utilisées sont déjà inclues de base et sont :
 
* SPI.h
 
* Ethernet.h
 
* SD.h
 
== Ethernet : attribution d'une adresse IP ==
 
=== Adressage IP dynamique ===
 
Le plus simple, si votre réseau possède un serveur DHCP (normalement votre box est dotée de cette fonctionnalité...), est de demander la configuration OSI de niveau 3. Les seuls paramètres qui nous intéresserons vraiment sont l'adresse IP et le masque. La passerelle ne sera pas utile dans notre cas...
 
<source lang="c">
 
#include <SPI.h>
 
#include <Ethernet.h>
 
/**
 
  Adresse MAC du module, doit être unique sur le réseau !
 
  Ici 00:01:02:03:04:05
 
*/
 
byte mac[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
 
 
 
void setup() {
 
  // Démarrage du port série
 
  Serial.begin(9600);
 
  Serial.println(F("Requesting ip..."));
 
  /**
 
    Démarrage du shield Ethernet sans spécifier d'adresse IP
 
    Cela oblige le contrôleur Ethernet à demander une configuration OSI 3
 
  */
 
  if (Ethernet.begin(mac) == 0) {
 
    // Si c'est un échec, pas la peine de pousuivre...
 
    Serial.println(F("DHCP failure !"));
 
    while (true);
 
  }
 
  // Affichage des informations obtenues
 
  Serial.print(F("IP : "));
 
  Serial.println(Ethernet.localIP());
 
  Serial.print(F("Mask : "));
 
  Serial.println(Ethernet.subnetMask());
 
  Serial.print(F("Gateway : "));
 
  Serial.println(Ethernet.gatewayIP());
 
  Serial.print(F("DNS : "));
 
  Serial.println(Ethernet.dnsServerIP());
 
}
 
void loop() {
 
}
 
</source>
 
Si vous avez une réponse de votre serveur DHCP, vous devriez obtenir, dans le terminal série, le résultat suivant:
 
<pre>
 
Requesting ip...
 
IP : 192.168.1.26
 
Mask : 255.255.255.0
 
Gateway : 192.168.1.254
 
DNS : 192.168.1.254
 
</pre>
 
 
 
=== Adressage IP statique ===
 
On peut très bien spécifier une configuration OSI de niveau 3 de manière statique. Deux avantages : pas d'adresse IP qui change et c'est plus rapide !
 
<source lang="c">
 
#include <SPI.h>
 
#include <Ethernet.h>
 
// Adresse IP
 
IPAddress ip = { 192, 168, 1, 26 };
 
// Masque de sous-réseau
 
IPAddress mask = { 255, 255, 255, 0 };
 
// Passerelle
 
IPAddress gateway = { 192, 168, 1, 254 };
 
// DNS
 
IPAddress server_dns = { 192, 168, 1, 254 };
 
/**
 
  Adresse MAC du module, doit être unique sur le réseau !
 
  Ici 00:01:02:03:04:05
 
*/
 
byte mac[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
 
 
 
void setup() {
 
  // Démarrage du port série
 
  Serial.begin(9600);
 
  Serial.println(F("Setting ip..."));
 
  /**
 
    Démarrage du shield Ethernet en spécifiant la configuration OSI 3
 
  */
 
  Ethernet.begin(mac, ip, server_dns, gateway, mask);
 
  // Affichage des informations
 
  Serial.print(F("IP : "));
 
  Serial.println(Ethernet.localIP());
 
  Serial.print(F("Mask : "));
 
  Serial.println(Ethernet.subnetMask());
 
  Serial.print(F("Gateway : "));
 
  Serial.println(Ethernet.gatewayIP());
 
  Serial.print(F("DNS : "));
 
  Serial.println(Ethernet.dnsServerIP());
 
}
 
void loop() {
 
}
 
</source>
 
Le même résultat que précédement devrait s'afficher dans le terminal série.
 
 
 
=== Adressage au choix ! ===
 
Le '''must''', c'est de pouvoir choisir en fonction du réseau où on va placer notre montage ! Le code ci-dessous utilise la compilation conditionnelle pour faire cohabiter les deux codes précédents. Le choix se fera en fonction de la variable ''DHCP'' positionner au tout début du sketch.
 
<source lang="c">
 
#include <SPI.h>
 
#include <Ethernet.h>
 
/**
 
  Variable permettant de choisir entre une assignation
 
  fixe ou dynamique du niveau 3 OSI
 
  '0' --> configuration statique
 
  '1' --> configuration dynamique
 
*/
 
#define DHCP 0
 
/**
 
  Adresse MAC du module, doit être unique sur le réseau !
 
  Ici 00:01:02:03:04:05
 
*/
 
byte mac[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
 
#if DHCP == 0
 
// Adresse IP
 
IPAddress ip = { 192, 168, 1, 26 };
 
// Masque de sous-réseau
 
IPAddress mask = { 255, 255, 255, 0 };
 
// Passerelle
 
IPAddress gateway = { 192, 168, 1, 254 };
 
// DNS
 
IPAddress server_dns = { 192, 168, 1, 254 };
 
#endif
 
 
 
void setup() {
 
  // Démarrage du port série
 
  Serial.begin(9600);
 
  /**
 
    Démarrage du shield Ethernet sans spécifier d'adresse IP
 
    Cela oblige le contrôleur Ethernet à demander une configuration OSI 3
 
  */
 
#if DHCP == 0
 
  Serial.println(F("Setting ip..."));
 
  Ethernet.begin(mac, ip, server_dns, gateway, mask);
 
#else
 
  Serial.println(F("Requesting ip..."));
 
  if (Ethernet.begin(mac) == 0) {
 
    // Si c'est un échec, pas la peine de pousuivre...
 
    Serial.println(F("DHCP failure !"));
 
    while (true);
 
  }
 
#endif
 
  // Affichage des informations obtenues
 
  Serial.print(F("IP : "));
 
  Serial.println(Ethernet.localIP());
 
  Serial.print(F("Mask : "));
 
  Serial.println(Ethernet.subnetMask());
 
  Serial.print(F("Gateway : "));
 
  Serial.println(Ethernet.gatewayIP());
 
  Serial.print(F("DNS : "));
 
  Serial.println(Ethernet.dnsServerIP());
 
}
 
void loop() {
 
}
 
</source>
 
Lorsque vous avez plusieurs possibilités n'hésitez pas à user de la compilation conditionnelle car, la mémoire réduite de l'ATMega ne permet pas d'embarquer beaucoup de code...
 
 
 
== Ethernet : utilisation d'un serveur ==
 
 
{|align="center" style="width:600px"
 
{|align="center" style="width:600px"
 
|-
 
|-
Ligne 195 : Ligne 6 :
 
[[Fichier:Warning manual.jpg|centré|200px]]
 
[[Fichier:Warning manual.jpg|centré|200px]]
 
|
 
|
Avant d'aller plus loin, lisez la section sur [[Arduino_sketch_writing| comment écrire un sketch]]. Le code qui va suivre fait référence à des parties bien spécifiques, détaillées dans la section suscitée.
+
Soyez sûr de comprendre la section sur [[Arduino_sketch_writing| comment écrire un sketch]] avant de poursuivre. Le code ci-dessous fait référence à des parties bien spécifiques, détaillées et expliquées dans la section suscitée.
 
|}
 
|}
 +
= Ethernet : utilisation d'un serveur =
 
Il est possible de démarrer un serveur qui écoutera sur un port précis. Dans notre cas de figure, le serveur utilisera le port ''TCP 80''. Dans un premier temps, nous allons récupérer la requête qui vient du client pour voir comment elle est formatée. Dans un deuxième temps, on verra comment on pourra traiter et formuler une réponse.
 
Il est possible de démarrer un serveur qui écoutera sur un port précis. Dans notre cas de figure, le serveur utilisera le port ''TCP 80''. Dans un premier temps, nous allons récupérer la requête qui vient du client pour voir comment elle est formatée. Dans un deuxième temps, on verra comment on pourra traiter et formuler une réponse.
=== Récupération de la requête ===
+
== Récupération de la requête ==
====variables globales====
+
===variables globales===
 
<source lang="c">
 
<source lang="c">
 
   // Serveur écoutant sur le port 80
 
   // Serveur écoutant sur le port 80
 
   EthernetServer server(80);
 
   EthernetServer server(80);
 
</source>
 
</source>
====setup()====
+
===setup()===
 
<source lang="c">
 
<source lang="c">
 
   // Démarrage du serveur
 
   // Démarrage du serveur
 
   server.begin();
 
   server.begin();
 
</source>
 
</source>
====loop()====
+
===loop()===
 
<source lang="c">
 
<source lang="c">
 
   // On écoute les connections entrantes
 
   // On écoute les connections entrantes
Ligne 254 : Ligne 66 :
 
Pour terminer proprement la requête ''HTTP'', il suffit de mettre fin à la session TCP à réception des deux sauts de ligne.
 
Pour terminer proprement la requête ''HTTP'', il suffit de mettre fin à la session TCP à réception des deux sauts de ligne.
  
=== Traitement de fin de requête ===
+
== Traitement de fin de requête ==
 
On va écrire un programme qui ''attend'' le saut de ligne (''\n'') à la fin d'une ligne vide.
 
On va écrire un programme qui ''attend'' le saut de ligne (''\n'') à la fin d'une ligne vide.
====loop()====
+
===loop()===
 
<source lang="c">
 
<source lang="c">
 
// On écoute les connections entrantes
 
// On écoute les connections entrantes
Ligne 318 : Ligne 130 :
 
A partir de maintenant, côté navigateur, la requête se termine proprement et on attend plus le ''timeout'' TCP. Il faudrait maintenant, ''digérer'' l'URL pour pouvoir répondre en fonction de la requête !
 
A partir de maintenant, côté navigateur, la requête se termine proprement et on attend plus le ''timeout'' TCP. Il faudrait maintenant, ''digérer'' l'URL pour pouvoir répondre en fonction de la requête !
  
=== Digestion de l'URL ===
+
== Digestion de l'URL ==
 
A partir de maintenant, le code devient trop complexe pour résider dans la fonction ''loop()''. Il faut donc le fragmenter en plusieurs fonctions qui seront appelées dans ''loop()''.
 
A partir de maintenant, le code devient trop complexe pour résider dans la fonction ''loop()''. Il faut donc le fragmenter en plusieurs fonctions qui seront appelées dans ''loop()''.
====variables globales====
+
===variables globales===
 
<source lang="c">
 
<source lang="c">
 
</source>
 
</source>
====loop()====
+
===loop()===
 
<source lang="c">
 
<source lang="c">
 
</source>
 
</source>
====fonctions annexes====
+
===fonctions annexes===
 
<source lang="c">
 
<source lang="c">
 
</source>
 
</source>
  
== Utilisation de la carte SD ==
+
= Utilisation de la carte SD =
=== Affichage des paramètres de la carte ===
+
== Affichage des paramètres de la carte ==
=== Lecture ===
+
== Lecture ==
=== Écriture ===
+
== Écriture ==
=== Ajout ===
+
== Ajout ==
=== Effacement ===
+
== Effacement ==

Version du 11 janvier 2017 à 09:44

Introduction

Il faut prendre connaissance du module W5100 et s'assurer que le module possède une configuration de niveau 3 OSI.

Warning manual.jpg

Soyez sûr de comprendre la section sur comment écrire un sketch avant de poursuivre. Le code ci-dessous fait référence à des parties bien spécifiques, détaillées et expliquées dans la section suscitée.

Ethernet : utilisation d'un serveur

Il est possible de démarrer un serveur qui écoutera sur un port précis. Dans notre cas de figure, le serveur utilisera le port TCP 80. Dans un premier temps, nous allons récupérer la requête qui vient du client pour voir comment elle est formatée. Dans un deuxième temps, on verra comment on pourra traiter et formuler une réponse.

Récupération de la requête

variables globales

  // Serveur écoutant sur le port 80
  EthernetServer server(80);

setup()

  // Démarrage du serveur
  server.begin();

loop()

  // On écoute les connections entrantes
  EthernetClient client = server.available();
  // Si la connection est établie (SYN / SYN+ACK / ACK)...
  if (client) {
    Serial.println(F("---- new request ----"));
    // ...pendant que le client maintient la session TCP...
    while (client.connected()) {
      // ...et que la requête contient des caractères...
      if (client.available()) {
        // ...on récupére les caractères...
        char c = client.read();
        // ... et on les affiche sur le terminal série
        Serial.print(c);
      }
    }
    // Fin de la requête
    Serial.println(F(""));
    Serial.println(F("---- end request ----"));
  }
}

Lorsque l'on entre dans la barre de recherche du navigateur l'adresse IP du module Ethernet on a, après le timeout TCP, le résultat suivant :

---- new request ----
GET / HTTP/1.1
Host: 192.168.1.26
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:50.0) Gecko/20100101 Firefox/50.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1


---- end request ----

On constate que :

  • la requête démarre par le verbe HTTP (ici GET) suivit de l'URL (ici /) et du protocole (ici HTTP/1.1) ;
  • la requête contient toutes les entêtes envoyés par le navigateur ;
  • la requête se termine par deux sauts de ligne.

Pour terminer proprement la requête HTTP, il suffit de mettre fin à la session TCP à réception des deux sauts de ligne.

Traitement de fin de requête

On va écrire un programme qui attend le saut de ligne (\n) à la fin d'une ligne vide.

loop()

// On écoute les connections entrantes
EthernetClient client = server.available();
// Si la connection est établie (SYN / SYN+ACK / ACK)...
if (client) {
  // Nos deux compteurs
  uint8_t nbNewLine = 0, bytesOnLine = 0;
  while (client.connected()) {
    if (client.available()) {
      char c = client.read();
      if (c != '\r') {
        if (c == '\n') {
          // Fin de ligne
          if (bytesOnLine == 0) {
            // Ligne vide on incrémente le conteur
            nbNewLine++;
            if (nbNewLine == 1) {
              // Deuxième ligne vide = fin requête
              // Envoie du status HTTP, ici '200 OK'
              client.println("HTTP/1.1 200 OK");
              // Entête spécifiant le contenu du corps
              client.println("Content-Type: text/html");
              /**
                 On prévient le client qu'à la fin de
                 la requête, on coupe la session TCP
              */
              client.println("Connection: close");
              // Spération entre les entêtes HTTP et le corps du message
              client.println();
              // contenu HTML
              client.println("<!DOCTYPE HTML>");
              client.println("<html>");
              client.println("It Works !");
              client.println("</html>");
              // On donne le temps au navigateur de traiter le message
              delay(1);
              // Fermeture de la session TCP
              client.stop();
              break;
            }
          } else {
            // Ligne contenant des caractères, on remet le conteur à zéro
            bytesOnLine = 0;
          }
        } else {
          /**
            On reçoit des caractères autre que \r et \n 
            alors on incrémente le conteur de caractères
          */
          bytesOnLine++;
        }
      }
    }
  }
  // Fin de la requête
  Serial.println(F(""));
  Serial.println(F("---- end request ----"));
}

A partir de maintenant, côté navigateur, la requête se termine proprement et on attend plus le timeout TCP. Il faudrait maintenant, digérer l'URL pour pouvoir répondre en fonction de la requête !

Digestion de l'URL

A partir de maintenant, le code devient trop complexe pour résider dans la fonction loop(). Il faut donc le fragmenter en plusieurs fonctions qui seront appelées dans loop().

variables globales

loop()

fonctions annexes

Utilisation de la carte SD

Affichage des paramètres de la carte

Lecture

Écriture

Ajout

Effacement