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

De The Linux Craftsman
Aller à la navigation Aller à la recherche
Ligne 172 : Ligne 172 :
 
===loop()===
 
===loop()===
 
<source lang="c">
 
<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 };
 +
// Serveur écoutant sur le port 80
 +
EthernetServer server(80);
 +
// Méthode démarrant le début de l'URL
 +
static const char METHOD[] = "GET";
 +
/**
 +
  Arbitrairement on décide que le chemin
 +
  contiendra 5 partie de 10 caractères
 +
*/
 +
static const uint8_t URL_MAX_PART = 5;
 +
static const uint8_t URL_PART_SIZE = 10;
 +
/**
 +
  Arbitrairement on décide qu'il y aura
 +
  maximum 5 paramètres de 10 caractères
 +
*/
 +
static const uint8_t PARAM_MAX_NUMBER = 5;
 +
static const uint8_t PARAM_SIZE = 10;
 +
// Tableau permettant de stocker les parties du chemin
 +
char url_parts[URL_MAX_PART][URL_PART_SIZE];
 +
// Tableau permettant de stocker les paramètres
 +
char param_names[PARAM_MAX_NUMBER][PARAM_SIZE];
 +
char param_values[PARAM_MAX_NUMBER][PARAM_SIZE];
 +
 +
// Variables utilisée à l'exécution
 +
uint16_t http_error_code = 0;
 +
bool isUrl = false;
 +
// Nombre de parties du chemin
 +
uint8_t nbUrlPart = 0;
 +
// Nombre de paramètres de l'URL
 +
uint8_t nbParam = 0;
 +
 +
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 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() {
 +
  // 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 (!isUrl && digestURL(c)) {
 +
          isUrl = true;
 +
          if (http_error_code != 0) {
 +
            Serial.print(F("Error : "));
 +
            Serial.println(http_error_code);
 +
          } else {
 +
            for (uint8_t i = 0; i < nbUrlPart; i++) {
 +
              Serial.println(url_parts[i]);
 +
            }
 +
            for (uint8_t i = 0; i < nbParam; i++) {
 +
              Serial.print(param_names[i]);
 +
              Serial.print(F(" = "));
 +
              Serial.println(param_values[i]);
 +
            }
 +
          }
 +
        }
 +
      } else {
 +
        // Fin de la requête
 +
        client.print(F("OK"));
 +
        delay(1);
 +
        client.stop();
 +
        Serial.println(F(""));
 +
        Serial.println(F("---- end request ----"));
 +
      }
 +
    }
 +
    // RAZ des variables
 +
    isUrl = false;
 +
    http_error_code = 0;
 +
    nbUrlPart = 0;
 +
    nbParam = 0;
 +
  }
 +
}
 +
/**
 +
  Digère l'URL en séparant le chemin et les paramètres
 +
*/
 +
bool digestURL(char c) {
 +
  static int8_t algoPart = 0, readBytes = 0;
 +
  if (algoPart == -1) {
 +
    // RAZ des variables
 +
    algoPart = 0;
 +
    readBytes = 0;
 +
  }
 +
  else if (algoPart == 0) {
 +
    // On vérifie que le caractère lue correspond à un caractère de la méthode
 +
    if (c == METHOD[readBytes++]) {
 +
      if (readBytes == strlen(METHOD)) {
 +
        // On vient de lire 'GET', on passe à la suite
 +
        algoPart++;
 +
        readBytes = 0;
 +
      }
 +
    } else {
 +
      //On remet le compteur à zéro
 +
      readBytes = 0;
 +
    }
 +
  } else if (algoPart == 1) {
 +
    // On doit ignorer les 2 caractères qui suivent ' /'
 +
    if (readBytes < 1) {
 +
      readBytes++;
 +
    } else {
 +
      // Les deux caractères sont passés, on passe à la suite
 +
      algoPart++;
 +
      readBytes = 0;
 +
    }
 +
  } else if (c == ' ' || c == '\n') {
 +
    // On à terminé la lecture de l'URL, RAZ des variables
 +
    if (algoPart == 2) {
 +
      // On termine la chaîne
 +
      url_parts[nbUrlPart][readBytes] = '\0';
 +
      nbUrlPart++;
 +
    } else if (algoPart == 4) {
 +
      // On termine la chaîne
 +
      param_values[nbParam][readBytes] = '\0';
 +
      nbParam++;
 +
    }
 +
    algoPart = -1;
 +
    return true;
 +
  } else if (algoPart == 2) {
 +
    // Lecture des parties du chemin
 +
    if (c == '/') {
 +
      // On termine la chaîne
 +
      url_parts[nbUrlPart][readBytes] = '\0';
 +
      /**
 +
        On passe à la lecture de la partie suivante
 +
        si le nombre max de partie n'est pas atteint
 +
      */
 +
      if (nbUrlPart < URL_MAX_PART) {
 +
        nbUrlPart++;
 +
        readBytes = 0;
 +
      } else {
 +
        // Erreur 414 (Request-URI Too Long)
 +
        http_error_code = 414;
 +
        algoPart = -1;
 +
        return true;
 +
      }
 +
    } else if (c == '?') {
 +
      // On termine la chaîne
 +
      url_parts[nbUrlPart][readBytes] = '\0';
 +
      // On a terminé la lecture du chemin et il y a des paramètres
 +
      nbUrlPart++;
 +
      algoPart++;
 +
      readBytes = 0;
 +
    } else {
 +
      /**
 +
        On ajoute le caractère à la partie si on
 +
        a pas atteint le nombre max de caractères
 +
      */
 +
      if (readBytes <  URL_PART_SIZE) {
 +
        url_parts[nbUrlPart][readBytes++] = c;
 +
      } else {
 +
        // Erreur 413 (Request Entity Too Large)
 +
        http_error_code = 413;
 +
        algoPart = -1;
 +
        return true;
 +
      }
 +
    }
 +
  } else if (algoPart == 3) {
 +
    // Lecture des noms de paramètres
 +
    if (c == '=') {
 +
      // On termine la chaîne
 +
      param_names[nbParam][readBytes] = '\0';
 +
      // On passe à la lecture de la valeur
 +
      algoPart++;
 +
      readBytes = 0;
 +
    } else {
 +
      /**
 +
        On ajoute le caractère au nom si on
 +
        a pas atteint le nombre max de caractères
 +
      */
 +
      if (readBytes <  PARAM_SIZE) {
 +
        param_names[nbParam][readBytes++] = c;
 +
      } else {
 +
        // Erreur 413 (Request Entity Too Large)
 +
        http_error_code = 413;
 +
        algoPart = -1;
 +
        return true;
 +
      }
 +
    }
 +
  } else if (algoPart == 4) {
 +
    // Lecture des valeurs de paramètres
 +
    if (c == '&') {
 +
      // On termine la chaîne
 +
      param_values[nbParam][readBytes] = '\0';
 +
      /**
 +
        On passe à la lecture du nom du paramètre suivant
 +
        si le nombre max de paramètres n'est pas atteint
 +
      */
 +
      if (nbParam < PARAM_MAX_NUMBER) {
 +
        nbParam++;
 +
        algoPart--;
 +
        readBytes = 0;
 +
      } else {
 +
        // Erreur 414 (Request-URI Too Long)
 +
        http_error_code = 414;
 +
        algoPart = -1;
 +
        return true;
 +
      }
 +
    } else {
 +
      /**
 +
        On ajoute le caractère à la valeur si on
 +
        a pas atteint le nombre max de caractères
 +
      */
 +
      if (readBytes <  PARAM_SIZE) {
 +
        param_values[nbParam][readBytes++] = c;
 +
      } else {
 +
        // Erreur 413 (Request Entity Too Large)
 +
        http_error_code = 413;
 +
        algoPart = -1;
 +
        return true;
 +
      }
 +
    }
 +
  }
 +
  return false;
 +
}
 
</source>
 
</source>
 +
 
===fonctions annexes===
 
===fonctions annexes===
 
<source lang="c">
 
<source lang="c">

Version du 12 janvier 2017 à 08:00

Introduction

Warning manual.jpg

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

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 qui, dans notre cas de figure, 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 puis, 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

Le code devient trop complexe pour résider dans la fonction principale. Il faut donc le fragmenter en plusieurs fonctions qui seront appelées dans loop().

Algo

Prenons comme exemple la requête suivante :

GET /part1/part2/index.htm?paramA=valueA&...&paramX=valueX HTTP1.1

Nous allons écrire la fonction bool digestURL(char c) qui va :

  • vérifier que la ligne commence par GET ;
  • lire chaque partie du chemin (part1, part2, index.htm) ;
  • détecter le '?' pour déterminer si la requête contient des arguments ;
  • détecter les '=' pour faire la séparation entre nom de paramètre et valeur ;
  • détecter les '&' pour séparer les paramètres.

On par du principe que la fonction loop() appelle digestURL(char c) à la réception d'un caractère et renvoie vrai lorsque l'URL est lue.

variables globales

// Méthode démarrant le début de l'URL
static const char METHOD[] = "GET";
/**
  Arbitrairement on décide que le chemin
  contiendra 5 partie de 10 caractères
*/
static const uint8_t URL_PARTS = 5;
static const uint8_t URL_PARTS_SIZE = 10;
/**
  Arbitrairement on décide qu'il y aura
  maximum 5 paramètres de 10 caractères
*/
static const uint8_t PARAM_NUMBER = 5;
static const uint8_t PARAM_SIZE = 10;
// Tableau permettant de stocker les parties du chemin
char url_parts[URL_PARTS][URL_PARTS_SIZE];
// Tableau permettant de stocker les paramètres
char param_names[PARAM_NUMBER][PARAM_SIZE];
char param_values[PARAM_NUMBER][PARAM_SIZE];

loop()

#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 };
// Serveur écoutant sur le port 80
EthernetServer server(80);
// Méthode démarrant le début de l'URL
static const char METHOD[] = "GET";
/**
  Arbitrairement on décide que le chemin
  contiendra 5 partie de 10 caractères
*/
static const uint8_t URL_MAX_PART = 5;
static const uint8_t URL_PART_SIZE = 10;
/**
  Arbitrairement on décide qu'il y aura
  maximum 5 paramètres de 10 caractères
*/
static const uint8_t PARAM_MAX_NUMBER = 5;
static const uint8_t PARAM_SIZE = 10;
// Tableau permettant de stocker les parties du chemin
char url_parts[URL_MAX_PART][URL_PART_SIZE];
// Tableau permettant de stocker les paramètres
char param_names[PARAM_MAX_NUMBER][PARAM_SIZE];
char param_values[PARAM_MAX_NUMBER][PARAM_SIZE];

// Variables utilisée à l'exécution
uint16_t http_error_code = 0;
bool isUrl = false;
// Nombre de parties du chemin
uint8_t nbUrlPart = 0;
// Nombre de paramètres de l'URL
uint8_t nbParam = 0;

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 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() {
  // 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 (!isUrl && digestURL(c)) {
          isUrl = true;
          if (http_error_code != 0) {
            Serial.print(F("Error : "));
            Serial.println(http_error_code);
          } else {
            for (uint8_t i = 0; i < nbUrlPart; i++) {
              Serial.println(url_parts[i]);
            }
            for (uint8_t i = 0; i < nbParam; i++) {
              Serial.print(param_names[i]);
              Serial.print(F(" = "));
              Serial.println(param_values[i]);
            }
          }
        }
      } else {
        // Fin de la requête
        client.print(F("OK"));
        delay(1);
        client.stop();
        Serial.println(F(""));
        Serial.println(F("---- end request ----"));
      }
    }
    // RAZ des variables
    isUrl = false;
    http_error_code = 0;
    nbUrlPart = 0;
    nbParam = 0;
  }
}
/**
   Digère l'URL en séparant le chemin et les paramètres
*/
bool digestURL(char c) {
  static int8_t algoPart = 0, readBytes = 0;
  if (algoPart == -1) {
    // RAZ des variables
    algoPart = 0;
    readBytes = 0;
  }
  else if (algoPart == 0) {
    // On vérifie que le caractère lue correspond à un caractère de la méthode
    if (c == METHOD[readBytes++]) {
      if (readBytes == strlen(METHOD)) {
        // On vient de lire 'GET', on passe à la suite
        algoPart++;
        readBytes = 0;
      }
    } else {
      //On remet le compteur à zéro
      readBytes = 0;
    }
  } else if (algoPart == 1) {
    // On doit ignorer les 2 caractères qui suivent ' /'
    if (readBytes < 1) {
      readBytes++;
    } else {
      // Les deux caractères sont passés, on passe à la suite
      algoPart++;
      readBytes = 0;
    }
  } else if (c == ' ' || c == '\n') {
    // On à terminé la lecture de l'URL, RAZ des variables
    if (algoPart == 2) {
      // On termine la chaîne
      url_parts[nbUrlPart][readBytes] = '\0';
      nbUrlPart++;
    } else if (algoPart == 4) {
      // On termine la chaîne
      param_values[nbParam][readBytes] = '\0';
      nbParam++;
    }
    algoPart = -1;
    return true;
  } else if (algoPart == 2) {
    // Lecture des parties du chemin
    if (c == '/') {
      // On termine la chaîne
      url_parts[nbUrlPart][readBytes] = '\0';
      /**
         On passe à la lecture de la partie suivante
         si le nombre max de partie n'est pas atteint
      */
      if (nbUrlPart < URL_MAX_PART) {
        nbUrlPart++;
        readBytes = 0;
      } else {
        // Erreur 414 (Request-URI Too Long)
        http_error_code = 414;
        algoPart = -1;
        return true;
      }
    } else if (c == '?') {
      // On termine la chaîne
      url_parts[nbUrlPart][readBytes] = '\0';
      // On a terminé la lecture du chemin et il y a des paramètres
      nbUrlPart++;
      algoPart++;
      readBytes = 0;
    } else {
      /**
         On ajoute le caractère à la partie si on
         a pas atteint le nombre max de caractères
      */
      if (readBytes <  URL_PART_SIZE) {
        url_parts[nbUrlPart][readBytes++] = c;
      } else {
        // Erreur 413 (Request Entity Too Large)
        http_error_code = 413;
        algoPart = -1;
        return true;
      }
    }
  } else if (algoPart == 3) {
    // Lecture des noms de paramètres
    if (c == '=') {
      // On termine la chaîne
      param_names[nbParam][readBytes] = '\0';
      // On passe à la lecture de la valeur
      algoPart++;
      readBytes = 0;
    } else {
      /**
         On ajoute le caractère au nom si on
         a pas atteint le nombre max de caractères
      */
      if (readBytes <  PARAM_SIZE) {
        param_names[nbParam][readBytes++] = c;
      } else {
        // Erreur 413 (Request Entity Too Large)
        http_error_code = 413;
        algoPart = -1;
        return true;
      }
    }
  } else if (algoPart == 4) {
    // Lecture des valeurs de paramètres
    if (c == '&') {
      // On termine la chaîne
      param_values[nbParam][readBytes] = '\0';
      /**
         On passe à la lecture du nom du paramètre suivant
         si le nombre max de paramètres n'est pas atteint
      */
      if (nbParam < PARAM_MAX_NUMBER) {
        nbParam++;
        algoPart--;
        readBytes = 0;
      } else {
        // Erreur 414 (Request-URI Too Long)
        http_error_code = 414;
        algoPart = -1;
        return true;
      }
    } else {
      /**
         On ajoute le caractère à la valeur si on
         a pas atteint le nombre max de caractères
      */
      if (readBytes <  PARAM_SIZE) {
        param_values[nbParam][readBytes++] = c;
      } else {
        // Erreur 413 (Request Entity Too Large)
        http_error_code = 413;
        algoPart = -1;
        return true;
      }
    }
  }
  return false;
}

fonctions annexes

Utilisation de la carte SD

Affichage des paramètres de la carte

Lecture

Écriture

Ajout

Effacement