Différences entre versions de « Esp8266 udp server »

De The Linux Craftsman
Aller à la navigation Aller à la recherche
(Page créée avec « = Introduction= {|style="width:650px" align="center" | centré|300px |valign="top"| Soyez sûr de comprendre la section sur Arduino_sketch... »)
 
 
(16 versions intermédiaires par le même utilisateur non affichées)
Ligne 3 : Ligne 3 :
 
|
 
|
 
[[Fichier:Warning manual.jpg|centré|300px]]
 
[[Fichier:Warning manual.jpg|centré|300px]]
|valign="top"|
+
|valign="top" align="justify"|
 
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.
 
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.
De plus, il est impératif d'avoir configuré la puce comme [[Esp8266_wifi#Client_WiFiclient WiFi !]]
+
De plus, il est impératif d'avoir configuré la puce comme [[Esp8266_wifi#Client_WiFi |client WiFi]] !
 
|}
 
|}
  
Ligne 44 : Ligne 44 :
 
Connecte a e900 avec l'ip 192.168.1.188
 
Connecte a e900 avec l'ip 192.168.1.188
 
</pre>
 
</pre>
= Ajout d'un serveur UDP unicast =
+
= Serveur UDP unicast =
 
==Création du serveur==
 
==Création du serveur==
 
Nous allons maintenant ajouter la partie écoute en UDP.
 
Nous allons maintenant ajouter la partie écoute en UDP.
Ligne 60 : Ligne 60 :
 
// Tampon de réception
 
// Tampon de réception
 
char buffer[BUFFER_SIZE];
 
char buffer[BUFFER_SIZE];
 +
// Taille du paquet reçu;
 +
uint16_t len = 0;
 
// L'instance du serveur UDP
 
// L'instance du serveur UDP
 
WiFiUDP udp;
 
WiFiUDP udp;
 
</source>
 
</source>
A la fin de la fonction ''setup()'' nous allons ajouter la ligne suivante :
+
A la fin de la fonction ''setup()'' nous allons ajouter les lignes suivantes :
 
<source lang="c">
 
<source lang="c">
 
// Démarrage de l'écoute
 
// Démarrage de l'écoute
Ligne 70 : Ligne 72 :
 
Serial.println(PORT);
 
Serial.println(PORT);
 
</source>
 
</source>
 +
 
==Récupération et affichage des paquets==
 
==Récupération et affichage des paquets==
 
Pour récupérer et afficher le contenu du paquet, nous allons créer une fonction :
 
Pour récupérer et afficher le contenu du paquet, nous allons créer une fonction :
 
<source lang="c">
 
<source lang="c">
 
void readPacket() {
 
void readPacket() {
uint16_t len = udp.parsePacket();
+
len = udp.available();
 
Serial.print("Paquet de ");
 
Serial.print("Paquet de ");
 
Serial.print(len);
 
Serial.print(len);
Ligne 94 : Ligne 97 :
 
<source lang="c">
 
<source lang="c">
 
void loop() {
 
void loop() {
if (udp.available() > 0) {
+
if (udp.parsePacket() > 0) {
 
readPacket();
 
readPacket();
 
}
 
}
Ligne 101 : Ligne 104 :
 
Il est désormais possible, en utilisant un logiciel comme [https://packetsender.com/ Packet Sender] d'envoyer un paquet à notre ESP:
 
Il est désormais possible, en utilisant un logiciel comme [https://packetsender.com/ Packet Sender] d'envoyer un paquet à notre ESP:
 
[[Fichier:Send packet udp esp unicast.png|700px|centré]]
 
[[Fichier:Send packet udp esp unicast.png|700px|centré]]
Lorsque vous appuyer sur ''send'', vous devriez voir le message suivant s'afficher dans la console :
+
Lorsque vous appuyez sur ''send'', vous devriez voir le message suivant s'afficher dans la console :
 
<pre>
 
<pre>
 
Paquet de 11 octets recu de 192.168.1.1:62971 contenant 'Hello World'
 
Paquet de 11 octets recu de 192.168.1.1:62971 contenant 'Hello World'
 
</pre>
 
</pre>
== Envoie d'un paquet en UDP ==
+
 
 +
== Envoie d'un paquet UDP ==
 
Maintenant que nous recevons les paquets, il faudrait que nous puissions répondre !
 
Maintenant que nous recevons les paquets, il faudrait que nous puissions répondre !
 
Pour cela nous allons créer la fonction suivante :
 
Pour cela nous allons créer la fonction suivante :
Ligne 115 : Ligne 119 :
 
}
 
}
 
</source>
 
</source>
Pour créer un serveur ''echo'', après la ligne  
+
Pour créer un serveur ''echo'', après la ligne:
 
<source lang="c">
 
<source lang="c">
 
readPacket();
 
readPacket();
 
</source>
 
</source>
Il nous suffit d'ajouter
+
Il nous suffit d'ajouter:
 
<source lang="c">
 
<source lang="c">
 
sendPacket(buffer, udp.remoteIP(), udp.remotePort());
 
sendPacket(buffer, udp.remoteIP(), udp.remotePort());
 
</source>
 
</source>
 +
On a maintenant une réponse de l'ESP:
 +
[[Fichier:Send packet udp esp unicast response.png|700px|centré]]
 +
 +
= Serveur UDP multicast =
 +
Le [https://fr.wikipedia.org/wiki/Multicast multicast] permet de créer des groupes de diffusions, très pratiques pour parler à plusieurs équipements en ayant à envoyer qu'un seul paquet.
 +
 +
 +
Pour commencer, nous allons ajouter dans la partie des ''variables statiques'' les lignes suivantes :
 +
<source lang="c">
 +
// Adresse IP multicast
 +
const IPAddress IP_MCAST(239, 0, 0, 50);
 +
</source>
 +
Ensuite, dans la fonction ''setup()'', nous allons remplacer les lignes suivantes :
 +
<source lang="c">
 +
// Démarrage de l'écoute
 +
udp.begin(PORT);
 +
Serial.print("Démarrage de l'ecoute sur le port ");
 +
Serial.println(PORT);
 +
</source>
 +
par les lignes :
 +
<source lang="c">
 +
// Démarrage de l'écoute en multicast
 +
udp.beginMulticast(WiFi.localIP(), IP_MCAST, PORT);
 +
Serial.print("Demarrage de l'ecoute sur les adresses ");
 +
Serial.print(WiFi.localIP());
 +
Serial.print(" et ");
 +
Serial.print(IP_MCAST);
 +
Serial.print(" sur le port ");
 +
Serial.println(PORT);
 +
</source>
 +
On peut maintenant envoyer un message sur n'importe laquelle des deux adresses:
 +
[[Fichier:Send packet udp esp multicast.png|700px|centré]]
 +
= Pour aller plus loin =
 +
La souscription à un groupe ''multicast'' devient nécessaire lorsque certains appareils ''parlent'' le même langage (protocole) car cela leurs permet de se reconnaître entre eux en échangeant des messages respectant un certain format. On peut prendre comme exemple les protocoles [https://fr.wikipedia.org/wiki/Cisco_Discovery_Protocol ''CDP''] ou [https://fr.wikipedia.org/wiki/Open_Shortest_Path_First#Paquets_OSPF_et_LSA ''OSPF''].
 +
C'est une alternative plus intéressante que le ''broadcast'' car cela permet de cibler uniquement les équipements parlant le même langage et donc intéressés par le message.
 +
== Définition des messages ==
 +
Comme dans l'élaboration de n'importe quel protocole, avant d'aller plus loin, il faut commencer par définir la sémantique des messages:
 +
* Les commandes :
 +
** HELO &rarr; permet de connaître l'adresse ''unicast'' de tous les intervenants;
 +
** SUBSW.X.Y.Z &rarr; ordonne à un équipement de souscrire au groupe ''multicast'' W.X.Y.Z;
 +
** EXIT &rarr; ordonne à un équipement de quitter le groupe ''multicast'' auquel il est abonné;
 +
* Les acquittements :
 +
** ACK &rarr; acquittement généré suite à la réussite d'une commande;
 +
** NACK &rarr; acquittement généré suite à l'échec d'une commande;
 +
== Définition des adresses ==
 +
Une fois les messages définis, il reste maintenant à décider d'une adresse ''multicast'' ou les équipements vont se retrouver.
 +
 +
D'après l'[https://www.iana.org/ IANA] le groupe ''multicast'' à utiliser sur un réseau local doit se trouver dans la plage ''239.0.0.0/8'', nous pouvons donc conserver l'adresse ''239.0.0.50'' comme adresse ''multicast'' de découverte. Sur cette adresse, seule les paquets ''HELO'' sont autorisés et sur les autres adresses, tous les paquets sont autorisés. C'est généralement de la sorte que l'on procède, les messages génériques comme HELO sont acceptés sur toutes les interfaces et les messages plus spécifiques sont reçu en ''unicast'' ou sur un groupe ''multicast'' restreint.
 +
 +
== Implémentation du protocole ==
 +
La première étape consiste à définir des variables statiques suivantes:
 +
<source lang="c">
 +
// Commande 'HELO'
 +
const char CMD_HELO[] = "HELO";
 +
// Commande 'subscribe'
 +
const char CMD_SUBS[] = "SUBS";
 +
// Commande 'EXIT'
 +
const char CMD_EXIT[] = "EXIT";
 +
// Contenu ACK
 +
const char CONTENT_ACK[] = "ACK";
 +
// Contenu NACK
 +
const char CONTENT_NACK[] = "NACK";
 +
</source>
 +
Nous allons ensuite créer les fonctions suivantes pour :
 +
* envoyer un acquittement positif à l’émetteur
 +
<source lang="c">
 +
void sendBackAck() {
 +
sendPacket(CONTENT_ACK, udp.remoteIP(), udp.remotePort());
 +
}
 +
</source>
 +
* envoyer un acquittement négatif à l’émetteur
 +
<source lang="c">
 +
void sendBackAck() {
 +
sendPacket(CONTENT_NACK, udp.remoteIP(), udp.remotePort());
 +
}
 +
</source>
 +
* se désabonner d'un groupe ''multicast''
 +
<source lang="c">
 +
void unsubscribe() {
 +
// On arrête toutes les écoutes
 +
udp.stopAll();
 +
}
 +
</source>
 +
* s'abonner à un groupe ''multicast''
 +
<source lang="c">
 +
void subscribe(IPAddress mip) {
 +
udp.beginMulticast(WiFi.localIP(), mip, PORT);
 +
Serial.print("Ecoute multicast l'adresses ");
 +
Serial.print(mip);
 +
Serial.print(" sur le port ");
 +
Serial.println(PORT);
 +
}
 +
</source>
 +
Il faut maintenant modifier la fonction ''readPacket()'' pour interpréter les messages:
 +
<source lang="c">
 +
void readPacket() {
 +
// Récupération de la taille du paquet
 +
len = udp.available();
 +
// Mise en tampon du paquet
 +
udp.read(buffer, len);
 +
// Ajout du terminateur de chaîne
 +
buffer[len] = '\0';
 +
if (strcmp(buffer, CMD_HELO) == 0) {
 +
// Commande 'HELO', réponse en envoyant un ACK
 +
sendBackAck();
 +
} else if (udp.destinationIP() != IP_MCAST) {
 +
// La paquet n'est pas à destination de l'adresse de découverte
 +
if (strcmp(buffer, CMD_EXIT) == 0) {
 +
// Commande 'EXIT', désinscription du groupe et ACK
 +
sendBackAck();
 +
// On arrête toutes les écoutes
 +
unsubscribe();
 +
// On démarre l'écoute sur le groupe de découverte
 +
subscribe(IP_MCAST);
 +
} else if (strncmp(buffer, CMD_SUBS, 4) == 0) {
 +
// Commande 'subscribe'
 +
IPAddress mip;
 +
// Test de validité de l'adresse
 +
if (mip.fromString(buffer + strlen(CMD_SUBS))) {
 +
sendBackAck();
 +
// On arrête toutes les écoutes
 +
unsubscribe();
 +
// On démarre l'écoute sur le groupe de découverte
 +
subscribe(IP_MCAST);
 +
// On démarre l'écoute sur le nouveau groupe
 +
subscribe(mip);
 +
} else {
 +
sendBackNack();
 +
}
 +
}
 +
} else {
 +
// Commande non reconnue
 +
sendBackNack();
 +
}
 +
}
 +
</source>
 +
 +
== Mise en pratique ==
 +
===Découverte===
 +
La premier test est celui du paquet 'HELO' fait sur le réseau ''multicast'' de découverte :
 +
[[Fichier:Multicast HELO packet.png|700px|centré]]
 +
On voit que tous les deux équipements présents sur le groupe ont répondu !
 +
===Écoute sur un autre groupe===
 +
Dans un premier temps, on peut s'assurer du faite que les paquets ''SUBS'' ne fonctionne pas sur le réseau de découverte:
 +
[[Fichier:Multicast SUBS discovery packet.png|700px|centré]]
 +
On a bien la réponse ''NACK'' attendue.
 +
 +
Testons maintenant sur une adresse ''unicast'':
 +
[[Fichier:Multicast SUBS unicast packet.png|700px|centré]]
 +
et sur le port série on a :
 +
<pre>
 +
Ecoute multicast sur l'adresses 239.0.0.50 sur le port 4321
 +
Ecoute multicast sur l'adresses 239.0.0.25 sur le port 4321
 +
</pre>
 +
 +
==Désinscription==
 +
La désinscription marche sur le même principe, elle ne fonctionnera qu'en ''unicast'' ou sur le groupe ''multicast'' restreint ''239.0.0.25'' :
 +
[[Fichier:Multicast EXIT unicast packet.png|700px|centré]]
 +
et sur le port série on a :
 +
<pre>
 +
Ecoute multicast sur l'adresses 239.0.0.50 sur le port 4321
 +
</pre>

Version actuelle datée du 22 février 2018 à 19:43

Introduction

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. De plus, il est impératif d'avoir configuré la puce comme client WiFi !

Dans cet exemple, nous allons créer un serveur UDP qui va écouter les messages en unicast et multicast

Connexion au réseau

Commençons par nous connecter à un réseau en utilisant le DHCP :

#include <ESP8266WiFi.h>

const char ssid[] = "e900";
const char password[] = "********";

void setup() {
	// on démarre le port série
	Serial.begin(115200);
	// On attend "un peu" que le buffer soit prêt
	delay(10);
	// On efface la configuration précédente
	WiFi.disconnect(true);
	// Initialisation de la connection
	WiFi.begin(ssid, password);
	// Test pour déterminer quand la connection est prete
	while (WiFi.status() != WL_CONNECTED) {
		delay(500);
	}
	// Affichage des informations
	Serial.print("\nConnecte a ");
	Serial.print(ssid);
	Serial.print(" avec l'ip ");
	Serial.println(WiFi.localIP());
}
void loop() {
}

Le code précédent vous donne le résultat suivant sur le port série :

Connecte a e900 avec l'ip 192.168.1.188

Serveur UDP unicast

Création du serveur

Nous allons maintenant ajouter la partie écoute en UDP.

Dans la partie des imports ajoutez la ligne suivante :

#include <WiFiUDP.h>

Dans la partie des variables statiques ajoutez les lignes suivantes :

// Port d'écoute UDP
const uint16_t PORT = 4321;
// Taille du tampon de réception
const uint16_t BUFFER_SIZE = 512;
// Tampon de réception
char buffer[BUFFER_SIZE];
// Taille du paquet reçu;
uint16_t len = 0;
// L'instance du serveur UDP
WiFiUDP udp;

A la fin de la fonction setup() nous allons ajouter les lignes suivantes :

// Démarrage de l'écoute
udp.begin(PORT);
Serial.print("Démarrage de l'ecoute sur le port ");
Serial.println(PORT);

Récupération et affichage des paquets

Pour récupérer et afficher le contenu du paquet, nous allons créer une fonction :

void readPacket() {
	len = udp.available();
	Serial.print("Paquet de ");
	Serial.print(len);
	Serial.print(" octets recu de ");
	Serial.print(udp.remoteIP());
	Serial.print(":");
	Serial.print(udp.remotePort());
	Serial.print(" contenant '");
	// Mise en tampon du paquet
	udp.read(buffer, len);
	// Affichage du contenu du paquet
	for (int i = 1; i <= len; i++) {
		Serial.print(buffer[i - 1]);
	}
	Serial.println("'");
}

Que nous allons appeler dans la fonction loop():

void loop() {
	if (udp.parsePacket() > 0) {
		readPacket();
	}
}

Il est désormais possible, en utilisant un logiciel comme Packet Sender d'envoyer un paquet à notre ESP:

Send packet udp esp unicast.png

Lorsque vous appuyez sur send, vous devriez voir le message suivant s'afficher dans la console :

Paquet de 11 octets recu de 192.168.1.1:62971 contenant 'Hello World'

Envoie d'un paquet UDP

Maintenant que nous recevons les paquets, il faudrait que nous puissions répondre ! Pour cela nous allons créer la fonction suivante :

void sendPacket(const char content[], IPAddress ip, uint16_t port) {
	udp.beginPacket(ip, port);
	udp.write(content);
	udp.endPacket();
}

Pour créer un serveur echo, après la ligne:

readPacket();

Il nous suffit d'ajouter:

sendPacket(buffer, udp.remoteIP(), udp.remotePort());

On a maintenant une réponse de l'ESP:

Send packet udp esp unicast response.png

Serveur UDP multicast

Le multicast permet de créer des groupes de diffusions, très pratiques pour parler à plusieurs équipements en ayant à envoyer qu'un seul paquet.


Pour commencer, nous allons ajouter dans la partie des variables statiques les lignes suivantes :

// Adresse IP multicast
const IPAddress IP_MCAST(239, 0, 0, 50);

Ensuite, dans la fonction setup(), nous allons remplacer les lignes suivantes :

// Démarrage de l'écoute
udp.begin(PORT);
Serial.print("Démarrage de l'ecoute sur le port ");
Serial.println(PORT);

par les lignes :

// Démarrage de l'écoute en multicast
udp.beginMulticast(WiFi.localIP(), IP_MCAST, PORT);
Serial.print("Demarrage de l'ecoute sur les adresses ");
Serial.print(WiFi.localIP());
Serial.print(" et ");
Serial.print(IP_MCAST);
Serial.print(" sur le port ");
Serial.println(PORT);

On peut maintenant envoyer un message sur n'importe laquelle des deux adresses:

Send packet udp esp multicast.png

Pour aller plus loin

La souscription à un groupe multicast devient nécessaire lorsque certains appareils parlent le même langage (protocole) car cela leurs permet de se reconnaître entre eux en échangeant des messages respectant un certain format. On peut prendre comme exemple les protocoles CDP ou OSPF. C'est une alternative plus intéressante que le broadcast car cela permet de cibler uniquement les équipements parlant le même langage et donc intéressés par le message.

Définition des messages

Comme dans l'élaboration de n'importe quel protocole, avant d'aller plus loin, il faut commencer par définir la sémantique des messages:

  • Les commandes :
    • HELO → permet de connaître l'adresse unicast de tous les intervenants;
    • SUBSW.X.Y.Z → ordonne à un équipement de souscrire au groupe multicast W.X.Y.Z;
    • EXIT → ordonne à un équipement de quitter le groupe multicast auquel il est abonné;
  • Les acquittements :
    • ACK → acquittement généré suite à la réussite d'une commande;
    • NACK → acquittement généré suite à l'échec d'une commande;

Définition des adresses

Une fois les messages définis, il reste maintenant à décider d'une adresse multicast ou les équipements vont se retrouver.

D'après l'IANA le groupe multicast à utiliser sur un réseau local doit se trouver dans la plage 239.0.0.0/8, nous pouvons donc conserver l'adresse 239.0.0.50 comme adresse multicast de découverte. Sur cette adresse, seule les paquets HELO sont autorisés et sur les autres adresses, tous les paquets sont autorisés. C'est généralement de la sorte que l'on procède, les messages génériques comme HELO sont acceptés sur toutes les interfaces et les messages plus spécifiques sont reçu en unicast ou sur un groupe multicast restreint.

Implémentation du protocole

La première étape consiste à définir des variables statiques suivantes:

// Commande 'HELO'
const char CMD_HELO[] = "HELO";
// Commande 'subscribe'
const char CMD_SUBS[] = "SUBS";
// Commande 'EXIT'
const char CMD_EXIT[] = "EXIT";
// Contenu ACK
const char CONTENT_ACK[] = "ACK";
// Contenu NACK
const char CONTENT_NACK[] = "NACK";

Nous allons ensuite créer les fonctions suivantes pour :

  • envoyer un acquittement positif à l’émetteur
void sendBackAck() {
	sendPacket(CONTENT_ACK, udp.remoteIP(), udp.remotePort());
}
  • envoyer un acquittement négatif à l’émetteur
void sendBackAck() {
	sendPacket(CONTENT_NACK, udp.remoteIP(), udp.remotePort());
}
  • se désabonner d'un groupe multicast
void unsubscribe() {
	// On arrête toutes les écoutes
	udp.stopAll();
}
  • s'abonner à un groupe multicast
void subscribe(IPAddress mip) {
	udp.beginMulticast(WiFi.localIP(), mip, PORT);
	Serial.print("Ecoute multicast l'adresses ");
	Serial.print(mip);
	Serial.print(" sur le port ");
	Serial.println(PORT);
}

Il faut maintenant modifier la fonction readPacket() pour interpréter les messages:

void readPacket() {
	// Récupération de la taille du paquet
	len = udp.available();
	// Mise en tampon du paquet
	udp.read(buffer, len);
	// Ajout du terminateur de chaîne
	buffer[len] = '\0';
	if (strcmp(buffer, CMD_HELO) == 0) {
		// Commande 'HELO', réponse en envoyant un ACK
		sendBackAck();
	} else if (udp.destinationIP() != IP_MCAST) {
		// La paquet n'est pas à destination de l'adresse de découverte
		if (strcmp(buffer, CMD_EXIT) == 0) {
			// Commande 'EXIT', désinscription du groupe et ACK
			sendBackAck();
			// On arrête toutes les écoutes
			unsubscribe();
			// On démarre l'écoute sur le groupe de découverte
			subscribe(IP_MCAST);
		} else if (strncmp(buffer, CMD_SUBS, 4) == 0) {
			// Commande 'subscribe'
			IPAddress mip;
			// Test de validité de l'adresse
			if (mip.fromString(buffer + strlen(CMD_SUBS))) {
				sendBackAck();
				// On arrête toutes les écoutes
				unsubscribe();
				// On démarre l'écoute sur le groupe de découverte
				subscribe(IP_MCAST);
				// On démarre l'écoute sur le nouveau groupe
				subscribe(mip);
			} else {
				sendBackNack();
			}
		}
	} else {
		// Commande non reconnue
		sendBackNack();
	}
}

Mise en pratique

Découverte

La premier test est celui du paquet 'HELO' fait sur le réseau multicast de découverte :

Multicast HELO packet.png

On voit que tous les deux équipements présents sur le groupe ont répondu !

Écoute sur un autre groupe

Dans un premier temps, on peut s'assurer du faite que les paquets SUBS ne fonctionne pas sur le réseau de découverte:

Multicast SUBS discovery packet.png

On a bien la réponse NACK attendue.

Testons maintenant sur une adresse unicast:

Multicast SUBS unicast packet.png

et sur le port série on a :

Ecoute multicast sur l'adresses 239.0.0.50 sur le port 4321
Ecoute multicast sur l'adresses 239.0.0.25 sur le port 4321

Désinscription

La désinscription marche sur le même principe, elle ne fonctionnera qu'en unicast ou sur le groupe multicast restreint 239.0.0.25 :

Multicast EXIT unicast packet.png

et sur le port série on a :

Ecoute multicast sur l'adresses 239.0.0.50 sur le port 4321