WiFiEsp.h Server e Client - Distinguere richieste da risposte

Ciao makers, sto cercando di mixare un webserver e webclient via wifi, vorrei che Arduino postasse periodicamente ad un server remoto delle letture ma anche che risponda a comandi esterni.

Sono partito da WebServer.ino e WebClientRepeating.ino

Funziona: quando parte il webserver ascolta in attesa di nuovi clienti e risponde con una pagina di prova,
MA dall'altra parte, quando Arduino webclient invia dati al server remoto la risposta viene interpretata come un nuovo client, quindi lo rigira all'Arduino webserver.

Quindi, come faccio a capire se il messaggio in arrivo è una risposta ad una richiesta inviata da Arduino e una richiesta da un nuovo client al server Arduino?
E' possibile capire se è una risposta o una chiamata?

E' una questione di connessioni multiple?

Thanks

WiFiEspClient client = server.available();
  if (client) {
    Serial.println("New client");
    // an http request ends with a blank line
    boolean currentLineIsBlank = true;
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        Serial.write(c);

Immagino il problema sia qui, che prende riga per riga e le gira al server, dovrei aspettare che il messaggio sia terminato e poi in base al contenuto capire se devo girarlo al server o se è la risposta ad una mia chiamata. E' praticabile?

Scusa, ma non stai parlando già di questa cosa in QUESTO thread? :o

Cortesemente spiegami perché non dovrei chiudere questo tuo thread che mi sembra proprio un "cross-posting" (... non permesso da REGOLAMENTO, punto 13) della problematica che avevi nell'altro e spiegami perché non hai continuato a discutrere della cosa in quel thread che parla sempre di avere Client e Server contemporaneamente.

Resto in attesa ... ::slight_smile:

Guglielmo

No... non direi, in quel thread tentavo usare l'esp come microcomputer autonomo, passando i dati a Arduino, notare
#include <WiFiClient.h>
#include <ESP8266WebServer.h>

In questo voglio usare l'esp come slave, comandato direttamente da Arduino, usando #include "WiFiEsp.h" che a quanto capisco è un layer più alto degli AT Command.

Il mio scopo è sempre lo stesso ma l'approccio totalmente diverso quindi credevo che la questione meritasse un nuovo thread;
tanto più che ora il problema non è la comunicazione ma distinguere i messaggi in ingresso (tra nuove chiamate e risposte a vecchie richieste in uscita).

Se il thread è sbagliato fammi sapere come agire.

voidbrain:
Il mio scopo è sempre lo stesso ma l'approccio totalmente diverso quindi credevo che la questione meritasse un nuovo thread; tanto più che ora il problema non è la comunicazione ma distinguere i messaggi in ingresso (tra nuove chiamate e risposte a vecchie richieste in uscita).

Ok, grazie mille per la spiegazione ...
... con questo chiarimento direi allora che puoi continuare qui. :slight_smile:

Guglielmo

Ma sbaglio, o la distinzione chiamata/risposta viene data dalla porta utilizzata?
Tipicamente 80 oppure 8080 per le richieste, è una effimera differente da queste due per la risposta?

il server viene settato prima della funzione setup

WiFiEspServer server(80);

Il client nella chiamata http

client.connect(server, 80)

Se mi bastasse cambiare una delle due porte sarebbe una pacchia.
Così semplice? Provo ad impostare uno dei due sulla porta 8080? Stasera provo.

Usando Esp8266 autonomamente, potendo quindi usare ESP8266WebServer.h, se non ho capito male attribuiva un ID ad ogni client per poter distinguere le diverse chiamate, è per quello che mi chiedevo se non fosse questione di connessioni multiple.

Se ricordo bene la connessioni multiple sono gestite cambiando la effimera

Le due righe che hai postato dovrebbero essere corrette, una garanzia un server che risponde alla 80. La seconda interroga un server usando la 80
Servirebbe sapere se è come il client imposta l'effimera

Avevo sperato che bastasse impostare il server in listening sulla 8080, così i 2 "canali" sarebbero stati distinti, evidentemente ho capito male.

Servirebbe sapere se è come il client imposta l'effimera

Mi puoi spiegare cosa intendi per effimera? Avevo capito fosse una sorta di ID della richiesta ma nel codice non mi pare mi permetta di gestire gli id.

Spulciando nella libreria trovo che il client invia tramite

sendData(uint8_t sock, const uint8_t *data, uint16_t len)

quindi forse l'effimera è il numero di socket? Sto solo facendo una grande confusione?

Forse la soluzione è UDP?

infatti riconosce le connessioni in base al socket

Metti programma

#include "WiFiEsp.h"

const char* ssid         = "ssid";
const char* password     = "password";
int status = WL_IDLE_STATUS;     // the Wifi radio's status
int reqCount = 0;
char webServer[] = "www.voidbrain.net";
String url = "/temp/grover/ajax/moduli/api/redneck/endpoint";
String params = "";

WiFiEspServer ArduinoServer(80);
WiFiEspClient webClient;

void setup() {
  Serial.begin(115200);
  Serial1.begin(115200);
  WiFi.init(&Serial1);

  if (WiFi.status() == WL_NO_SHIELD) {
    Serial.println("WiFi shield not present");
    while (true);   // don't continue
  }
  while ( status != WL_CONNECTED) { // attempt to connect to WiFi network
    Serial.print("Attempting to connect to WPA SSID: ");
    Serial.println(ssid);
    // Connect to WPA/WPA2 network
    status = WiFi.begin(ssid, password);
  }
  Serial.println("You're connected to the network");
  printWifiStatus();

  ArduinoServer.begin();
}

void loop() {
  while (webClient.available()) { // risposta dal server remoto. se il server Arduino è attivo non funziona
    char c = webClient.read();
    Serial.write(c);
  }

  //loadServer();
  loadClient();
}

void loadServer(){
  // listen for incoming clients
  WiFiEspClient serverConnection = ArduinoServer.available();
  if (serverConnection) {
    Serial.println("New client");
    // an http request ends with a blank line
    boolean currentLineIsBlank = true;
    while (serverConnection.connected()) {
      if (serverConnection.available()) {
        char incomingMessage = serverConnection.read();
        // if you've gotten to the end of the line (received a newline
        // character) and the line is blank, the http request has ended,
        // so you can send a reply
        if (incomingMessage == '\n' && currentLineIsBlank) {
          Serial.println("Sending response");
          
          // send a standard http response header
          // use \r\n instead of many println statements to speedup data send
          serverConnection.print(
            "HTTP/1.1 200 OK\r\n"
            "Content-Type: text/html\r\n"
            "Connection: close\r\n"  // the connection will be closed after completion of the response
            "Refresh: 20\r\n"        // refresh the page automatically every 20 sec
            "\r\n");
          serverConnection.print("<!DOCTYPE HTML>\r\n");
          serverConnection.print("<html>\r\n");
          serverConnection.print("<h1>Hello World!</h1>\r\n");
          serverConnection.print("Requests received: ");
          serverConnection.print(++reqCount);
          serverConnection.print("
\r\n");
          serverConnection.print("Analog input A0: ");
          serverConnection.print(analogRead(0));
          serverConnection.print("
\r\n");
          serverConnection.print("</html>\r\n");
          break;
        }
        if (incomingMessage == '\n') {
          // you're starting a new line
          currentLineIsBlank = true;
        }
        else if (incomingMessage != '\r') {
          // you've gotten a character on the current line
          currentLineIsBlank = false;
        }
      }
    }
    // give the web browser time to receive the data
    delay(10);

    // close the connection:
    serverConnection.stop();
    Serial.println("Client disconnected");
  }
}

void loadClient(){
  while (Serial.available()) {
    params = Serial.readString(); // "azione=accendi&sensore_id=12"
    httpRequestToWebServer();
  }
}

// this method makes a HTTP connection to the server
void httpRequestToWebServer() {
  webClient.stop(); // close any connection before send a new request, this will free the socket on the WiFi shield

  if (webClient.connect(webServer, 80)) {
    Serial.println("Connecting...");
    params = "azione=accendi&sensore_id=12"; // temporaneo
    webClient.println(String("GET ") + url + String("?") + params + String(" HTTP/1.1"));
    webClient.println(String("Host: ")+webServer);
    webClient.println("Connection: close");
    webClient.println();
    Serial.println(String("url: ")+webServer+url+ String("?") + params);
  } else {
    Serial.println("Connection failed");
  }
}


void printWifiStatus() {
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());
  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address: ");
  Serial.println(ip);
  long rssi = WiFi.RSSI();
  Serial.print("Signal strength (RSSI):");
  Serial.print(rssi);
  Serial.println(" dBm");
}

Se loadServer(); è commentato vedo le risposte alle chiamate del client.
Se invece accendo anche il server, ogni messaggio viene dirottato a lui come nuova connessione, anche se era una risposta al client.

Altra domanda, se posso: vorrei passare una querystring, ma se la scrivo a mano funziona, se invece provo a passarla dall'input del serial monitor no. C'è forse un qualche carattere invisibile NL CR che scombina la querystring? O è perchè il server riceve carattere per carattere e non aspetto che Serial1 abbia finito?
Se nello stesso punto stampo la stringa vedo il contenuto correttamente

Anche il Server Arduino deve gestire dati passati in querystring, vedo che se chiamo http://192.168.1.4/?azione=accendi&motore=2 il server risponde, devo ora capire come estrapolarmi chiavi e valori

Pensavo di distinguere chiamate/risposte in base al contenuto, anche se non credo sia molto efficiente.
Ma l'approccio del server:

while (serverConnection.connected()) {
if (serverConnection.available()) {
char incomingMessage = serverConnection.read();
...

se non capisco male è char-by-char, mentre per distinguere le chiamate devo avere il contenuto completo per poi cercare una qualche stringa identificativa prima di assegnarlo al server o al client.

Una chiamata al server è una stringa terminante con \n
Una risposta al client è una pagina web, con header e content e a capi vari

che poi se fosse possibile usare due seriali diverse sarei a cavallo (ho più ESP) ma alla Serial1 io attribuisco WiFi, non server o client (mi vien da dire che li colleghi in automatico) e non so se c'è modo di distinguerli.

voidbrain:
che poi se fosse possibile usare due seriali diverse sarei a cavallo (ho più ESP) ma alla Serial1 io attribuisco WiFi, non server o client (mi vien da dire che li colleghi in automatico) e non so se c'è modo di distinguerli.

Qui mi sono perso

Sono totalmente ignorante in materia e non so dove informarmi, ma a me viene una curiosità:
Nel circuito che stai utilizzando tu entrambi (server e client) devono poter fare domande? Perché se così fosse non sarebbe (ripeto che NON NE SO NULLA) possibile distinguerle soltanto dal destinatario? O dal fatto che le stia leggendo o scrivendo? Perché se io che leggo sono il server, penso, posso leggere soltanto domande, alle quali scrivo risposte, mentre se io che leggo sono client posso leggere solo risposte mentre scrivo solo domande.
Cosa non ho capito?

Standardoil:
Qui mi sono perso

Era solo un mio vaneggio, nel codice, semplificato:

WiFiEspServer ArduinoServer(80);
WiFiEspClient webClient;
void setup() {
  [b]WiFi.init(&Serial1);[/b]
}

void loop() {
  WiFiEspClient serverConnection = [b]ArduinoServer.available()[/b]; 
    if (serverConnection) {
      Serial.println("New client");

  while ([b]webClient.available()[/b]) {
    char c = webClient.read();
    Serial.write(c);
  }
}

quindi evidentemente gestisce lui server/client e sa che arrivano dal canale wifi (cioè Serial1)
se avessi un qualche modo per dirgli che il server comunica sulla Serial1 e il client sulla Serial2 non avrei il problema che me li confonde.... credo. Boh.

Comunque cercando di capire la libreria WifiEspServer.cpp trovo:

WiFiEspClient WiFiEspServer::available(byte* status)
{
	// TODO the original method seems to handle automatic server restart

	int bytes = EspDrv::availData(0);
	if (bytes>0)
	{
		LOGINFO1(F("New client"), EspDrv::_connId);
		[b]WiFiEspClass::allocateSocket(EspDrv::_connId);[b][/b][/b]
		WiFiEspClient client(EspDrv::_connId);
		return client;
	}

    return WiFiEspClient(255);
}

Ma allora attribuisce socket diversi (_connId) ? E quindi perché interpreta la risposta al client come una nuova chiamata?

È dalla pausa pranzo che me lo domando, ma non trovo la risposta

Standardoil:
È dalla pausa pranzo che me lo domando, ma non trovo la risposta

Ti prego di non desistere :cold_sweat:

Silente:
Sono totalmente ignorante in materia e non so dove informarmi, ma a me viene una curiosità:
Nel circuito che stai utilizzando tu entrambi (server e client) devono poter fare domande? Perché se così fosse non sarebbe (ripeto che NON NE SO NULLA) possibile distinguerle soltanto dal destinatario? O dal fatto che le stia leggendo o scrivendo? Perché se io che leggo sono il server, penso, posso leggere soltanto domande, alle quali scrivo risposte, mentre se io che leggo sono client posso leggere solo risposte mentre scrivo solo domande.
Cosa non ho capito?

Qual è la differenza tra un client che scrive una domanda e un server che invia una risposta?

O lo capisci dall'id, perchè il client gli aveva attribuito un "segnaposto", (ed è il metodo che spero di trovare) oppure dal contenuto del messaggio (almeno credo).
Nel momento in cui ho una comunicazione in entrata, per capire se è una domanda o risposta, devo riceverla tutta e analizzare il contenuto e in base a quello posso capirlo. Come ho detto non credo sia la soluzione più performante ma il problema principale è che a quanto capisco, la logica dello sketch di partenza è che il server scatta per primo (c'è un messaggio e lui dice "è mio, è mio!") e cerca di interpretarlo.
Poi visto che il server come chiamata si aspetta solo una string (del tipo Google) quando sente un a capo dice "ah ok, fine della comunicazione".
La risposta al client è più lunga, piena di "a capo" e quindi il server crede di avere finito ogni volta che ne trova uno

La mia DOMANDA DA IGNORANTE é leggermente diversa.
Esempio:
In classe, il professore e uno studente.
Il professore sta facendo lezione.
Ora io sono il professore
Visto che sto facendo lezione so che tutto quello che sento non possono che essere domande (perché io sono il conoscitore di tutte le risposte), e, informazione ausiliaria, so che tutto quello che dico sono risposte, perché sto spiegando.
Sempre nella stessa aula. Ora sono lo studente.
In quanto studente so che tutto quello che io sento sono risposte, per il semplice fatto che io le sento e che é il professore a darle.

Ora la mia domanda é: possiamo fare si che una delle due nostre macchine sia il professore, e che quindi quello che sente, per il fatto che lei lo sente, sia una domanda?
E possiamo far si che l'altra macchina sia lo studente cje tutto quello che sente é una risposta, solo perché lo sente?
Ripeto che non so se ho capito bene o se mi sono spiegato

Temo che l'esempio del professore non combaci fino in fondo... provo ad analizzarlo, così mi capisco anch'io.

Arduino deve periodicamente inviare dei report al server. La risposta del server è ok, ricevuto.
(Il professore spiega, lo studente risponde sì, ho capito)

Da server poi devo in ogni momento poterlo chiamare per fare una domanda o dare un comando.
(lo studente comanda, il professore obbedisce (siamo proprio come nel mondo reale)) + risponde con un ok, eseguito.

Vista da fuori la sequenza è
--> messaggio A (report)
<-- messaggio B (ok)
--> messaggio C (report)
<-- messaggio D (ok)
<-- messaggio E (istruzione)
--> messaggio F (ok)
Il problema è che il sistema non ha memoria, riguarda la sequenza, questa volta senza spiegazione:
--> messaggio A
<-- messaggio B
--> messaggio C
<-- messaggio D
<-- messaggio E
--> messaggio F
Quando arrivano il messaggio D o E so solo che ho un messaggio in entrata.
Per capire se è un report o un ok oppure un'istruzione o un ok (e ho evitato casi come A,C,D,B... la latenza è subdola) o li ricevo per intero e li analizzo (dispendioso e come già detto la logica è diversa) o riesco a capire l'id di partenza che B, D e F si portano dietro (e so che in teoria è fattibile perché "sotto" lo usa).