>>> 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(); }