Ethernet e dati UDP spazzatura

Premessa, una rete composta come da disegno seguente.

rete01.png

  • Unità A ogni due secondi trasmette un messaggio al server SRV.
  • Il server risponde ad A con un ACK.
  • Il server inoltra il messaggio alle unità B e C

I messaggi e gli ACK sono tutti pacchetti UDP. Funziona: B e C ricevono (quasi sempre) il pacchetto MSG. Il fatto che qualche volta le unità connesse in WiFi perdessero pacchetti era preventivato e considerato accettabile. Anzi, l'idea era proprio quella di trasmissioni senza garanzia ma molto veloci.

Ma non mi aspettavo invece di:

  • Veder arrivare a B e C messaggi mescolati con i byte dell'ACK trasmesso solo ad A.
  • Veder tornare l'ACK verso A mescolato con i byte di un messaggio trasmesso verso B o C
  • Veder arrvare a B o C messaggi lunghi il doppio se l'altra unità veniva scollegata.

Naturalmente prima ho cercato di capire se c'era un errore nel codice del server (visibile qui -aggiornato-), ma non ho trovato nulla di sbagliato. Dopo alcune serate di sconforto arrivo alla seguente conclusione: il chip W5100 del server (o la libreria che lo gestisce) tiene comunque in pancia i byte dell'ultimo pacchetto che non è riuscito ad inviare, e li aggiunge in testa al prossimo pacchetto che si cerca di spedire anche se riguarda un altro indirizzo.

La soluzione che ho trovato mi sembra un obbrobrio: quando la funzione .endPacket ritorna fallito (zero), invio un pacchetto spazzatura fittizio verso il router (che sicuramente è acceso) in modo da portare via i byte non più utili.

void sendPacket(){

    IPAddress ipDest(192, 168, 1, udpADDR);
    Udp.beginPacket(ipDest, 9000);
    Udp.write(udpBuffer, udpLen);
    uint8_t result = Udp.endPacket();
    if (0 == result){
        
        Udp.beginPacket(IPROUTER, 9000);
        Udp.print('\0');
        Udp.print('\0');
        Udp.endPacket();

    }
}

Funziona, B e C non ricevono più messaggi anomali, le unità si possono scollegare e ricollegare a piacere, e A riceve solo ACK corretti.

Ma c'è qualcosa di meglio oltre a bersagliare il router come cestino dell'immondizia per tutti i pacchetti andati a male? :confused:

Claudio_FF:
... arrivo alla seguente conclusione: il chip W5100 del server (o la libreria che lo gestisce) tiene comunque in pancia i byte dell'ultimo pacchetto che non è riuscito ad inviare, e li aggiunge in testa al prossimo pacchetto che si cerca di spedire anche se riguarda un altro indirizzo.

Però questo comportamento sarebbe in contraddizione con il protocollo UDP che è un fire and forget!
Cioè come farebbe il server a capire se una sua comunicazione in UDP è andata a buon fine oppure no?
Mi sembra strana questa tua conclusione, anche se ormai non mi stupisco più di nulla.
Non è che potrebbe capitare un accavallamento di messaggi? L'ho sparata li, non so se sia possibile in base all'incapsulamento che fai sui dati e cosa effettivamente ricevi.
Hai un qualche sistema per validare i pacchetto ricevuto? se si, ricevi sporco dentro un pacchetto verificato? se si, esclude la mia ipotesi strampalata.

La risparo li: "Il chip W5100 è in grado di accodare più pacchetti in arrivo in un proprio buffer interno da qualche kbyte, da cui possono essere letti in momenti successivi con la funzione read." magari è stato gestito male questo buffer?

maubarzi:
Però questo comportamento sarebbe in contraddizione con il protocollo UDP che è un fire and forget!
Cioè come farebbe il server a capire se una sua comunicazione in UDP è andata a buon fine oppure no?

Brao, davo anche io per scontata questa cosa, ma a quanto vedo riguarda non tanto l'UDP in quanto trasporto, ma qualcosa di livello inferiore (trame ethernet? ARP?).

Anche se il pacchetto è UDP, di default è impostato un retransmissionCount pari a 8 e un retransimissionTimeout di 200ms, il che vuol dire che il nostro fire and forget può impiegare anche un secondo e mezzo prima di fallire, mentre tutto il resto è bloccato intanto che qualcosa "al di sotto cerca".

EDIT: eccolo qua come sospettavo: Arduino constantly "screams" ARP broadcast - Networking, Protocols, and Devices - Arduino Forum Il W5100 non gestisce internamente nessuna tabella di ARP e ad ogni trasmissione manda un broadcast per farsi dare il MAC dagli IP destinatari... altro che trasmissione fast >:(

Hai un qualche sistema per validare i pacchetto ricevuto? se si, ricevi sporco dentro un pacchetto verificato?

Beh, no, per ora osservo direttamente i byte relativi al singolo pacchetto che viene rilevato come blocco intero.

La risparo li: "Il chip W5100 è in grado di accodare più pacchetti in arrivo in un proprio buffer interno da qualche kbyte, da cui possono essere letti in momenti successivi con la funzione read." magari è stato gestito male questo buffer?

Con l'invio del pacchetto spazzatura (che si forma se passa il tempo retransmissionTimeout) tutti gli altri vengono letti correttamente. In effetti in questo momento le unità non perdono un colpo, salvo appunto un rallentamento, pari al transmissionTimeout per ogni pacchetto la cui trasmissione non va a buon fine (a livello sottostante l'UDP/IP).

Ok, però anche se ogni volta si fa dare l'indirizzo fisico, dovrebbe essere una operazione velocissima.
A meno che non si sia disconnesso il dispositivo, che quindi non risponde, non dovresti avere problemi.
Se invece il dispositivo va off-line allora può essere che la richiesta arp vada in timeout e il pacchetto udp non venga spedito e resti in pancia.

Una volta acquisito il mac address dovrebbe sparare il pacchetto senza un timeout.

Quindi il problema è la richiesta del mac address che fallisce, secondo me.
Lo hai verificato? E' verificabile in qualche modo?
Non conosco la libreria, sto andando puramente a logica e buon senso, spero di non farti perdere tempo.

Da quello che mi sto immaginando, la tua soluzione è nella direzione giusta.
Se il comportamento è quello che ho descritto io, cioè che la trasmissione abortisce perchè non riesce a reperire il mac address e non perchè fallisce l'invio del pacchetto udp che sarebbe contro il protocollo, mi aspetto che tenga il messaggio in pancia lasciando allo sviluppatore la scelta di cosa fare, se riprovare poco dopo o di droppare il pacchetto.
Quindi se decidi di droppare il pacchetto la tua soluzione è quella giusta.
Con una sola annotazione, mi pare strano che per farlo non ci sia un metodo apposito e si debba passare per una trasmissione più affidabile, che invece di quella del router consiglierei quella di loopback che sei sicuro sia sempre on :wink:
Quindi 127.0.0.1
Prova, però, a vedere se c'è un qualche metodo per liberare il buffer senza sparare messaggi a caso, è probabile che ci sia.

EDIT:
Una cosa strana della libreria, è che: passi che non si memorizza la tabella arp e quindi debba ogni volta andare a chiedere l'indirizzo fisico, ma che non ti permetta nemmeno di gestirla esternamente esponendo metodi per reperire e impostare il dato non mi pare una bella cosa, costava poco farlo, bastava un metodo per interrogare un ip e un parametro in più nell'invio.
A volte ci si perde in un bicchier d'acqua...

Claudio_FF:
EDIT: eccolo qua come sospettavo: Arduino constantly "screams" ARP broadcast - Networking, Protocols, and Devices - Arduino Forum Il W5100 non gestisce internamente nessuna tabella di ARP e ad ogni trasmissione manda un broadcast per farsi dare il MAC dagli IP destinatari...

Scusate, ma mi perplimo quando leggo ste cose. Non credo che c'entri nulla il MAC e/o l'ARP, in quanto:

  1. il pacchetto UDP non ha negli header alcun MAC quindi non serve una query ARP (questa cosa la fa lo switch) ma un indirizzo IP, e comunque anche nell'RFC768 non vedo informazioni relative al MAC address...

  2. la beginPacket() ha 2 overload:

int EthernetUDP::beginPacket(const char *host, uint16_t port)
int EthernetUDP::beginPacket(IPAddress ip, uint16_t port)

quindi se viene usata fornendo una stringa (char*) come destinatario, anche se poi ci si scrive l'IP, va a chiedere al DNS l'indirizzo IP corrispondente.
Se si conosce l'indirizzo IP si deve usare la seconda, quindi fornendo a parametro un "IPAddress" non c'è alcuna chiamata al DNS tantomeno una query ARP.

  1. In quel topic (che ho letto un poco di sfuggita, ma questa è la mia impressione) si parla di casini interni al W5100 ma con una configurazione particolare ossia se si usano i 4 socket del W5100 (immagino che bastino 2) sia in TCP sia in UDP, in quel caso per fare richieste NTP

Insomma, io all'interno dei sorgenti della Ethernet e di EthernetUDP non vedo chiamate ARP riferite alla gestione UDP.

Ma al limite il problema potrebbe essere un altro: sicuro che i tre arduini che usi hanno tutti dei MAC address differenti? Non è che ce ne sono (almeno) 2 con il classico DEAD BEEF FEED e quindi il problema è nello switch che fai impazzire e quindi i pacchetti vanno in giro "ad minchiam"? :wink:

Infine, magari se ci postassi almeno le parti ci codice dove definisci, inizializzi ed usi Ethernet e gli UDP...

Erm, e quindi che indirizzo MAC comparirebbe come destinazione a livello data link nei pacchetti inviati, secondo te? :o

Quello che lo switch ti risolve ad esempio?

Lo switch non risolve niente, si limita a indirizzare ogni pacchetto entrante sul cavo che porta verso il MAC destinatario scritto nell'intestazione data link del pacchetto. È il mittente che deve inserire questo indirizzo nel pacchetto, e lo fa partendo dall'IP tramite ARP, appunto.

Allora siamo d'accordo, le PDU ethernet si muovono solo grazie ai MAC address, e i protocolli di livello più alto come l'IP sono il payload. Quando un' "unità IP" deve parlare con un'altra "unità IP" prima di tutto deve inviare un broadcast ARP per farsi dare il MAC address, poi può spedire il suo pacchetto IP nella PDU eth.

Ovviamente il pacchetto UDP non contiene il MAC, non c'entra, è un altro livello della pila OSI, ma la PDU di livello 2 non può essere spedita se non si conosce il MAC del destinatario.

Se il W5100 non memorizza le associazioni IP/MAC (tabella di ARP) deve ovviamente richiedere il MAC ad ogni nuova trasmissione. E infatti non capivo cosa c'entravano un retransmissionTimeout e un retransmissionCount con il protocollo UDP che per definizione è senza connessione e senza ritrasmissioni.

Non ho pacchetti che arrivano a indirizzi errati, ma solo pacchetti che contengono anche i dati del precedente in caso non sia stato trasmesso. Mi aspettavo che un beginPacket riportasse a zero il puntatore del buffer di trasmissione, ma forse ha ragione maubarzi, è una scelta progettuale, solo che appunto volendolo scartare ci dovrebbe essere un modo migliore.

Rimane il fatto che se ad ogni trasmissione viene generata la trafila ARP con relativi timeout, si perde il vantaggio della velocità "one shot" dell'UDP. Vero che l'ARP è veloce, su cavo, ma tra unità connesse in WiFi ci sono molti pacchetti persi (e quindi ritrasmissioni).

E comunque se ci sono molte unità che vanno off line i timeout si sommano e la comunicazione si pianta, a questo punto se al W5100 arrivano pacchetti più velocemente dei timeout di quelli che non partono si intasa tutto (alias... non è la scelta migliore per farci un "server").

Il link al codice l'ho messo nel primo post. L'unico MAC address da settare a mano riguarda la ethernet shield e non gli ESP01.

Il wifi aggiunge uno strato in più e l'unico componente che non può mai dare problemi è quello che non c'è!
Ma il wifi come lo gestisci? se non ho letto male il W5100 prevede la connessione via cavo, quindi il wifi sarebbe nel mezzo e gestito a parte giusto?

Comunque, a parte questo, mi sa che hai poco margine di manovra.

Puoi abbassare il tempo di timeout
"RTR (Retry Time-value Register) [R/W] [0x0017 – 0x0018] [0x07D0] This register sets the period of timeout. Value 1 means 100us. The initial value is 2000(0x07D0). That will be set as 200ms."

E ridurre le ritrasmissioni al minimo sindacale
"RCR (Retry Count Register) [R/W] [0x0019] [0x08] This register sets the number of re-transmission. If retransmission occurs more than the number recorded in RCR, Timeout Interrupt (TIMEOUT bit of Socket n Interrupt Register (Sn_IR) is set as ‘1’) will occur."

In verità questo lascerebbe speranze:
"It is used in UDP mode. The basic operation is same as SEND. Normally SEND operation needs Destination Hardware Address that is received in ARP(Address Resolution Protocol) process. SEND_MAC uses Socket n Destination Hardware Address(Sn_DHAR) that is written by users without ARP process. "
Se si riuscisse a impostare l'indirizzo direttamente, non ci sarebbe più timeout possibile perchè tornerebbe ad essere un vero fire and forget

Ultima curiosità: non è che per caso c'è un metodo close() o qualcosa di simile? perchè nei diagrammi di flusso è indicato come l'ultimo step quando è finito tutto, magari se esiste fa proprio la pulitura della eventuale fuffa rimasta in canna.

Edit:

void EthernetClass::setRetransmissionTimeout(uint16_t milliseconds)
{
if (milliseconds > 6553) milliseconds = 6553;
SPI.beginTransaction(SPI_ETHERNET_SETTINGS);
W5100.setRetransmissionTime(milliseconds * 10);
SPI.endTransaction();
}

void EthernetClass::setRetransmissionCount(uint8_t num)
{
SPI.beginTransaction(SPI_ETHERNET_SETTINGS);
W5100.setRetransmissionCount(num);
SPI.endTransaction();
}

Che non ci sia nemmeno un minimo (che so, 4 voci?) di cache ARP mi sembra davvero assurdo :confused: .

Comunque in UDP c'è un checksum, non so se il W5100 lo gestisca, ma potrebbe aiutarti a scartare i pacchetti malformati. Oppure verrà calcolato correttamente sul contenuto sbagliato? :-X

SukkoPera:
Lo switch non risolve niente, si limita a indirizzare ogni pacchetto entrante sul cavo che porta verso il MAC destinatario scritto nell'intestazione data link del pacchetto.

Hai ragione, ho detto una cazzata senza ragionarci chiedo venia. Passando per i layer, ovviamente il datagram UDP (che ha solo la porta) viene inserito in un datagram IP (che ha l'IP) e a sua volta in uno Ethernet che quindi necessita del MAC. E per avere il MAC deve per forza fare un ARP query se non ha (come gli switch L3 dei quali parlavo) una sua cache ARP. Non ci piove.

Ora, per tornare al problema del nostro amico, seguivo questa cosa perché mi preoccupa un poco per via del fatto che uno dei miei progetti è un ripetitore di telecomando via WiFi ed uso UDP per la comunicazione.
Finora non ho avuto problemi, forse perché uso i WeMos (quindi non libreria Ethernet ma ESP8266WiFi) ma anche perché ovviamente non mi accorgo affatto se un pacchetto ci mette 10 millisecondi in più, ma mai capitato di ricevere pacchetti anomali (che non verrebbero riconosciuti e quindi non funzionerebbe la pressione di quel tasto).

Ora però continuo a non capire bene, i problemi del thread linkato a me non sembra riguardino le query ARP ma un malfunzionamento quando si usano tutti e 4 gli slot, sia per TCP sia per UDP, e se non ho capito male non è questo il nostro caso.

Claudio_FF:
Non ho pacchetti che arrivano a indirizzi errati, ma solo pacchetti che contengono anche i dati del precedente in caso non sia stato trasmesso.

Che ci siano pacchetti con contenuto "mischiato" mi continua a sembrare strano, anche considerando eventuali problemi di trasmissione del precedente (perché poi dovrebbe fallire l'invio?). Nella pagina del Pico server ci sono solo spezzoni, magari meglio se metti i tuoi incluso chi invia. Per capire se il problema è nell'invio, nel trasporto, o nella ricezione (ad esempio hai già provato a svuotare tutta udpBuffer dopo la endPacket?) dove trovo il codice o anche solo i metodi di invio e le routine di ricezione dei vari elementi (A, C e SRV dello schema)?

...errare umanum est :wink:

Il link era stato messo perchè si parlava delle richieste ARP su protocollo UDP, da li è emerso che non viene gestita una tabella ARP e quindi ogni richiesta UDP ne fa una per il mac address perchè dismenticotta.
E' proprio questa richiesta ARP, secondo me, la causa dei problemi perchè ha timeout e ritrasmissione in caso di fail.

il W5100, in caso di fail su tutte le ritrasmissioni, si salva tutto su un area di parcheggio e mi sa che alla trasmissione successiva prende la parte pendente e la unisce al nuovo buffer.

Da quello che ho letto sul datasheet c'è un registro su cui mettere il mac address per cui forse si può evitare tutta sta manfrina.

Se questo non fosse possibile/agevole, sulla libreria standard ci sono i metodi per ridurre il timeout e il numero di ritrasmissioni per mitigare il problema.
Resta però il problema di come svuotare il buffer in caso di fallimento.
A me una richiesta al router a perdere mi pareva meno efficace di una richiesta a se stessi mediante l'interfaccia di loopback che eviterebbe anche inutile traffico di rete e sarebbe infallibile, visto che da protocollo dovrebbe copiare semplicemente il pacchetto dal buffer di uscita al baffer di ingresso.

docdoc credo possa dormire tranquillo :slight_smile: Il discorso riguarda solo il W5100 (quindi collegamento via cavo) quando deve trasmettere verso più destinazioni e una di esse fallisce. Per ora gli ESP8266 non hanno mostrato comportamenti strani (a parte il non comparire nella lista dispositivi WiFi connessi del router).


Comunque in UDP c'è un checksum, non so se il W5100 lo gestisca, ma potrebbe aiutarti a scartare i pacchetti malformati. Oppure verrà calcolato correttamente sul contenuto sbagliato? :-X

Mi sembra che la libreria ethernet sia semplificata, almeno per quanto riportato nel reference, come per la seriale dove non si vedono i frame error sui byte in arrivo ecc. Non avendo per ora ricevuto byte diversi da quelli trasmessi (salvo l'accodamento in trasmissione di cui si parla in questo topic) non saprei dire se quel checksum funziona oppure no.


Il codice picoserver completo è il seguente. I vari 'LIVELLO n' indicano solo l'annidamento delle chiamate a funzione, al piano terra ci sono 'setup' e 'loop'. Le funzioni lavorano sui dati condivisi 'buffer di ricezione e trasmissione'. Un pacchetto viene ricevuto dal W5100 e messo nel buffer, e da qui elaborato e/o ritrasmesso.

//----------------------------------------------------------
//  Hardware ArduinoUNO + ethernet shield W5100
//----------------------------------------------------------

#include <Ethernet.h>
#include <EthernetUdp.h>

//----------------------------------------------------------
//  VARIABILI DI LAVORO GLOBALI
//----------------------------------------------------------

EthernetUDP    Udp;

// buffer di ricezione e di
// trasmissione (mai contemporanee)
const uint8_t  MAXBUF = 24;
uint8_t        udpBuffer[MAXBUF];
uint8_t        udpADDR;
uint16_t       udpLen;

// tabella sottoscrizioni topic/indirizzo
const uint16_t MAXSUBS = 200;
uint16_t       subs = 0;
uint8_t        mqttTopic[MAXSUBS] = { 0 };
uint8_t        mqttADDR[MAXSUBS]  = { 0 };

const uint8_t  IPAUTHORIZED[] = { 2, 31, 32 };

//----------------------------------------------------------
//  DESCRIZIONE: Cancella un indirizzo dalla tabella
//               sottoscrizioni
//----------------------------------------------------------
void deleteSubscription(uint8_t addr)
{
    for (uint8_t i=0;  i<MAXSUBS;  i++)
    {
        if (addr == mqttADDR[i]) { mqttADDR[i] = 0; }
    }
}


//----------------------------------------------------------
//  DESCRIZIONE: Trasmette pacchetto UDP, se fallisce
//               lo scarta verso router
//  PARAMETRI  : outService se true abilita la
//               cancellazione indirizzo
//               da lista sottoscrizioni, usato per
//               pacchetti notifica se unita`
//               destinatarie non raggiungibili
//----------------------------------------------------------
void sendPacket(bool outService)
{
    IPAddress ipRouter(192, 168, 1, 1);
    IPAddress ipDest(192, 168, 1, udpADDR);
    Udp.beginPacket(ipDest, 9000);
    Udp.write(udpBuffer, udpLen);
    uint8_t result = Udp.endPacket();
 
    if (0 == result)  // scarico pacchetto non trasmesso
    {
        Udp.beginPacket(ipRouter, 9090);
        Udp.print('\0');
        Udp.print('\0');
        Udp.endPacket();
        if (outService) { deleteSubscription(udpADDR); }
    }
}


//----------------------------------------------------------
//  DESCRIZIONE: Aggiunge indirizzo/topic a tabella
//               sottoscrizioni. Se gia` esistente
//               o tabella piena annulla operazione
//----------------------------------------------------------
void subscribe(uint8_t topic, uint8_t addr)
{
    int16_t pos = -1;      // indice per posizione libera
    bool    giaPresente = false;  // stato sottoscrizione
 
    for (uint8_t i=0;  i<MAXSUBS;  i++)
    {
        uint8_t addrI = mqttADDR[i];
        if (0 == addrI){ 
 
            pos = i;              // posizione libera
 
        }else if ((addr == addrI) 
              and (topic == mqttTopic[i])){
 
            giaPresente = true;
            break;
 
        }else{
            // nothing
        }
    }
 
    if (!giaPresente  and  (pos >= 0))
    {
        mqttADDR[pos]  = addr;
        mqttTopic[pos] = topic;
    }
}


//----------------------------------------------------------
//  DESCRIZIONE: Notifica un messaggio a tutti
//               i sottoscrittori
//----------------------------------------------------------
void publish(void)
{
    uint8_t topic = udpBuffer[1];
    udpBuffer[0] = 'N';
    for (uint8_t i=0;  i<MAXSUBS;  i++)
    {
        udpADDR = mqttADDR[i];
        if ((udpADDR != 0) and (topic == mqttTopic[i]))
        { 
            sendPacket(true); 
        }
    }
}


//----------------------------------------------------------
//  DESCRIZIONE: Invia al mittente un pacchetto ACK + PID
//----------------------------------------------------------
void sendACK(void)
{
    uint8_t tempTopic = udpBuffer[1];
    uint8_t tempLen = udpLen;
    udpBuffer[0] = 'A';
    udpBuffer[1] = udpBuffer[2];
    udpLen = 2;
    sendPacket(false);
    udpBuffer[1] = tempTopic;        
    udpLen = tempLen;
}


//----------------------------------------------------------
//  DESCRIZIONE: Elabora il pacchetto appena ricevuto
//               in udpBuffer lungo udpLen
//----------------------------------------------------------
void processPacket(void)
{
    uint8_t type = udpBuffer[0];
 
    if (('P' == type) and (udpLen > 1)){ 
 
        sendACK();
        publish(); 
     
    }else if (('S' == type) and (3 == udpLen)){ 
 
        uint8_t topic = udpBuffer[1];
        sendACK();
        subscribe(topic, udpADDR);
     
    }else{
        // nothing
    }
}


//----------------------------------------------------------
//  DESCRIZIONE: Scarta pacchetto ricevuto fuori specifiche
//----------------------------------------------------------
void dropIncomingPacket(void)
{
    while (Udp.available()) { Udp.read(udpBuffer, MAXBUF); }
}


//----------------------------------------------------------
//  DESCRIZIONE: Verifica se un IP e` autorizzato a
//               trasmettere al server
//----------------------------------------------------------
bool authorized(uint8_t n)
{
    uint8_t nAuthorized = sizeof(IPAUTHORIZED);
    for (uint8_t i=0;  i<nAuthorized;  i++)
    {
        if (n == IPAUTHORIZED[i]) { return true; }
    }
    return false;
}


//----------------------------------------------------------
void setup(void)
{
    byte MAC[] = { 0xFA, 0xCC, 0x10, 0xCA, 0xFF, 0xEE };
    IPAddress ipLocale(192, 168, 1, 30);
    Ethernet.init(10);                  // pin CS
    Ethernet.begin(MAC, ipLocale);
    Ethernet.setRetransmissionCount(10);
    Ethernet.setRetransmissionTimeout(30);
    Udp.begin(9000);
}


//----------------------------------------------------------
void loop(void)
{
    if (Ethernet.hardwareStatus() != EthernetNoHardware)
    {
        udpLen  = Udp.parsePacket();
        udpADDR = Udp.remoteIP()[3];
        if ((udpLen > MAXBUF) || !authorized(udpADDR)){ 
        
            dropIncomingPacket(); 
         
        }else if (udpLen > 0){
             
            Udp.read(udpBuffer, MAXBUF);
            processPacket();
 
        }else{
            // nothing
        }
    }
}

Al momento c'è ancora lo scarico verso il router. Ho provato anche il loopback 127.0.0.1 e non vedo anomalie, ma non sono sicuro di cosa faccia realmente il W5100 (nel senso se realmente si richiude su se stesso).


Per quanto riguarda la parte WiFi viene smazzata tra ESP01 e router. Gli ESP01 fanno da ponte UDP verso le seriali degli gli arduini. Non uso il firmware originale AT, ma uno sketch nuovo con le librerie 'ESP8266WiFi.h' e 'WiFiUdp.h'. Quindi c'è di mezzo anche un ulteriore protocollo seriale e lo sketch è più incasinato. Prevede un comando da Arduino per settare l'IP locale dell'ESP all'accensione, e gli avvisi dell'ESP verso Arduino sullo stato della connessione WiFi.

Prima che venga accusato il protocollo seriale... :smiling_imp: sia gli Arduini (attraverso ESP01+seriale) che il PC (attraverso socket UDP) vedono arrivare esattamente gli stessi byte, giusti o maggiorati che siano. Lo scarico verso il router ha eliminato totalmente la ricezione di pacchetti maggiorati, segno che il problema nasce nel W5100.


Per quanto riguarda timeout/ritrasmissioni ARP, che farebbero collassare la comunicazione e il server in caso ad esempio di una ventina di unità destinatarie che vanno off line, la soluzione compromesso è cancellarle dalla lista dei destinatari, così il rallentamento c'è solo una volta al momento della prima irraggiungibilità. Poi sta alle unità sottoscriversi nuovamente.

Sugli ESP01 gira questo codice uguale per tutti. Il delay(30) nel loop serve per far respirare i processi in background (e limita i pacchetti UDP elaborabili a 33 al secondo).

//**********************************************************************************
//  Firmware ESP01 UDP bridge
//  Questo sketch rende un ESP8266 ESP01 un ponte WiFi per pacchetti UDP
//  La parte bassa dell'indirizzo IP locale viene ricevuta via seriale
//  all'accensione. Finche' non viene ricevuta non effettua la connessione.
//  Gli altri parametri della rete sono hardcoded, la porta UDP e` la 9000.
//**********************************************************************************
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>


const char*    ssid = ".........";
const char*    PASSWORD = "........................";
IPAddress      IPLOCALE(192, 168, 1, 0);  // zero finale provvisorio
IPAddress      GATEWAY(192, 168, 1, 1);
IPAddress      SUBNET(255, 255, 255, 0);


// buffer ricezione UDP

const uint16_t MAXBUF = 24;
uint16_t       packetSize;
char           packetBuffer[MAXBUF];


// buffer seriale ricezione da Arduino

uint8_t        serBuffer[MAXBUF];
uint8_t        serType;
uint8_t        serADDR;
uint8_t        serLen;


uint32_t       now;

WiFiUDP        Udp;

//**********************************************************************************
//   L E V E L    3
//**********************************************************************************

void readPacket() { Udp.read(packetBuffer, MAXBUF); }



//**********************************************************************************
//   L E V E L    2
//**********************************************************************************

bool connesso() { return WiFi.status() == WL_CONNECTED; }



void dropIncomingPacket() { while (Udp.available()) { readPacket(); } }



void sendInfoLink(char ch){
// Spedisce messaggio stato link ad Arduino su seriale
//
//.-----.-----.-----.-----.-----.-----.-----.
//|  1  |  1  |  1  | 'D' |  0  |  0  |  4  | 
//'-----'-----'-----'-----'-----'-----'-----'
//
//.-----.-----.-----.-----.-----.-----.-----.
//|  1  |  1  |  1  | 'U' |  0  |  0  |  4  | 
//'-----'-----'-----'-----'-----'-----'-----'
//
    Serial.write(1);
    Serial.write(1);
    Serial.write(1);
    Serial.write(ch);
    Serial.write(0);
    Serial.write(0);
    Serial.write(4);
}



//**********************************************************************************
//   L E V E L    1
//**********************************************************************************

void processMessage(){

    if (connesso() && ('T' == serType)){
    
        IPAddress IPDEST(192, 168, 1, serADDR);
        Udp.beginPacket(IPDEST, 9000);
        Udp.write(serBuffer, serLen);
        Udp.endPacket();

    }else if ('S' == serType){ 
        
        IPLOCALE[3] = serADDR; 
        
    }else{
        // nothing
    }
}



bool ricevutoSerial(){
// Riceve messaggio seriale da Arduino
//.-----.-----.-----.-----.------.-----.-----.     .-----.-----.
//|  1  |  1  |  1  | 'T' | addr | len |     | ... |     |  4  | 
//'-----'-----'-----'-----'------'-----'-----'     '-----'-----'
//                                      ^____UDP pkt_____^
//
//.-----.-----.-----.-----.------.-----.-----.
//|  1  |  1  |  1  | 'S' | addr |  0  |  4  | 
//'-----'-----'-----'-----'------'-----'-----'
//
    static uint8_t  s = 0;
    static uint32_t t;
    uint8_t         i;

    if ((0 != s) && (now-t > 50))      { s = 0;       }

    while (Serial.available())
    {
        uint8_t rx = Serial.read();
        switch (s)
        {
        case 0:
            if (1 == rx)               { t = now;  i = 0;  s = 1; }
        break;
        case 1:
            if (1 == rx)               { s = 2;       }
            else                       { s = 0;       }
        break;
        case 2:
            if (1 == rx)               { s = 3;       }
            else                       { s = 0;       }
        break;
        case 3:
            serType = rx;
            s = 4;            
        break;
        case 4:
            serADDR = rx;
            s = 5;
        break;
        case 5:
            serLen = rx;
            if      (0 == serLen)      { s = 7;       }
            else if (serLen > MAXBUF)  { s = 0;       }
            else                       { s = 6;       }
        break;
        case 6:
            serBuffer[i] = rx;
            i++;
            if (i == serLen)           { s = 7;       }
        break;
        case 7:
            s = 0;
            if (4 == rx)               { return true; }
        break;
        default:
            // nothing
        break;
        }
    }

    return false;
}



void processPacket(){
// Spedisce pacchetto UDP ad Arduino su seriale
//
//.-----.-----.-----.-----.------.-----.-----.     .-----.-----.
//|  1  |  1  |  1  | 'R' | addr | len |     | ... |     |  4  | 
//'-----'-----'-----'-----'------'-----'-----'     '-----'-----'
//                                      ^____UDP pkt_____^
    Serial.write(1);
    Serial.write(1);
    Serial.write(1);
    Serial.write('R');
    Serial.write(Udp.remoteIP()[3]);
    Serial.write((uint8_t)packetSize);
    for (uint8_t i=0;  i<packetSize;  i++){ 
    
        Serial.write(packetBuffer[i]); 
    }
    Serial.write(4);
}



bool ricevutoUDP(){

    if (connesso()){
    
        packetSize = Udp.parsePacket();
        if      (packetSize > MAXBUF)  { dropIncomingPacket();       }
        else if (packetSize > 0)       { readPacket();  return true; }
        else                           {                             }
    }
    return false;
}



void linkUpdate(){
// connette e riconnette WiFi

    static uint8_t  s = 0;
    static uint32_t t0;
    static uint32_t t1 = 0;

    if ((0 == s) && (0 != IPLOCALE[3])){

        WiFi.mode(WIFI_STA);
        WiFi.config(IPLOCALE, GATEWAY, SUBNET);
        WiFi.begin(ssid, PASSWORD);
        t0 = now;
        s = 1;
    
    }else if ((1 == s) && connesso()){ 
    
        sendInfoLink('U');
        s = 2; 
    
    }else if ((1 == s) && (now-t0 > 4000)){ 
        
        WiFi.disconnect();  
        s = 0; 

    }else if ((2 == s) && !connesso()){ 
    
        WiFi.disconnect(); 
        sendInfoLink('D');
        s = 0; 

    }else if ((2 != s) && (now-t1 > 250)){ 
        
        t1 = now;  
        Serial.print("*****"); 

    }else{
        // nothing
    }
}


//**********************************************************************************
//   L E V E L    0
//**********************************************************************************

void setup(){

    Serial.begin(38400);
    while (!Serial) { delay(100); }
    Udp.begin(9000);
}



void loop(){

    now = millis();
    linkUpdate();
    if (ricevutoUDP())    { processPacket();  }
    if (ricevutoSerial()) { processMessage(); }
    delay(30);
}

La trasmissione seriale ESP/Arduino è realizzata con questo protocollo:

Discussione interessante. Seguo.

maubarzi:
...errare umanum est :wink:

Grazie, com'è umano lei.. :smiley:

Il link era stato messo perchè si parlava delle richieste ARP su protocollo UDP, da li è emerso che non viene gestita una tabella ARP e quindi ogni richiesta UDP ne fa una per il mac address perchè dismenticotta.
E' proprio questa richiesta ARP, secondo me, la causa dei problemi perchè ha timeout e ritrasmissione in caso di fail.

Io però continuo a perplimermi perché non vedo come le due cose siano in relazione, ossia come mai eventuali ritardi o timeout nelle richieste ARP possano portare a "mischiare" il contenuto di pacchetti differenti e quindi mandare a B roba di un pacchetto per A.

Non ho ben capito come sia "incastrata" sta cosa nel resto dei discorsi su MQTT (scusate ma tra lavoro ed ora attesa di un piccolo intervento non ho proprio molta voglia di analizzare tanti codici, ma vorrei comunque capire quanto possibile) e mi/vi chiedo:

  1. sicuri che dopo i vari udp.write() ci sia sempre anche un udp.endPacket() finale?
  2. se lo fa, come credo/spero, in invio dopo gli udp.endPacket() il buffer lo si svuota sempre? Ad esempio proprio nella "sendPacket()" magari?

Non ho ben capito come sia "incastrata" sta cosa nel resto dei discorsi su MQTT

Questo aspetto si può ignorare, non è influente ai fini del discorso pacchetti, è solo saltato fuori tentando di realizzare un'architettura publish/subscribe (che di per sè funziona).

  1. sicuri che dopo i vari udp.write() ci sia sempre anche un udp.endPacket() finale?

A partire dai dati da trasmettere preparati in queste variabili:

uint8_t        udpBuffer[24];
uint8_t        udpADDR;
uint16_t       udpLen;

La funzione 'sendPacket' originale era questa:

void sendPacket()
{
    IPAddress ipDest(192, 168, 1, udpADDR);
    Udp.beginPacket(ipDest, 9000);
    Udp.write(udpBuffer, udpLen);
    Udp.endPacket();
}

Mi aspettavo che in quanto protocollo UDP con la 'endPacket' i giochi fossero conclusi (a buon fine o meno era un altro discorso), e che una nuova 'beginPacket' creasse un pacchetto nuovo su cui scrivere con nuove write ecc.

Invece la 'endPacket' può fallire con codice di ritorno zero.

In tal caso i dati scritti con le write precedenti restano nel buffer di trasmissione del W5100, e le nuove write del nuovo pacchetto si accodano.

Quindi ho scritto una nuova 'sendPacket', per "scaricare" il buffer in caso di fallimento verso un altro IP denominato "cestino" (un PC con uno script Python per visualizzare i byte dei pacchetti UDP in arrivo). Ora si comporta tutto come previsto, i pacchetti "anomali" (cioè il pacchetto precedente più due zeri finali nuovi) arrivano solo al cestino, mentre alle unità arrivano solo quelli corretti.

void sendPacket()
{
    IPAddress ipCestino(192, 168, 1, 2);

    IPAddress ipDest(192, 168, 1, udpADDR);
    Udp.beginPacket(ipDest, 9000);
    Udp.write(udpBuffer, udpLen);
    uint8_t result = Udp.endPacket();
 
    if (0 == result)  // scarico pacchetto non trasmesso con una nuova trasmissione
    {
        Udp.beginPacket(ipCestino, 9090);
        Udp.print('\0');
        Udp.print('\0');
        Udp.endPacket();
    }
}
  1. se lo fa, come credo/spero, in invio dopo gli udp.endPacket() il buffer lo si svuota sempre? Ad esempio proprio nella "sendPacket()" magari?

E questa era infatti la mia domanda: c'è un modo più corretto per svuotare il buffer di trasmissione del W5100 visto che in caso di fallita trasmissione il pacchetto "rimane in canna"?

Poi, ma questo è marginale riguardo alla domanda anche se crea ben altri problemi, c'è il discorso del flooding di richieste ARP (e relativi timeout) che il W5100 invia ogni volta che vuole trasmettere qualcosa a qualcuno, compreso un pacchetto UDP che per sua natura non dovrebbe invece avere alcuna latenza. Con netdiscovery ho verificato che trasmettendo tre pacchetti UDP a tre destinazioni, ogni volta vedo 9..12 richieste ARP provenire dall'indirizzo del W5100.

Come diceva maubarzi scriversi una propria cache ARP sarebbe semplice, se il W5100/libreria permettessero di conoscere/impostare il MAC address del corrispondente.

Claudio_FF:
Quindi ho scritto una nuova 'sendPacket', per "scaricare" il buffer in caso di fallimento verso un altro IP denominato "cestino" (un PC con uno script Python per visualizzare i byte dei pacchetti UDP in arrivo). Ora si comporta tutto come previsto, i pacchetti "anomali" (cioè il pacchetto precedente più due zeri finali nuovi) arrivano solo al cestino, mentre alle unità arrivano solo quelli corretti.

Grazie, ora ho capito meglio.
Ma per me non ha alcun senso che un pacchetto UDP di pochi byte possa "fallire" l'invio, tantomeno per problemi di timeout in una rete locale che suppongo non sia satura, a meno di problemi hardware sulla rete stessa (un cavo difettoso, uno switch o router "impazzito" o quantomeno da resettare proprio per fargli ricreare la sua ARP table). Almeno non mi è mai capitato di sentire una cosa del genere, e spero che il W5100 non sia realmente così bacato...

Ho un W5100 "di scorta", questo weekend per curiosità se riesco faccio qualche prova comparativa con il mio software che gira correttamente su WeMos.

Ho un W5100 "di scorta", questo weekend per curiosità se riesco faccio qualche prova comparativa con il mio software che gira correttamente su WeMos.

Il fenomeno si vede solo se il W5100 trasmette pacchetti verso più destinazioni quando una diventa irraggiungibile. Tra l'altro con le impostazioni di default (8 ritrasmissioni con 200ms di timeout) significa un blocco di 1.6 secondi per ogni unità remota non trovata.