esp8266-protocolo-udp

Cómo comunicar un ESP32 por protocolo UDP

  • 5 min

UDP es un protocolo de comunicación ligero y sin conexión que podemos usar en ESP32 cuando nos interesa enviar datos rápido y con poca sobrecarga.

El ejemplo está orientado a ESP32. En muchos casos también puede adaptarse a ESP8266 cambiando librerías y algunos detalles de pines.

Llevamos varias entradas viendo formas de comunicar el ESP8266 con el cliente. Hemos visto los formularios web como solución sencilla (y algo obsoleta), y las más modernas conexiones Ajax, los websockets, y los websockets asíncronos.

Todas estas soluciones funcionan mediante HTTP sobre TCP. Pero, a veces, nos olvidamos de que también existen las comunicaciones UDP. De forma muy resumida, UDP (Universal Datagram Protocol) prescinde de parte de los paquetes necesarios para crear la conexión y la verificación de errores.

En una comunicación UDP el servidor envía paquetes sin esperar acuse de recibo del cliente. Si un paquete se pierde, el cliente no puede solicitar que se reenvíe. Por tanto, TCP es útil para aplicaciones que requieren confiabilidad en la comunicación. Mientras que UDP es útil para transmisiones rápidas, incluso mayor que un Websocket.

Y en el caso de un microprocesador como el ESP8266, una comunicación UDP encaja en muchos casos. Además, las comunicaciones UDP suponen una menor carga para el servidor ya que evita una buena parte de los paquetes requeridos.

Por ejemplo, si estamos enviando una animación a una serie de LEDs, o el control de posición de un robot. En estos casos de comunicación “casi continua” no me importa tanto el acuse de recibo, si no velocidad. Si un paquete se pierde, será inmediatamente sustituido por el siguiente.

Ejemplo de código

Afortunadamente, implementar una comunicación UDP en el ESP8266 es muy sencillo gracias a la librería ‘WiFiUDP.h’. Vamos a verlo con un ejemplo.

En primer lugar, el bucle principal del programa queda de la siguiente forma.

Veremos que los únicos puntos relevantes son que hemos incluido los ficheros oportunos, y las funciones ‘ConnectUDP()’ y ‘GetUDP_Packet()’ que veremos a continuación.

También tenemos comentada la función ‘SendUDP_Packet(“abcde”)’ que ilustraría el envío de un String por UDP. No la usaremos en este ejemplo, pero ahí está.

#include <WiFi.h>
#include <WiFiUDP.h>

#include "config.h"  // Sustituir con datos de vuestra red
#include "UDP.hpp"
#include "ESP32_Utils.hpp"
#include "ESP32_Utils_UDP.hpp"

void setup() 
{
  Serial.begin(115200);

  ConnectWiFi_STA();
  ConnectUDP();
}

void loop() 
{
  GetUDP_Packet();
  
  //SendUDP_Packet("abcde");
}
Copied!

Por otro lado, tenemos el fichero ‘UDP.hpp’, en el que hemos metido todas las funciones relativas al UDP de nuestro programa.

En este fichero, hemos instanciado un objeto ‘WiFiUDP’, los puertos en los que funcionará la conexión, y definido la función ‘ProcessPacket(String response)’ que recoge la respuesta que queremos dar ante una petición UDP.

// UDP variables
WiFiUDP UDP;

unsigned int localPort = 8888;
unsigned int remotePort = 8889;
char packetBuffer[UDP_TX_PACKET_MAX_SIZE]; //buffer to hold incoming packet,

void ProcessPacket(String response)
{
   Serial.println(response);
}
Copied!

Finalmente, tenemos el fichero ESP32_Utils_UDP.hpp, en el que hemos definido una serie de funciones comunes que podemos reaprovechar entre programas.

Aquí tenemos la función ‘ConnectUDP()’ que establece la conexión UDP, las funciones ‘SendUDP_ACK()’ que envía un acuse de recibo, ‘SendUDP_Packet(String content)’ que envía un String por UDP, y la función ‘GetUDP_Packet(bool sendACK = true)’ que recibe un paquete UDP y lo procesa con la función que hemos definido en el fichero anterior.

boolean ConnectUDP() {
  Serial.println();
  Serial.println("Starting UDP");

  // in UDP error, block execution
  if (UDP.begin(localPort) != 1) 
  {
    Serial.println("Connection failed");
    while (true) { delay(1000); } 
  }

  Serial.println("UDP successful");
}

void SendUDP_ACK()
{
  UDP.beginPacket(UDP.remoteIP(), remotePort);
  UDP.write("ACK");
  UDP.endPacket();
}

void SendUDP_Packet(String content)
{
  UDP.beginPacket(UDP.remoteIP(), remotePort);
  UDP.write(content.c_str());
  UDP.endPacket();
}

void GetUDP_Packet(bool sendACK = true)
{
  int packetSize = UDP.parsePacket();
  if (packetSize)
  {
    // read the packet into packetBufffer
    UDP.read(packetBuffer, UDP_TX_PACKET_MAX_SIZE);

    Serial.println();
    Serial.print("Received packet of size ");
    Serial.print(packetSize);
    Serial.print(" from ");
    Serial.print(UDP.remoteIP());
    Serial.print(":");
    Serial.println(UDP.remotePort());
    Serial.print("Payload: ");
    Serial.write((uint8_t*)packetBuffer, (size_t)packetSize);
    Serial.println();
    ProcessPacket(String(packetBuffer));

    //// send a reply, to the IP address and port that sent us the packet we received
    if(sendACK) SendUDP_ACK();
  }
  delay(10);
}
Copied!

Subimos todo a nuestro ESP8266, y vamos a probar nuestra comunicación UDP. Para ello, os disponemos dos pequeños script en Python.

El script ‘recieveUDP.py’ que recibe un paquete UDP y lo muestra por la pantalla.

import socket

UDP_PORT = 8889

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('', UDP_PORT))

while True:
  data, addr = sock.recvfrom(1024)
  print("received message:")
  print(data.decode('utf-8'))  
Copied!

El script fichero ‘sendUDP.py’ que envía un paquete de ejemplo por UDP.

import socket

UDP_IP = '192.168.43.237'
UDP_PORT = 8888
UDP_PAYLOAD = 'abcdef'

def yes_or_no(question):
    reply = str(input(question)).lower().strip()
    if reply[0] == 'y':
        return 0
    elif reply[0] == 'n':
        return 1
    else:
        return yes_or_no("Please Enter (y/n) ")
    
while True:
  try:
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.sendto(bytes(UDP_PAYLOAD, "utf-8"), (UDP_IP, UDP_PORT))    
    sock.close()
    print("UDP target IP:", UDP_IP)
    print("UDP target port:", UDP_PORT)
    print("message:", UDP_PAYLOAD)
    if(yes_or_no('Send again (y/n): ')):
      break
  except:
    pass
Copied!

Resultado

Lanzamos primero y ‘recieveUDP.py’, que queda a la escucha, y a continuación ‘sendUDP.py’. Vemos en la consola que el paquete se envía correctamente.

arduino-esp8266-udp-send

En el puerto serie de Arduino podemos comprobar que, efectivamente, el ESP8266 recibe el paquete.

arduino-esp8266-udp-serial-port

Finalmente, como en la función de recibir del ESP8266 hemos indicado que queremos que mande una señal ACK, vemos que el script de Python recibe el mensaje de acuse de recibo.

arduino-esp8266-udp-ack

¡Todo funciona correctamente! Fácil, muy útil, y en muchas ocasiones olvidada, las conexiones UDP son otra forma de comunicar un cliente con el ESP8266.

Por supuesto, en lugar de un simple String con “abced”, normalmente trabajaremos con un fichero JSON que contengan la información que queramos, como vimos en esta entrada.

Con esto ya tenemos una comunicación UDP funcionando. No siempre será la opción adecuada, pero cuando encaja, es una herramienta muy útil para enviar mensajes rápidos entre dispositivos.

Descarga el código

Todo el código de esta entrada está disponible para su descarga en Github.

github-full

Versión para el ESP8266: https://github.com/luisllamasbinaburo/ESP8266-Examples

Versión para el ESP32: https://github.com/luisllamasbinaburo/ESP32-Examples