1°/ Wifi
- les esp8266/32 intègre nativement un support bluetooth et wifi, ce qui n'est pas le cas de l'ATmega328
- De plus, ils peuvent être programmés pour agir comme un client wifi, un point d'accès (AP) wifi, ou bien les deux :
- Un client se connecte à un AP et reçoit en retour une adresse IP,
- Un AP fournit des connexions Wifi et des IPs aux clients, mais n'a pas forcément d'IP
- La combinaison des eux permet à l'AP d'avoir une IP et de partciper aux communications entre µC.
- Un sketch nécessitant d'utiliser le Wifi doit forcément inclure le fichier .h contenant les déclarations des fonctions/classes/constantes/... liées au Wifi.
- Malheureusement, le nom de ce fichier n'est pas standardisé et il change selon le µC.
- Par exemple, pour un arduino et esp32, il faut inclures WiFi.h, et pour les esp8266, c'est ESP8266WiFi.h
1.1°/ client Wifi
- Généralement, on écrit une fonction qui sera appelée dans setup().
- Cette fonction essaie à l'infini d'établir une connexion à un point d'acces dont on connait le SSID et le mot de passe.
Exemple (esp8266) :
#include <ESP8266WiFi.h>
...
void setupWifi() {
const char *ssid = "ssid_wifi_ap"; // the ssid of the AP
const char *password = "pass_ap"; // the password of the AP
// comment 2 following lines if not needed
WiFi.setAutoConnect(false); // see comments
WiFi.setSleepMode(WIFI_NONE_SLEEP); // always active => big consumption
WiFi.mode (WIFI_STA); // setup as a wifi client
WiFi.begin(ssid,password); // try to connect
while (WiFi.status() != WL_CONNECTED) { // check connection
delay(500);
Serial.print(".");
}
// debug messages
Serial.print("Connected, IP address: ");
Serial.println(WiFi.localIP());
}
Remarques :
- setAutoConnect(false) permet d'éviter que le µC se connecte automatiquement au dernier AP qu'il a utilisé. En effet, ses informations sont stockées en mémoire flash et par défaut, le µC tente lors du boot de s'y connecter. Au mieux, cela ralentit la connexion à un nouvel AP, mais dans certains cas, cela empêche totalement cette connexion. Il est donc conseillé de désactiver cette connexion automatique quand le µC est nomde et va souvent changer d'AP. On la laisse active uniquement s'il se connecte toujours au même AP.
- setSleepMode(WIFI_NONE_SLEEP) permet de garder le circuit Wifi toujours actif. Ce n'est pas le mode par défaut où ce circuit peut parfois être mis en pause. Ces pauses impliquent que des réceptions peuvent être manquées par le µC, ce qui nécessite de renvoyer les données. Dans certaines applications, cette réémission n'est pas acceptable, d'où la fait de garder le wifi toujours actif. En revanche, ce mode consomme beaucoup d'énergie électrique ce qui pose problème si le µC est alimenté sur batterie/pile (cf. section sur la consommation)
1.2°/ Point d'accès + client Wifi access
- Comme un AP agit comme un serveur DHCP, il faut obligatoirement fixer un adresse et un masque de sous-réseau, ainsi qu'une passerelle pour que l'AP soit en mesure d'attribuer des IPs pour ce sous-réseau.
- Dans l'exemple ci-dessous, on suppose que l'AP fournit des adresses de classe C, comme 192.168.0.XXX
- Il faut également définir un canal Wifi.
Apparté :
- La norme Wifi 2.4GHz définit 13 canaux, correspondant à 13 fréquences autour de 2.4GHz.
- Si deux AP sont proches l'un de l'autre, cela risque de provoquer des interférences s'ils utilisent des fréquences trop proche. Dans ce cas, des paquets de données sont corrompus et doivent être rémis.
- Pour régler ce type de problème, on utilise des canaux séparé d'au moins 4 en valeur.
- Par exemple, dans une même pièce, on peut mettre jusqu'à 4 AP, avec comme canaux 1, 5, 9 et 13.
Exemple (esp8266) :
#include <ESP8266WiFi.h>
...
void setupAP() {
IPAddress local_IP(192,168,0,1);
IPAddress gateway(192,168,0,1);
IPAddress subnet(255,255,255,0);
WiFi.mode(WIFI_AP_STA); // AP + client
int channel = 1; // choose between 1 and 13
const char *ssid = "ssid_wifi_ap";
const char *password = "pass_ap";
// CAUTION: on some µC, 2 following lines may be exchanged to work
WiFi.softAP(ssid, password, channel);
WiFi.softAPConfig(local_IP, gateway, subnet);
// debug messages
IPAddress myIP = WiFi.softAPIP();
Serial.print("AP IP address: ");
Serial.println(myIP);
}
Remarques :
- en tant qu'AP, un esp8266/32 peut accepter au maximum 4 clients, mais il est déconseillé d'atteindre ce nombre, surtout si l'AP est lui-même un client est doit faire quelque chose d'autre. Dans ce cas, le µC peut vite être surchargé et planter.
- il est donc conseillé d'utiliser un µC comme AP uniquement dans des applications où seulement 2 µC "nomades" doivent communiquer.
2°/ Connexion et communication TCP
- Une fois qu'un µC possède une IP, il est possible de créer une connexion TCP
- A µC can act as a TCP client, or a TCP server., soit comme un client, soit comme un serveur.
2.1°/ client TCP
- Généralement, on écrit une fonction chargée de demander la connexion et qui stocke l'état de cette connexion das une variable globale.
- Comme il est fréquent qu'une connexion TCP soit interrompue, cette fonction est plutôt appelée si nécessaire au début de la fonction loop(), pour être sur de pouvoir communiquer dans la suite de l'exécution.
- Cette fonction doit utiliser une instance de la classe WiFiClient, qui contient une méthode connect() pour demander la connexion.
- Cette classe contient également des méthodes :
- available() pour tester si des octets peuvent être lus,
- write() pour écrire 1 seul octet,
- read() pour lire un seul octet,
- println() pour écrire une ligne de texte, transformée en une suite d'octets avant d'être envoyée.
- Malheureusement, il n'existe pas de fonction pour lire directement une ligne de texte. Il faut l'écrire soi-même.
Exemple (esp32) :
#include <WiFi.h>
...
WiFiClient client;
byte connState;
const char* ipServ = "192.168.0.1";
int portServ = 12345;
byte buf[1024];
int idx = 0;
...
String readLine() {
char buffer[1024];
memset(buffer,0,1024); // reset buffer
int index = 0;
char c;
while(true) {
while (!client.available()) {} // wait for smthg to read
while ((index < 1023) && (client.available())) {
c = client.read();
if (c == '\n') return String(buffer); // end-of-line reached => return
buffer[index++] = c; // store the char
}
// prevent buffer overflow: return the whole buffer even if no \n encountered
if (index == 1023) return String(buffer);
}
}
void setup() {
Serial.begin(115200);
connState = 0;
// establish the wifi connection
...
}
bool doConnection() {
if (client.connect(ipServ, portServ)) {
connState = 1;
Serial.println("connected to server")
// uncomment next line for esp8266: disable buffering small mesg
// client.setNoDelay(true);
return true;
}
else {
connState = 0;
Serial.println("connection failed")
return false;
}
void loop() {
if (connState == 0) {
if (! doConnection()) { delay(1000); }
}
if (connState == 1) {
client.write(10); // write a byte equal to 10
client.println("10"); // write 3 bytes: ascii code of 1, ascii code of 0, ascii code of new_line
// wait something from the server
while (!client.available()) {} // see comments below
// wait something from the server
String msg = readLine();
Serial.println(msg);
}
Remarques :
- contrairement à un OS classique, lire sur une socket grâce à la fonction WiFiClient.read() n'est pas bloquant. S'il n'y a rien à lire, la fonction renvoie donc une valeur invalide.
- C'est pourquoi il faut toujours vérifier que quelque chose est disponible AVANT de lire. Pour cela, on utilise available().
- Cependant, il est possible d'attendre qu'au moins un octet soit disponible à la lecture avec une boucle "bizarre" : while(!client.available()) {}. Sur un processeur classique, cela ne peut pas se faire sinon on le charge à 100%. Mais sur un µC, cela fonctionne parfaitement et c'est même la seule façon de faire.
2.2°/ serveur TCP
- Pour attendre une connexion, le µC doit utiliser une instance de WiFiServer, dont le constructeur permet de fixer le port de connexion.
- Cette classe contient une méthode available() qui teste s'il y a une demande de connexion client.
- Attention : cette fonction n'est pas bloquante comme sur un OS classique. Elle renvoie directement null si aucune demande n'est faite. Il faut donc l'appeler en boucle.
- Dès qu'une demande est accepté, cette fonction renvoie un objet instance de WiFiClient qui permet de communiquer avec le client, comme vu en section 7.1
Exemple (esp32) :
#include <WiFi.h>
...
WiFiServer server(12345);
WiFiClient client;
byte connState;
byte buf[1024];
int idx = 0;
...
void setup() {
Serial.begin(115200);
connState = 0;
// establish the wifi connection
...
}
void waitConnection() {
client = server.available(); // test if there is an incoming connection and if yes, accept it.
if (client) {
connState = 1;
Serial.println("client connected")
// special for esp8266: disable buffering small mesg
client.setNoDelay(true);
}
}
void loop() {
if (connState == 0) {
waitConnection();
}
if (connState == 1) {
// wait something from the client
while (!client.available()) {}
// example on how to read what is available in a simple buffer
while (client.available()) {
buf[idx++] = client.read(); // read & store a byte
}
Serial.println(String(buf)); // print the content of buf as a string
for(int i=0;i<idx;i++) buf[i] = 0; // reset buf
}
}
8°/ A propos de la consommation électrique
- Utiliser le wifi (et/ou le bluetooth) sur un µC est ce qui consomme le plus d'énergie électrique.
- Par exemple, envoyer des données en Wifi sur un ESP32 soutire jusqu'à 250mA de la source d'alimentation, et environ 150mA en réception.
- Si le circuit Wifi est actif mais non communicant, le courant soutiré est entre 80 et 100mA.
- Ce ne sont pas des valeurs énormes mais elles sont tout de même suffisamment élevées pour poser des problèmes d'alimentation quand le µC fonctionne sur batterie/pile.
- Par exemple, une batterie 1000mAh est capable de délivrer 1A pendant une heure, ou bien 100mA pendant 10h.
- Cela veut dire qu'une telle batterie tiendrait seulement 1000/250 = 4 heures pour alimenter un µC constamment en train d'envoyer des données sur le Wifi.
- Et même si le µC n'envoie des données que quelques fois par jour, le fait d'avoir le wifi actif ne permettrait de tenir qu'au maximum 10h.
- Ce n'est évidemment pas applicable. Une solution de maison intelligente basée sur des boîtiers à µC, qu'il faut recharger toutes les 10h n'aurait pas beaucoup de succès.
- Il faut donc mettre en place des stratégies de réduction de la consommation.
- La première solution est de mettre régulièrement en pause le circuit wifi et de le réveiller juste le temps nécessaire pour vérifier s'il y a des données à recevoir depuis l'AP.
- Ce mode s'appelle "modem sleep" et c'est le mode par défaut de fonctionnement du circuit wifi sur un esp8266/32.
- Ce mode est possible grâce au fait qu'un AP émet régulièrement des trames dites de "balise" pour synchroniser le réseau (par ex tous les 100ms). Toutes les X trames de balise, la trame va contenir une information appelée DTIM (Delivery Traffic Information Map), qui permet à un client wifi de savoir s'il y a des données en attente sur l'AP pour lui. La trame contient également le temps avant la prochaine DTIM.
- Par conséquent, il suffit au µC de couper le circuit wifi entre deux trames contenant un DTIM et de se réveiller juste avant que la prochaine trame arrive.
- Avec cette "astuce", la consommation tombe en moyenne à 20mA, mais cela dépend du X (=nb balises entre 2 DTIM) qui est fixé par l'AP.
- En revanche, plus X est grand plus la latence de réception sera grande, ce qui peut poser problème à certaines applications où l'on a besoin d'une latence de communication la plus faible possible entre l'émetteur et le récepteur.
- Et si X est trop grand, il peut même y avoir des problèmes pour conserver la connexion wifi entre les client et l'AP.
- Malheureusement, même cette stratégie n'est pas très efficace puisqu'avec une batterie 1000mAh, il faudrait recharger au bout de 1000/20 = 50 heures. Pas terrible !
- Pour vraiment réduire la consommation, il faut passer à des modes beaucoup plus contraignants et qui coupent l'alimentation de plus ou moins de parties du µC.
- Ces modes ne sont utilisables que pour des applications où les µC envoient de façon sporadiques et qu'il n'y a quasi rien à faire entre deux communications. De plus, ce n'est pas adapté pour les cas où un µC doit pouvoir recevoir n'importe quand des données. C'est notamment le cas d'une solution où les µC se contentent de mesurer une grandeur, d'envoyer la valeur lue à un serveur, d'attendre sa réponse, et enfin s'endormir pour un temps fixe.
- Sur un esp8266/32, il y a principalement 3 modes :
- light-sleep : le CPU, l'horloge, et le wifi sont en pause mais l'état du CPU est mis en RAM. Il est possible de réveiller le CPU via un changement d'état sur une GPIO, connectée par exemple à un bouton poussoir. Dans ce mode, la consommation tombe à environ 1mA, et l'exécution reprend là où elle s'était arrêtée. En revanche, le µC ne peut rien faire pendant l'attente.
- deep-sleep : presque tout est arrêté excepté le circuit RTC (Real Time Clock) qui permet de compter le temps qui passe. Grâce à la RTC, le µC peut être programmé pour se réveiller au bout d'un temps paramétrable. On peut aussi le réveiller ĝrace à une GPIO. Cependant, contrairement au light-sleep, le µC reboote après le réveil et donc recommence entièrement le code du sketch. Dans ce mode, la consommation tombe à environ 10µA.
- hibernation : tout est arrêté et le µC ne redémarre qu'avec un changement sur une GPIO précise (GPIO16 sur l'esp8266). Dans ce mode, la consommation tombe à 2µA.
- ATTENTION : les consommations annoncées ci-dessus ne tiennent compte que du µC seul. La consommation réelle est plus élevée, notamment à cause des composants actifs de la carte de développement : convertisseur série-usb, régulateur de tension, .... Par exemple, il est courant d'avoir une consommation réelle entre 50 et 100µA en deep-sleep.
- A part ces 3 modes, il est également possible de complètement couper le circuit wifi s'il n'est pas nécessaire avec : WiFi.mode(WIFI_OFF)
- Au contraire, pur qu'il soit toujours actif et avoir une latence minimale, on utilise : WiFi.setSleepMode(WIFI_NONE_SLEEP)