Semana 11

Esta semana vamos a trabajar en otro protocolo de transporte que nos permite conectar sensores y actuadres en red inalámbrica. Se trata de UDP (la semana pasada trabajamos TCP).

Sesión 1

Las semanas anteriores utilizamos TCP/IP y el modelo cliente servidor para integrar sensores y actuadores utilizando WiFi. En ests sesión vamos a utilizar el protocolo UDP.

Objetivo de la sesión

Integrar sensores y actuadores con dispositivos de cómputo utilizando WiFi y el protocolo UDP.

Ejercicio: analizar el código

Para explorar UDP vamos a realizar un proyecto simple que ilustra el uso del protocolo. Se trata de un conjunto de actuadores distribuidos en el espacio y un coordinar central, un PC. Cada actuador enciende y apaga un puerto de entrada salida según lo indique el comando, recibido por UDP, que será enviado por el coordinador central. El coordinador cuenta con un dispositivo, que llamaremos bridge, quien recibirá por serial los comandos y los reenviará por UDP a los actuadores distribuidos.

El protocolo de comunicación serial es simple. Se trata de un protocolo ascii compuesto por tres caracteres. El primer carácter indica a cual actuador se enviará el comando. El segundo carácter el estado deseado para la salida (‘1’ on, ‘0’ off). Por último, se envía un carácter de sincronización (‘*’).

El código del bridge es el siguiente:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include <WiFi.h>
#include <WiFiUdp.h>

const char* ssid = "?";
const char* password = "?";
WiFiUDP udpDevice;
uint16_t localUdpPort = ?;
uint16_t UDPPort = ?;
#define MAX_LEDSERVERS 3
const char* hosts[MAX_LEDSERVERS] = {"?.?.?.?", "?.?.?.?", "?.?.?.?"};
#define SERIALMESSAGESIZE 3
uint32_t previousMillis = 0;
#define ALIVE 1000
#define D0 5

void setup() {
  pinMode(D0, OUTPUT);     // Initialize the LED_BUILTIN pin as an output
  digitalWrite(D0, HIGH);
  Serial.begin(115200);
  Serial.println();
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  // Print the IP address
  Serial.println(WiFi.localIP());
  udpDevice.begin(localUdpPort);
}

void networkTask() {
  uint8_t LEDServer = 0;
  uint8_t LEDValue = 0;
  uint8_t syncChar;

  // Serial event:
  if (Serial.available() >= SERIALMESSAGESIZE) {
    LEDServer = Serial.read() - '0';
    LEDValue = Serial.read();
    syncChar = Serial.read();
    if ((LEDServer == 0) || (LEDServer > 3)) {
      Serial.println("Servidor inválido (seleccione 1,2,3)");
      return;
    }
    if (syncChar == '*') {
      udpDevice.beginPacket(hosts[LEDServer - 1] , UDPPort);
      udpDevice.write(LEDValue);
      udpDevice.endPacket();
    }
  }
  // UDP event:
  uint8_t packetSize = udpDevice.parsePacket();
  if (packetSize) {
    Serial.print("Data from: ");
    Serial.print(udpDevice.remoteIP());
    Serial.print(":");
    Serial.print(udpDevice.remotePort());
    Serial.print(' ');
    for (uint8_t i = 0; i < packetSize; i++) {
      Serial.write(udpDevice.read());
    }
  }
}

void aliveTask() {
  uint32_t currentMillis;
  static uint8_t ledState = 0;
  currentMillis  = millis();
  if ((currentMillis - previousMillis) >= ALIVE) {
    previousMillis = currentMillis;
    if (ledState == 0) {
      digitalWrite(D0, HIGH);
      ledState = 1;
    }
    else {
      digitalWrite(D0, LOW);
      ledState = 0;
    }
  }
}

void loop() {
  networkTask();
  aliveTask();
}

Note que a diferencia de TCP/IP, con UDP no es necesario establecer una conexión. Los pasos necesario para enviar datos por UDP serán:

  • Crear un objeto WiFiUDP
  • Iniciar el objeto estableciendo un socket compuesto por la dirección IP y el puerto de escucha.
  • Iniciar la construcción del paquete a transmitir con beginPacket(),
  • Popular el buffer de transmisión con write.
  • Enviar el paquete con endPacket().

El código de los actuadores distribuidos será:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
 #include <WiFi.h>
 #include <WiFiUdp.h>

 const char* ssid = "?";
 const char* password = "?";
 WiFiUDP udpDevice;
 uint16_t localUdpPort = ?;
 uint32_t previousMillis = 0;
 #define ALIVE 1000
 #define D0 5
 #define D8 18

 void setup() {
     pinMode(D0, OUTPUT);     // Initialize the LED_BUILTIN pin as an output
     digitalWrite(D0, HIGH);
     pinMode(D8, OUTPUT);
     digitalWrite(D8, LOW);
     Serial.begin(115200);
     Serial.println();
     Serial.println();
     Serial.print("Connecting to ");
     Serial.println(ssid);

     WiFi.mode(WIFI_STA);
     WiFi.begin(ssid, password);

     while (WiFi.status() != WL_CONNECTED) {
         delay(500);
         Serial.print(".");
     }
     Serial.println("");
     Serial.println("WiFi connected");
     // Print the IP address
     Serial.println(WiFi.localIP());
     udpDevice.begin(localUdpPort);
 }


 void networkTask() {
     uint8_t data;
     uint8_t packetSize = udpDevice.parsePacket();
     if (packetSize) {
         data = udpDevice.read();
         if (data == '1') {
             digitalWrite(D0, HIGH);
         } else if (data == '0') {
             digitalWrite(D0, LOW);
         }
         // send back a reply, to the IP address and port we got the packet from
         udpDevice.beginPacket(udpDevice.remoteIP(), udpDevice.remotePort());
         udpDevice.write('1');
         udpDevice .endPacket();
     }
 }

 void aliveTask() {
     uint32_t currentMillis;
     static uint8_t ledState = 0;
     currentMillis  = millis();
     if ((currentMillis - previousMillis) >= ALIVE) {
         previousMillis = currentMillis;
         if (ledState == 0) digitalWrite(D8, HIGH);
         else digitalWrite(D8, LOW);
     }
 }

 void loop() {
     networkTask();
     aliveTask();
 }

Los pasos para recibir datos por UDP son:

  • Crear un objeto WiFiUDP
  • Iniciar el objeto estableciendo un socket compuesto por la dirección IP y el puerto de escucha.
  • Procesar el siguiente paquete UDP con parsePacket(). Esta acción devolverá el tamaño del paquete en bytes.
  • Luego de llamar parsePacket() será posible utilizar los métodos read() y available().
  • Leer el paquete.

En el ejemplo mostrado, note que un actuador distribuido responderá al bridge con el carácter ‘1’ cada que reciba un paquete. De esta manera el bridge sabrá que el dato llegó a su destino.

Ejercicio: despliegue del ejercicio

Para desplegar este ejercicio necesitamos varios dispositivos: PC, ESP32.

Para desplegar el ejercicio es necesario identificar claramente las direcciones IP de cada uno de los actuadores remotos.

Utilice un ESP32 para cada actuador y un ESP32 para el bridge. Como en este caso no contamos con tantos dispositivos entonces:

  • Usar el ESP32 como bridge y como actuadores el celular y el computador.
  • Utilice los programas Hercules para simular la aplicación del PC y los actuadores.

Sesión 2

En esta sesión vamos a practicar las comunicaciones por UDP.

RETO

Se trata de un programa en el PC que se comunica con un controlador ESP32. El controlador tiene conectados un sensor y un actuador.

  • Utilizar como referencia los códigos de la sesión 1.
  • Use hércules para simular un programa en el computador que solicitará leer el sensor y modificar el actuador.
  • El ESP32 tendrá conectado un BME280 o BMP280 y un LED (actuador).
  • Desde el programa del PC debemos leer el valor del sensor y cambiar el estado del LED
  • Usted debe definir el protocolo que utilizará. Puede seleccionar entre un protocolo binario o ascii.