Crea sito

Note WeMos – Ricezione pacchetti UDP

>>> LINK A PAGINA AGGIORNATA <<<

  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .

Partendo dalla connessione base, aggiungere libreria UDP e variabili di lavoro. Si ipotizza di voler ricevere pacchetti sulla porta 9000 (indifferentemente indirizzati all’IP locale della scheda o broadcast) lunghi al massimo 24 byte:

#include <WiFiUdp.h>

#define        UDPPORT 9000
#define        MAXBUF  24
uint16_t       g_udp_rx_len;
IPAddress      g_udp_rx_ip;
char           g_udp_rx_buffer[MAXBUF];
WiFiUDP        Udp;

Nella funzione setup inizializzare il link UDP:

Udp.begin(UDPPORT);

Nella funzione loop prevedere una chiamata ciclica:

if (ricevuto_UDP()) { process_packet(); }

E aggiungere le funzioni per la ricezione e la gestione dei pacchetti, ad esempio per visualizzare su seriale i byte ricevuti:

//----------------------------------------------------------
//  FUNZIONE   : drop_incoming_packet
//  DESCRIZIONE: Scarta pacchetto ricevuto fuori specifiche
//  PARAMETRI  : nessuno
//  RETURN CODE: ignorato
//----------------------------------------------------------
void drop_incoming_packet(void) 
{ 
    while (Udp.available())
    { 
        Udp.read(g_udp_rx_buffer, MAXBUF); 
    } 
}
//----------------------------------------------------------
//  FUNZIONE   : ricevuto_UDP
//  DESCRIZIONE: Verifica presenza pacchetti UDP, scarta
//               quelli piu`lunghi di 24 byte.
//               Questo e` un processo da
//               richiamare continuamente.
//  PARAMETRI  : nessuno
//  RETURN CODE: true se pacchetto ricevuto
//----------------------------------------------------------
bool ricevuto_UDP(void)
{
    if (!g_link_ok) { return false; }          // link down
    g_udp_rx_len = Udp.parsePacket();
    if (0 == g_udp_rx_len) { return false; }   // no dati
    if (g_udp_rx_len > MAXBUF)      // scarta troppo lunghi
    { 
        drop_incoming_packet();  
        return false; 
    }
    g_udp_rx_ip = Udp.remoteIP();
    Udp.read(g_udp_rx_buffer, MAXBUF);
    board_led_off();    // blink ad ogni pacchetto ricevuto
    return true;
}
//----------------------------------------------------------
//  FUNZIONE   : process_packet
//  DESCRIZIONE: Gestisce i pacchetti UDP ricevuti
//  PARAMETRI  : nessuno, usa il buffer ricezione globale
//  RETURN CODE: ignorato
//----------------------------------------------------------
void process_packet(void)
{
    Serial.print("From: ");
    Serial.print(g_udp_rx_ip[0]);
    Serial.print(" ");
    Serial.print(g_udp_rx_ip[1]);
    Serial.print(" ");
    Serial.print(g_udp_rx_ip[2]);
    Serial.print(" ");
    Serial.print(g_udp_rx_ip[3]);
    Serial.print(" - ");
    for (int i=0; i<g_udp_rx_len; i++)
    {
        Serial.print(g_udp_rx_buffer[i], DEC);
        Serial.print(' ');
    }
    Serial.println();
}

Uscite remote comandate da Python

Il seguente script Python3 genera pacchetti UDP broadcast porta 9000. Ogni pulsante produce un pacchetto con una diversa stringa di caratteri, rispettivamente B1 B2 B3 e B4.

import tkinter as tk
import socket

PORT      = 9000
BROADCAST = '192.168.1.255', PORT
IPLOCALE  = '', PORT

sock = socket.socket(
    socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP
    )
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
sock.bind(IPLOCALE)
sock.settimeout(0)

def invia(data): sock.sendto(data, BROADCAST)        

root = tk.Tk()
root.title('Telecomando UDP')
root.resizable(False, False)

button1 = tk.Button(root, width=10, text='Uscita 1')
button1.pack(side=tk.LEFT, padx=2, pady=4)
button1.bind('<Button-1>', lambda _:invia(b'B1'))

button2 = tk.Button(root, width=10, text='Uscita 2')
button2.pack(side=tk.LEFT, padx=2, pady=4)
button2.bind('<Button-1>', lambda _:invia(b'B2'))

button3 = tk.Button(root, width=10, text='Uscita 3')
button3.pack(side=tk.LEFT, padx=2, pady=4)
button3.bind('<Button-1>', lambda _:invia(b'B3'))

button4 = tk.Button(root, width=10, text='Uscita 4')
button4.pack(side=tk.LEFT, padx=2, pady=4)
button4.bind('<Button-1>', lambda _:invia(b'B4'))
root.mainloop();

Lato WeMos dobbiamo aggiungere definizioni e stati delle uscite:

#define        USCITA1   D5
#define        USCITA2   D6
#define        USCITA3   D7
#define        USCITA4   D8
uint8_t        g_uscita1 = 0;
uint8_t        g_uscita2 = 0;
uint8_t        g_uscita3 = 0;
uint8_t        g_uscita4 = 0;

Il settaggio iniziale nella ‘setup’:

    pinMode(USCITA1, OUTPUT);
    pinMode(USCITA2, OUTPUT);
    pinMode(USCITA3, OUTPUT);
    pinMode(USCITA4, OUTPUT);
    aggiorna_uscite();

E la gestione/riconoscimento dei pacchetti:

//----------------------------------------------------------
//  FUNZIONE   : aggiorna_uscite
//  DESCRIZIONE: Attiva o disattiva le uscite in base
//               ai valori delle variabili.
//  PARAMETRI  : nessuno
//  RETURN CODE: ignorato
//----------------------------------------------------------
void aggiorna_uscite(void)
{
    digitalWrite(USCITA1, g_uscita1); 
    digitalWrite(USCITA2, g_uscita2); 
    digitalWrite(USCITA3, g_uscita3); 
    digitalWrite(USCITA4, g_uscita4); 
}
//----------------------------------------------------------
//  FUNZIONE   : process_packet
//  DESCRIZIONE: Gestisce i pacchetti UDP ricevuti
//  PARAMETRI  : nessuno, usa il buffer ricezione globale
//  RETURN CODE: ignorato
//----------------------------------------------------------
void process_packet(void)
{
    if (  (g_udp_rx_len != 2)
       || (g_udp_rx_buffer[0] != 'B'))  { return; }

    if      ('1' == g_udp_rx_buffer[1]) { g_uscita1 ^= 1; }
    else if ('2' == g_udp_rx_buffer[1]) { g_uscita2 ^= 1; }
    else if ('3' == g_udp_rx_buffer[1]) { g_uscita3 ^= 1; }
    else if ('4' == g_udp_rx_buffer[1]) { g_uscita4 ^= 1; }
    else                                { return;         }

    aggiorna_uscite();
}

Il problema dei pacchetti persi

La trasmissione UDP è intrinsecamente non sicura, i pacchetti possono arrivare a destinazione oppure no, non vi sono segnalazioni di mancato recapito. Per realizzare un “telecomando” appena un po’ più sicuro (ma comunque non garantito) è necessario trasmettere più volte il pacchetto, diciamo quattro volte a distanza di una decina di millisecondi.

Per non generare comandi multipli, è necessario distinguere un pacchetto nuovo dalla ripetizione di uno arrivato in precedenza. Per questo si deve introdure anche un “sequence number”. Quando il sequence number è diverso da quello dell’ultimo pacchetto ricevuto, allora si tratta di un nuovo pacchetto e non di una ripetizione.

Se un ricevitore viene comandato da un solo punto, allora come sequence number è sufficiente la coppia 0 e 1. Se invece il comando può arrivare da più sorgenti allora serve un sequence number il più possibile univoco, ad esempio un numero random a 32 bit.

Esempio lato trasmettitore Python:

def invia(data): 
    seqnbr = random.randrange(2**32)
    byte1 = seqnbr >> 24
    byte2 = (seqnbr >> 16) & 0xFF
    byte3 = (seqnbr >> 8)  & 0xFF
    byte4 = seqnbr & 0xFF
    data += bytes([byte1, byte2, byte3, byte4])
    for _ in range(4):
        sock.sendto(data, BROADCAST)
        time.sleep(0.01)

Lato ricevitore WeMos, questa volta si attende un messaggio di 6 byte:

//----------------------------------------------------------
//  FUNZIONE   : process_packet
//  DESCRIZIONE: Gestisce i pacchetti UDP ricevuti
//  PARAMETRI  : nessuno, usa il buffer ricezione globale
//  RETURN CODE: ignorato
//----------------------------------------------------------
void process_packet(void)
{
    uint32_t static seqnbr = 0;

    if (  (g_udp_rx_len != 6)
       || (g_udp_rx_buffer[0] != 'B'))  { return; }

    uint32_t rseqnbr =  g_udp_rx_buffer[2] << 24;
    rseqnbr |= g_udp_rx_buffer[3] << 16;
    rseqnbr |= g_udp_rx_buffer[4] << 8;
    rseqnbr |= g_udp_rx_buffer[5];

    if (rseqnbr == seqnbr) { return; }  // ignora se uguali
    seqnbr = rseqnbr;    

    if      ('1' == g_udp_rx_buffer[1]) { g_uscita1 ^= 1; }
    else if ('2' == g_udp_rx_buffer[1]) { g_uscita2 ^= 1; }
    else if ('3' == g_udp_rx_buffer[1]) { g_uscita3 ^= 1; }
    else if ('4' == g_udp_rx_buffer[1]) { g_uscita4 ^= 1; }
    else                                { return;         }

    aggiorna_uscite();
}

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.