ESP32 et TCP – 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 . 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 AsyncTCP 10/2019 (TCP asynchrone pour ESP32)
    • Librairie ESPAsyncTCP 10/2019 (TCP asynchrone pour ESP8266)
    • 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 TCP. 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 TCP asynchrone local

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

  • il offre une connexion point à point entre deux hôtes
  • il assure la bonne réception des trames d’un hôte à l’autre

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. Vous avez fini !


IDE Arduino

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

  1. Installez le compilateur de cartes ESP32 :
    • 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 :
    • Téléchargez AsyncTCP, ESPAsyncTCP, TFT_eSPI, Button2
    • Dézippez les dans le dossier librairies d’Arduino
      Pour Windows : C:\Users\"nom"\Documents\Arduino\libraries
    • Fichier > Exemples : (Re) lancez 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 TCP

Le programme du serveur local TCP 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 TCPserver
  2. Créez 4 fichiers texte dans lesquels vous copierez les codes respectifs :
    • TCPserver.ino
    • config.h
    • screen.ino
    • tcp.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 TCP peuvent se connecter à la fois
  • Un client TCP 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 TCP

Le programme du client local TCP est dans le premier onglet. Il est à copier-coller dans l’IDE Processing. Seul la variable String ip_server est à 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

ProcessingTCPserver.inoconfig.hscreen.inotcp.ino
// Variables et librairies
import processing.net.*; 

Client myClient;
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
String ip_server = "192.168.8.159";

// 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()
{ 
  myClient = new Client(this, ip_server, 1234); 
  myClient.write("Hi there");
  frameRate(60);
}

// 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);
    
    if(myClient.active())
    {
      println(str(x) + " " + str(y));
      myClient.write(str(x/screen_x) + " " + str(y/screen_x) + "\0");
    }
    else
    {
      println("client déconnecté, vérifiez que le server est acif et relancez le programme ");
    }
  }
} 
//Librairies nécessaires au serveur TCP
#include <WiFi.h>
#include <AsyncTCP.h>
#include <vector>
#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


//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());
  tft_show_ip(WiFi.localIP());

  //Lancement du serveur
	AsyncServer* server = new AsyncServer(SERVER_HOST_PORT); //Connection WiFi à un routeur
	server->onClient(&handleNewClient, server);              //Activation de l'évènement "Un nouveau client se connecte"
	server->begin();                                         //Lancement du serveur
}


//Loop vide
void loop() {}
#ifndef CONFIG_H
  #define CONFIG_H
  
  #define ROUTEUR_SSID     "ssid"
  #define ROUTEUR_PASSWORD "password"
  
  #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(void* 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, (char *)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 "echec de connexion"
void tft_echec_connexion()
{
  tft.setTextSize(2);
  tft.setTextColor(TFT_RED);
  tft.setCursor(0, 16);
  tft.println("echec de connexion au WiFi");
}

static std::vector<AsyncClient*> clients; // a list to hold all clients
int temps = 0;

// Evènement "erreur de connexion du client"
static void handleError(void* arg, AsyncClient* client, int8_t error)
{
  Serial.printf("\n connection error %s from client %s \n", client->errorToString(error), client->remoteIP().toString().c_str());
}


// Evènement "Données recues par le client"
static void handleData(void* arg, AsyncClient* client, void *data, size_t len)
{
  if(len > 8) len = 8; //protection

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

  //Données reçues
  Serial.printf("\nData from %s (%2.0f) : ", client->remoteIP().toString().c_str(), 1000.0/delai );
  Serial.write((uint8_t*)data, len);

  //Envoi des données à l'écran
  tft_mouse(data, len);
  
  // Réponse du serveur au client
  if (client->space() > 32 && client->canSend())
  {
    char reply[32];
    sprintf(reply, "this is from %s", SERVER_HOST_NAME);
    client->add(reply, strlen(reply));
    client->send();
  }
}


// Evènement "Le client s'est déconnecté"
static void handleDisconnect(void* arg, AsyncClient* client)
{
  Serial.printf("\n client %s disconnected \n", client->remoteIP().toString().c_str());
}


// Evènement "Le client n'a pas confirmé la réception de ses données à temps"
static void handleTimeOut(void* arg, AsyncClient* client, uint32_t time)
{
  Serial.printf("\n client ACK timeout ip: %s \n", client->remoteIP().toString().c_str());
}


//Evènement "Un nouveau client se connecte"
static void handleNewClient(void* arg, AsyncClient* client) {
  Serial.printf("\n new client has been connected to server, ip: %s", client->remoteIP().toString().c_str());

  // Ajout du nouveau client "client" à la liste des clients existants
  clients.push_back(client);
  
  // Activation des 4 évènements précédents pour ce client
  client->onData(&handleData, NULL);
  client->onError(&handleError, NULL);
  client->onDisconnect(&handleDisconnect, NULL);
  client->onTimeout(&handleTimeOut, NULL);
}

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