docdoc credo possa dormire tranquillo
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...
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.