Invio e gestione pacchetti via ESP8266

Buongiorno a tutti :slight_smile:

Ancora una volta ho bisogno del vostro aiuto.

Ho da poco acquistato dei moduli WiFi ESP-01 per un progetto che richiede la trasmissione pressoché continua di dati tra dei client e un server (la base, un piccolo pc con un software ad hoc e router wifi connesso via cavo ethernet, riceve continuamente dati da ognuno dei client = tanti Arduino Mega dotati ognuno di un proprio modulo ESP-01).

Per trasmettere i dati, non faccio altro che effettuarne l'invio attraverso scritture sulla porta seriale 1 di Arduino, dirette al modulo WiFi, che funziona con dei comandi chiamati "AT", come ben saprete :slight_smile:

Fin qui, nessun problema :slight_smile: Ora, ciò che voglio che cercare di fare è effettuare una sorta di controllo su ciò che mi arriva in seriale dal modulo (cioè le varie response) per verificarne il corretto funzionamento ed intervenire in caso di problemi. Faccio un esempio per essere -spero- un po' piu' chiaro:

Un classico sistema per verificare il funzionamento del modulo è scrivere sulla seriale: AT e premere invio. Se tutto funziona correttamente, dopo un attimo il modulo dovrebbe rispondere OK.

Ora vengo al dunque: il modulo ha un funzionamento molto particolare perché lavora a 115200 baud e, inoltre, formatta le proprie risposte con un ritorno carrello e a capo \r\n ovvero l'opzione "entrambi" sul monitor seriale, diversamente non apparirà alcun dato sul monitor.

Ciò che vorrei fare, quindi, è creare un programma che mi vada a "selezionare" la parte di risposta del modulo, in grado quindi di far fronte a questa complessa formattazione che, per comodità, riepilogo di seguito:

AT

OK

Di seguito allego il codice (scopiazzato su internet) che utilizzo per scrivere e monitorare le risposte del modulo:

void setup()
{
    Serial.begin(9600);
    Serial1.begin(115200);
}
void loop()
{
    while (Serial1.available()) {
        Serial.write(Serial1.read());
    }
    while (Serial.available()) {
        Serial1.write(Serial.read());
    }
}

mentre questo è il codice che impiego per inviare comandi al modulo (senza monitorare le risposte):

void setup()
{
  Serial.begin(9600);
  Serial1.begin(115200); // your esp's baud rate might be different
  
 
  //espSend("AT+RST\r\n",1000, DEBUG);
  delay(3000);
  espSend("AT+CIPMUX=0\r\n", 1000, DEBUG);
  espSend("AT+CIPSTART=\"UDP\",\"0\",0,1336,2\r\n", 1000, DEBUG);
  espSend("AT+CIPMUX=1\r\n", 1000, DEBUG);
  espSend("AT+CIPSEND=0,5,\"192.168.0.255\",1336\r\n", 1000, DEBUG);
  espSend("R1_UP", 1000, DEBUG);
}
 
void loop()
{
    //LETTURA SERIALI
    while (Serial1.available()) {
        Serial.write(Serial1.read());
    }
    while (Serial.available()) {
        Serial1.write(Serial.read());
    }
}
 
String espSend(String command, const int timeout, boolean debug)
{
    String response = "";
    
    Serial1.print(command); // send the read character to the esp8266
    
    long int time = millis();
    
    while( (time+timeout) > millis())
    {
      while(Serial1.available())
      {
        
        // The esp has data so display its output to the serial window 
        char c = Serial1.read(); // read the next character.
        response+=c;
      }  
    }
    
    if(debug)
    {
      Serial.print(response);
    }
    
    return response;
}

Il collegamento va eseguito in questo modo:

utilizzando un adattatore ESP collego il pin VCC a 5V, GND a ground, Rx e tx del modulo, in maniera invertita, rispettivamente a TX1 e RX1 di Arduino.

Ringrazio in anticipo tutti coloro i quali vorranno aiutarmi :slight_smile:
Spero di essere stato chiaro ma se non lo fossi stato, vi chiedo di rispondere a questo post, elencando le vostre perplessità :slight_smile:

Grazie ancora e buona giornata!

Up!

Scusa, ma qual è la domanda?

SukkoPera:
Scusa, ma qual è la domanda?

Ahah hai ragione, forse sono stato un po' prolisso :slight_smile:

In parole povere, vorrei capire come fare per creare uno sketch che legga la seriale (come già fa il mio) e riesca ad identificare le risposte del modulo, escludendo le altre stringhe che scrive in automatico.

Faccio un altro esempio per essere piu' chiaro (spero):

quando viene ricevuto un pacchetto TCP o UDP dal modulo, esso ne trascrive il contenuto sulla seriale di Arduino in questo modo:

+IPD,24: Prova pacchetto ricevuto

Dove "+IPD," è una stringa costante scritta dal modulo e il numero successivo rappresenta la lunghezza della stringa ricevuta (in questo caso la frase prova pacchetto ricevuto è lunga 24 caratteri, spazi inclusi).

In pratica vorrei riuscire ad isolare solo il campo dati, cioè salvare in una variabile dello sketch il contenuto del pacchetto, ovvero "Prova pacchetto ricevuto".

Spero di essere riuscito a spiegarmi :slight_smile:

Grazie mille!

C'è qualche motivo particolare per cui non puoi usare la libreria WiFiEsp?

:o Non ne conoscevo l'esistenza ::slight_smile:

Il problema è che le ho già dato un'occhiata e a quanto pare non supporta ancora la trasmissione e ricezione con TCP... per caso conosci altre librerie simili che la supportino?

Vorrei rimanere sempre su quest'idea: comandare ESP con Arduino, non scrivere un programma che giri su ESP, perché non sarebbe adatto al mio scopo.

Grazie ancora!

E se non supportasse TCP a cosa servirebbe? Col solo UDP non vai molto lontano :wink:

Vedi questo esempio: WiFiEsp/WebClient.ino at master · bportaluri/WiFiEsp · GitHub.

Per curiosità, perché girare su ESP non andrebbe bene?

Risposta piu' che appropriata, dato il best-effort di UDP :wink:

Però giuro che guardando l'esempio non riesco a comprendere come inviare messaggi tramite TCP. Ti spiego come lo farei manualmente, assieme all'idea che sta dietro al mio progetto :slight_smile:

Ciò che voglio realizzare è un sistema di trasmissione dati per impostare e gestire una partita di Laser Tag. Ho già realizzato sia i fucili, che il codice, le shield ad hoc con tutti i componenti, ecc...

L'idea è che ogni fucile di qualunque squadra sia direttamente connesso via WiFi, attraverso un modulo ESP, sfruttando il protocollo TCP (non UDP, con UDP mi arriva un pacchetto su 20 trasmessi...) ad una "base" che non è nient'altro che un pc sul quale gira un software che si occupa specificatamente di intercettare questi pacchetti, comprenderne la provenienza e svolgere determinate azioni in base a ciò che viene ricevuto.

Ecco quindi il meccanismo spiegato in dettaglio. Supponiamo di essere nel caso piu' semplice di tutti: una partita 1vs1, ovvero giocata da 2 soli fucili appartenenti rispettivamente a 2 diverse squadre.

Li chiameremo A e B, per comprendere meglio la situazione.

Ad inizio gioco, la base invia sia ad A che a B un pacchetto che chiameremo "game_start", appena viene ricevuto dal modulo ESP, Arduino ne legge il contenuto e fa cominciare la partita. A questo punto, un timer controlla l'andamento del gioco e, allo scadere del tempo, lo termina (inviando alla fine un pacchetto di fine gioco alla base).

Durante il gioco però vengono totalizzate uccisioni dai rispettivi fucili dei giocatori, uccisioni che di per sé vengono già registrate da Arduino inizialmente all'interno di una matrice ed, infine, all'interno di un file scritto su una SD. La registrazione del colpo inferto è ad opera di Arduino che, attraverso un sensore Ir, riceve un codice identificativo del killer. Ogni volta che ricevo un codice, viene incrementata la variabile relativa al contatore delle uccisioni subite da quel giocatore.

Ciò che vorrei fare è, oltre a loggare i dati in questa maniera, effettuare un invio di punteggi, statistiche, conferme di uccisioni, ecc... tra la base e i vari fucili, per questo vorrei utilizzare TCP, al fine di garantire integrità e correttezza dei dati.

Se dovessi fare una cosa del genere manualmente, ecco come sarebbe il mio pseudo-codice:

//SETUP
espSend("AT", 1000, DEBUG);	//attendo la risposta OK del modulo WiFi
if(horicevuto "OK")
//LOOP
espSend("AT+CIPSTART=\"TCP\",\"IPDESTINAZIONE\",PORTADEST,0\r\n", 1000, DEBUG);
if(connessione riuscita, cioè ho un altro OK)
	espSend("AT+CIPSEND=\"LUNGHEZZASTRINGADAINVIARE\"\r\n", 1000, DEBUG);
	espSend("STRINGACONTENENTEMESSAGGIO\r\n", 1000, DEBUG);
else(connessione non riuscita, ci riprovo)

Facendo girare un programma localmente su ESP non saprei come passare i dati partita tra lui e Arduino e -credo- che tutto risulterebbe molto piu' complicato da programmare e gestire. E' anche vero che però ridurrei di molto il carico di lavoro di Arduino...

Ogni idea è ben accetta :slight_smile:

Grazie ancora!

Se fai un programma su ESP, Arduino lo elimineresti proprio, e tutto quel che fa lo farebbe l'ESP. Diciamo che se non hai bisogno di troppi pin e non ti serve nemmeno l'ADC, allora passare su ESP standalone ti semplificherebbe un po' la vita, visto che ha anche molta più RAM ed un clock ben più veloce.

Comunque quella libreria altro non implementa che quella è ormai l'interfaccia di rete standard di Arduino, dovresti fare qualcosa del genere, ovvero quel che trovi al termine del setup() nell'esempio di cui sopra:

if (client.connect(server, 80)) {
    Serial.println("Connected to server");
    // Make a HTTP request
    client.println("GET /asciilogo.txt HTTP/1.1");
    client.println("Host: arduino.cc");
    client.println("Connection: close");
    client.println();
}

Questo effettua di fatto una richiesta HTTP, ma sei libero di inviare quel che ti pare. Con quel che vedi poi nelle prime righe del loop, dovresti ricevere e gestire la risposta del server.

Ciao SukkoPera :slight_smile:

Intanto volevo ringraziarti per l'immenso aiuto che mi hai dato, non sarei mai riuscito a trovare quella libreria senza di te :wink:

Mi scuso se rispondo solo ora ma purtroppo mi è toccato trascorrere qualche giorno in ospedale per un piccolo intervento, ora va un po' meglio per fortuna.

Ho provato inizialmente ad utilizzare il codice per l'invio e la ricezione di pacchetti tramite UDP. Ok, non era esattamente quello che volevo poiché UDP non mi dà la stessa certezza di ricezione e integrità dei dati ricevuti, però per iniziare va benissimo. Ho fatto quindi riferimento a questo codice, che ho lievemente modificato cambiato porta locale e aggiungendo la possibilità di discriminare alcuni tipi di pacchetti in base al loro nome:

/*
 WiFiEsp example: WiFi UDP Send and Receive String

 This sketch wait an UDP packet on localPort using a WiFi shield.
 When a packet is received an 'ACK' packet is sent to the client on port remotePort.

 For more details see: http://yaab-arduino.blogspot.com/p/wifiesp-example-client.html
*/


#include <WiFiEsp.h>
#include <WiFiEspUdp.h>

// Emulate Serial1 on pins 6/7 if not present
#ifndef HAVE_HWSERIAL1
#include "SoftwareSerial.h"
SoftwareSerial Serial1(6, 7); // RX, TX
#endif
int vite=5;
char ssid[] = "nomeretewifi";            // your network SSID (name)
char pass[] = "passwordretewifi";        // your network password
int status = WL_IDLE_STATUS;     // the Wifi radio's status

unsigned int localPort = 1336;  // local port to listen on

char packetBuffer[255];          // buffer to hold incoming packet
char ReplyBuffer[] = "ACK";      // a string to send back

WiFiEspUDP Udp;

void setup() {
  // initialize serial for debugging
  Serial.begin(9600);
  // initialize serial for ESP module
  Serial1.begin(115200);
  // initialize ESP module
  WiFi.init(&Serial1);

  // check for the presence of the shield:
  if (WiFi.status() == WL_NO_SHIELD) {
    Serial.println("WiFi shield not present");
    // don't continue:
    while (true);
  }

  // attempt to connect to WiFi network
  while ( status != WL_CONNECTED) {
    Serial.print("Attempting to connect to WPA SSID: ");
    Serial.println(ssid);
    // Connect to WPA/WPA2 network
    status = WiFi.begin(ssid, pass);
  }
  
  Serial.println("Connected to wifi");
  printWifiStatus();

  Serial.println("\nStarting connection to server...");
  // if you get a connection, report back via serial:
  Udp.begin(localPort);
  
  Serial.print("Listening on port ");
  Serial.println(localPort);
}

void loop() {

  // if there's data available, read a packet
  int packetSize = Udp.parsePacket();
  if (packetSize) {
    Serial.print("Received packet of size ");
    Serial.println(packetSize);
    Serial.print("From ");
    IPAddress remoteIp = Udp.remoteIP();
    Serial.print(remoteIp);
    Serial.print(", port ");
    Serial.println(Udp.remotePort());

    // read the packet into packetBufffer
    int len = Udp.read(packetBuffer, 255);
    if (len > 0) {
      packetBuffer[len] = 0;
    }
    Serial.println("Contents:");
    Serial.println(packetBuffer);

    

    if(strcmp(packetBuffer,"TEMPO")==0){
      int tempo = packetBuffer;
    Serial.println("Tempo:"); 
    Serial.print(tempo);
    }
    else
    Serial.println("ERRATO");

    // send a reply, to the IP address and port that sent us the packet we received
    Udp.beginPacket(Udp.remoteIP(), Udp.remotePort());
    Udp.write(ReplyBuffer);
    Udp.endPacket();
  }
}


void printWifiStatus() {
  // print the SSID of the network you're attached to:
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());

  // print your WiFi shield's IP address:
  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address: ");
  Serial.println(ip);

  // print the received signal strength:
  long rssi = WiFi.RSSI();
  Serial.print("signal strength (RSSI):");
  Serial.print(rssi);
  Serial.println(" dBm");
}

Guardandolo, infatti, si potrà notare che nel caso in cui venga ricevuto un pacchetto contenente la stringa TEMPO , solo se essa risulta scritta in maiuscolo, il codice la riconoscerà e andrà a salvarla nella variabile tempo. Ma a che pro tutto ciò?

L'idea è quella di analizzare ogni volta il pacchetto ricevuto, discriminarne il contenuto e salvarlo all'interno di apposite variabili, a seconda di ciò che ho ricevuto. Ho aggiunto in seguito questo frammento di codice:

for(int k=0;k<15;k++){
	valore[k]=0;
	tipo[k]=0;
}

for(int i=0;i<15;i++){
	if(packetBuffer[i]=='|'){
		for(int j=0;j<i;j++)
			tipo[j]=packetBuffer[j];
	}
for(int j=0;j<15;j++)
valore[j]=packetBuffer[i+1];
Serial.print(valore[j]);
}

//visualizzo
Serial.print("Tipo:");
for(int i=0;i<15;i++)
Serial.print(tipo[i]);
Serial.println("Valore:");
for(int j=0;j<15;j++)
Serial.print(valore[j]);

ciò che dovrebbe fare (e purtroppo fa solo in maniera parzialmente corretta, salvando solo la prima parte di stringa) è separare la stringa ricevuta nel buffer in due sottostringhe (ma a me occorrerebbe lo facesse con tutti i tipi di campi).

In questo codice ho supposto la stringa di lunghezza fissa 15, ma dovrebbe capire da solo quanto è lunga in realtà e scomporla nei pezzi corretti.

Faccio un esempio:

Vorrei passare dalla base ai singoli fucili una serie di informazioni; mettiamo caso che voglia inviare i parametri di configurazione della partita quindi: durata del gioco, tipo gioco, nome giocatore, punteggio max, ecc...

Per cui faccio inviare alla base una maxi stringa tipo questa e ne misuro la lunghezza:

GAME_TYPE:dmsq|GAME_DURATION:23|PLAYERS:12|P_NAME:Gabriele|P_SQ:red

Ricevuta la stringa, la faccio separare in pezzi, dato un carattere separatore (che in questo caso ho definito come | ma può benissimo essere un altro) che mi permetterà di capire il tipo di dato da salvare e il valore ad esso annesso, il tutto al fine di effettuarne la memorizzazione all'interno di specifiche variabili (che già esistono) nel codice di Arduino, in modo che esso possa riutilizzarli per poter inizializzare il gioco.

Utilizzerei questo sistema non solo per l'inizializzazione della partita ma anche per inviare statistiche di gioco e punteggi durante la partita :slight_smile:

Pensi sia una soluzione ottimale o suggerisci qualcosa di meglio? Grazie mille per l'aiuto :wink:

Spero di essermi spiegato bene ma nel caso non fossi stato chiaro, chiedete pure precisazioni!

Buona Giornata!

Ci sono un paio di firmware per ESP8266 che lo trasformano di fatto in un WiFi/seriale.
Se non ricordo male tu spari sulla seriale che va all'Esp8266 i tuoi dati.
Dall'altro lato il PC dovra collegarsi con un socket alla porta messa a disposizione dell'ESP8266 e ricevere i dati che arrivano. Visto che hai più tag penso che dovrai configurare una porta diversa per ogni tag.

Tornando invece alla prima questione, cioè isolare il messaggio dalla risposta, credo che potresti usare
Serial.readStringUntil(":") in modo che leggi (e butti) fino al due punti
e poi usi il Serial.read o daccapo il Serial.readStringUntil(0x0D) solo che in questo caso ti rimane un 0x0A nel buffer, credo.

Francamente non capisco perché hai usato UDP quando ti ho passato un esempio di TCP, ma va bene. Se non ti è chiaro qualcosa chiedi pure, ma non va molto oltre fare una connect() e quindi fare delle print() su un oggetto Client.

In ogni caso, zoomx ti ha proposto alcune buone soluzioni al tuo problema, io ne aggiungo una: ho scritto tempo fa una funzione che splitta una stringa come vuoi tu, la trovi qua: Arduino-Sensoria/utils.cpp at master · SukkoPera/Arduino-Sensoria · GitHub, è la funzione int splitString (char *str, char **parts, size_t n, const char sep). Idealmente si usa così:

char *rc[2];
if (splitString (reply, rc, 2, '|') == 2) {
  // ...
}

E quindi in rc è come se avessi le varie stringhe separate. Occhio però che è solo "un'illusione", in quanto sono solo puntatori all'interno della stringa originaria, che dovrai quindi mantenere. Occhio anche che la stringa originaria verrà modificata, sostituendo i separatori con dei '\0'. Non sarà il massimo, ma è per risparmiare RAM.

Vedi tu se ti può essere utile.