ESP32 et UDP – Transmission locale en temps réel

Je souhaite, à partir de la souris de mon PC, diriger un curseur sur un petit système embarqué peu cher. Pour cela, je choisis une carte ESP32 équipée du WiFi et d’un écran (TTGO T Display) à moins de 10€.

La transmission sera de type client serveur. Elle se fera en UDP. Nous utiliserons les IDE Processing et Arduino ainsi que les librairies suivantes :

  • IDE Arduino 1.8.12 :
    • Gestionnaire de carte : esp32 1.0.4
    • Librairie AsyncUDP (UDP asynchrone pour ESP32)
    • Librairie TFT_eSPI 2.2.0 (Gestion de l’écran TFT)
    • Librairie Button2 1.1.0 (Gestion des boutons)

Principe

Ici, le but est de créer un IoT capable de traiter en temps réel (latence faible) un flux rapide de données (60Hz au moins). Un tel IoT est notamment utile pour des applications robotiques ou interactives.


Relation client serveur

Schématisation du système complet

Le client sera ici le logiciel Processing. Il récupérera la position xy de la souris et la renverra au serveur UDP. Les trames transmises seront simples et de la forme :

xxx yyy\0.

Le serveur, lui, positionnera sur son écran un carré à la position xy indiquée par le client en temps réel (60 Hz). reproduira en temps réel et sur son écran la position de la souris, d’ou le choix d’une carte ESP32 équipée d’un écran (TTGO T Display).

Le routeur, enfin, sert de relais entre les deux


Transmission UDP asynchrone local

Le protocole UDP est un des protocoles de base d’un réseau Ethernet avec le TCP. Facile à mettre en œuvre :

  • il offre une connexion point à point entre deux hôtes
  • il est moins lourd que le TCP

Un server asynchrone est un serveur dont les fonctions sont non bloquantes. Il n’attend pas d’avoir reçu les données d’un client pour traiter celles d’un autre. Il renvoie les réponses le plus vite possible sans les regrouper. Ce mode de fonctionnement est essentiel pour une réaction temps réel de la carte.

Le terme local signifie que le serveur (ESP32) et le client (Processing sur PC) sont sur le même réseau.

Le système peut également fonctionner à travers internet avec quelques adaptations.

Une vidéo est à suivre.

Installation

Préparons le matériel et les structures logicielles nécessaires avant de passer à la suite.


Matériel

Achetez les deux éléments suivants :

  • Carte ESP32 équipé d’un écran TFT : TTGO T Display
  • Cable USB-C pour relier votre carte à votre PC
Une carte ESP32 simple fonctionnera aussi. Mais bon … sans écran, vous ne verrez pas grand chose !

IDE Processing

Pour préparer l’IDE Processing, rendez-vous à cette Adresse. Installez l’exécutable. Puis :

  1. Installez la librairie UDP pour processing
    • Sketch ˃ Importer une librairie ˃ Ajoutez une librairie :
      Une fenêtre s’ouvre
    • Librairies :
      Tapez UDP dans la barre de recherche,
      Choisissez la version de Stephane Cousot

IDE Arduino

Pour préparer l’IDE Arduino, rendez-vous à cette Adresse. Installez l’exécutable, puis :

  1. Installez le compilateur de cartes ESP32 : lancez l’IDE
    • Fichier ˃ Préférences :
      Ajoutez https://dl.espressif.com/dl/package_esp32_index.json;
    • Outils ˃ Type de carte ˃ Gestionnaire de cartes :
      recherchez et installez esp32
    • Outil ˃ Type de carte :
      Choisissez ESP32 Dev Module
  1. Installez les librairies : Fermez l’IDE
    • Téléchargez TFT_eSPI, Button2
      (AsyncUDP est déjà présent pour les puces ESP32)
    • Dézippez les dans le dossier librairies d’Arduino
      Pour Windows : C:\Users\"nom"\Documents\Arduino\libraries
    • Fichier > Exemples : Relancez l’IDE, les librairies devraient être en bas.
  1. Préparez l’écran TFT :
    • TFT_eSPI ˃ User_Setup_Select.h :
      Commentez #include ˂User_Setup.h˃
      Décommentez #include ˂User_Setups/Setup25_TTGO_T_Display.h˃

Programme

Vous pouvez voir ici les deux programmes répartis sur 5 onglets.


Serveur local UDP

Le programme du serveur local UDP est dans les onglets 2 à 5. Il doit être téléversé dans la puce TTGO T Display à l’aide de l’IDE Arduino. Pour cela :

  1. Créez un dossier UDPserver
  2. Créez 4 fichiers texte dans lesquels vous copierez les codes respectifs :
    • TCPserver.ino
    • config.h
    • screen.ino
    • udp.ino
  3. Outil ˃ Type de carte : Choisissez ESP32 Dev Module
  4. Versez le code dans la puce TTGO T Display :
    • Branchez la puce à votre PC
    • Outil ˃ Port : Sélectionnez le nouveau port apparu
    • 1e icone à gauche : Téléversez le programme

Seul le fichier config.h est à modifier en fonction des paramètres de connexions de votre routeur / box sur lequel est connecté le client. Le programme est très robuste :

  • Plusieurs clients UDP peuvent se connecter à la fois
  • Un client UDP peut se déconnecter, puis se reconnecter sans problèmes
  • Les logs des activités sont renvoyés sur le port série (115200 bits/s)

Client local UDP

Le programme du client local UDP est dans le premier onglet. Il est à copier-coller dans l’IDE Processing. Seul la variable String ip_server est à modifier (et le port si vous souhaitez le modifier) en y rentrant l’ip du serveur. Moins robuste, ce programme ne doit être lancé qu’après que le serveur soit prêt (affiche une IP)


Code

ProcessingUDPserver.inoconfig.hscreen.inoudp.ino
// Variables et librairies
import hypermedia.net.*;

UDP client;
int screen_x = 3;   //Agrandissement de l'écran
int screen_h = 240; //Taille de l'écran horizontal de la puce TTGO T Display
int screen_v = 135; //Taille de l'écran vertical de la puce TTGO T Display

int old_x;
int old_y;


// Elément à changer selon l'IP du serveur, son port, et la fréquence des données
String ip_server   = "xxx.xxx.xxx.xxx"; // IP du serveur distant
int    port_server = 1234;              // Port du serveur distant
int    port_client = 6000;              // valeur aléatoire, normalement non utilisée
int    frequence   = 100;               // fréquence d'actualisation (oui j'ai mis, 100 Hz, pas 60 Hz)


// Lancement de l'écran (se lance avant "setup()")
void settings()
{
  size(screen_v*screen_x, screen_h*screen_x);
}


// Lancement du client, connexion au serveur et fixation du framerate
void setup()
{
  frameRate(frequence);
  client = new UDP(this, port_client);
  client.listen(true);
}


// Rafraichissement de l'écran et envoi de la position de la souris au serveur
void draw()
{
  int x = mouseX;
  int y = mouseY;
  
  if(old_x != x || old_y != y)
  {
    old_x = x;
    old_y = y;
    
    background(20);
    square(x, y, 3*screen_x);
  
    // Contrairement au TCP, il n'est pas nécessaire de voir si le client est actif
    // Car on ne vérifie pas si les données sont bien reçues en UDP
    println(str(x) + " " + str(y));
    client.send( str(x/screen_x) + " " + str(y/screen_x) + "\0" , ip_server, port_server );
  }
}


// Ecoute des informations rentrant par le "port_client"
void receive( byte[] data, String ip, int port )
{
  data = subset(data, 0, data.length-2);
  String message = new String( data );

  println( "receive: \""+message+"\" from "+ip+" on port "+port );
}
//Librairies nécessaires au serveur TCP
#include "WiFi.h"
#include "AsyncUDP.h"
#include "config.h"


//Librairies et paramètres de base nécessaires à l'écran
#include <TFT_eSPI.h>
#include <SPI.h>
#include "esp_adc_cal.h"

#ifndef TFT_DISPOFF
  #define TFT_DISPOFF 0x28
#endif

#ifndef TFT_SLPIN
  #define TFT_SLPIN   0x10
#endif

#define TFT_MOSI  19
#define TFT_SCLK  18
#define TFT_CS     5
#define TFT_DC    16
#define TFT_RST   23
#define TFT_BL     4  // Display backlight control pin

TFT_eSPI tft = TFT_eSPI(135, 240); // Invoke custom library

AsyncUDP udp;

//Initialisation
void setup()
{
  //Initialisation connexion série (pour les logs)
  Serial.begin(115200);
  Serial.println("\n\nHello World");
  
  //Initilisation écran
  tft_init();

  //Connection WiFi à un routeur
  WiFi.mode(WIFI_STA);
  WiFi.begin(ROUTEUR_SSID, ROUTEUR_PASSWORD);
  if (WiFi.waitForConnectResult() != WL_CONNECTED)
  {
    tft_echec_connexion();
    while(1)
    {
      Serial.println("WiFi Failed");
      delay(1000);
    }
  }
  Serial.printf("Connected to %s, IP %s \n", ROUTEUR_SSID, WiFi.localIP().toString().c_str());
  tft_show_ip(WiFi.localIP());

  //Lancement du serveur
  if(udp.listen(SERVER_HOST_PORT)) //Si le serveur UDP peut écouter sur le port "SERVER_HOST_PORT"
  {
    udp_listen(); //Démarrer l'écoute du serveur
  }
}


//Loop, peut être laissé vide
void loop()
{
  delay(1000);
  udp.broadcast("Anyone here?");
}
#ifndef CONFIG_H
  #define CONFIG_H
  
  #define ROUTEUR_SSID     "ssid"      //ssid de votre routeur
  #define ROUTEUR_PASSWORD "password"  //mot de passe de votre routeur
  
  #define SERVER_HOST_PORT 1234
  #define SERVER_HOST_NAME "esp_server"

#endif // CONFIG_H
//Initialisation de l'écran
void tft_init()
{
  tft.init();
  tft.setRotation(0);
  tft.fillScreen(TFT_BLACK);
  tft.setTextSize(2);
  tft.setTextColor(TFT_RED);
  tft.setCursor(0, 0);
  tft.setTextDatum(MC_DATUM);
  tft.println("Hello World\n");
}

//Simulation de la souris à l'écran à partir de la trame data
void tft_mouse(char* data_, size_t len)
{
  static int vx = 0;
  static int vy = 0;

  int old_vx = vx;
  int old_vy = vy;

  char temp[20] = "";
  
  strncpy(temp, data_, len);
  temp[len] = 0;
  sscanf(temp, "%d %d", &vx, &vy);

  if(old_vx != vx || old_vy != vy)
  {
    sprintf(temp, "%d %d %d %d", old_vx, old_vy, vx, vy);
    tft.fillRect(old_vx, old_vy, 3, 3, 0x000070);
    tft.fillRect(vx, vy, 3, 3, TFT_GREEN);
  }
}

//Affichage de l'IP sur l'écran
void tft_show_ip(IPAddress data_)
{
  tft.setTextSize(2);
  tft.setTextColor(TFT_RED);
  tft.setCursor(0, 16);
  tft.println(data_);
}

//Affichage de l'IP sur l'écran
void tft_echec_connexion()
{
  tft.setTextSize(2);
  tft.setTextColor(TFT_RED);
  tft.setCursor(0, 16);
  tft.println("echec de connexion au WiFi");
}
long temps = 0;


//Configuration du serveur
void udp_listen()
{
  Serial.printf("Server UDP %s Listening on IP : %s", SERVER_HOST_NAME, WiFi.localIP().toString().c_str());

  //Activation de l'évènement : Je reçoit une trame UDP
  udp.onPacket([](AsyncUDPPacket packet)
  {
    int len = packet.length();
    if(len > 8) len = 8; //protection

    float delai = (float)(millis() - temps);
    temps = millis();

    //Données reçues (log)
    Serial.printf("\nData from %s (%2.0f Hz) : ", packet.remoteIP().toString().c_str(), 1000.0/delai );
    Serial.write(packet.data(), len);
    //udp_log(&packet); //activez ceci si vous voulez un log détaillé et envoyer une réponse au client
    
    //Envoi des données à l'écran
    tft_mouse((char*)packet.data(), packet.length());
  });
}


//Log UDP, non activé par défaut, voir fonction précédente
void udp_log(AsyncUDPPacket* packet)
{
  //Lecture complète de la trame UDP reçue
  Serial.print(" UDP Packet Type: ");
  Serial.print(packet->isBroadcast()?"Broadcast":packet->isMulticast()?"Multicast":"Unicast");
  Serial.print(", From: ");
  Serial.print(packet->remoteIP());
  Serial.print(":");
  Serial.print(packet->remotePort());
  Serial.print(", To: ");
  Serial.print(packet->localIP());
  Serial.print(":");
  Serial.print(packet->localPort());
  Serial.print(", Length: ");
  Serial.print(packet->length());
  Serial.print(", Data: ");
  Serial.write(packet->data(), packet->length());
  Serial.println();
  
  //Réponse au client
  packet->printf("Got %u bytes of data", packet->length());
}

Votez cet article :

We are sorry that this post was not useful for you!

Let us improve this post!

Tell us how we can improve this post?

Laisser un commentaire