Closed atesin closed 4 years ago
you can do the same with a WiFiClient object. start with the WebClient example
sorry, now I understand. your goal is to send all data in one TCP packet. Are you sure every CIPSEND is send immediately as a TCP packet and the esp8266 doesn't wait a little for more data?
yes... i want to send all dynamic data at once in one AT+CIPSEND command to minimize overhead and delays... instead of send
AT+CIPSEND=0,24
>HTTP/1.1 200 OK\r\n
SEND OK
AT+CIPSEND=0,31
>Server: arduino/esp\r\n\r\n
SEND OK
AT+CIPSEND=0,27
>your ip address is:
SEND OK
AT+CIPSEND=0,11
>192.168.1.4
SEND OK
about the question, i find very difficult another thread are sending another command at the same time when i send the cipsend command throught uart tx line, because i think arduino is single threaded, so i find unlikely collissions or blockings may occur,.. so basicaly what i do is send all related commands in block sequence
var string1
var string2
...
AT+CIPSEND=(link),(sum_of_string_lengths) // ok
find(">") // esp listening, send strings now
string1
string2
...
SEND OK // receive status
but for all this to work i need to know the link id ... i know maybe is hidden due some kind of encapsulation, but that is this question is for
or maybe can implement some esp "prepared print" function/object like sql prepared statement or OB php functions or some varargs print, idk
the library has buffering. data are not send in so small pieces. turn on debug and you will see. you can set the buffer size at compile time or use a larger buffer in your code for example with my StreamLib BufferedPrint class https://github.com/jandrassy/WiFiEspAT#advanced-use
thanks for your interest
yes i know it has buffers, 32 bytes you said
maybe i deviate the conversation, i want return to "constructing a response summing all string lengths and passing them all directly to esp"
i am trying to develop a webserver, with a home page (just an html form to enter wifi auth) and some "scripts" that take the submitted form to processing (try access internet with wifi credentials to contact server in background)
my http headers are about 100 bytes stored in progmem (depending on status code, browser caching options, encodings, redirections, etc), the home page itself is about 300 bytes in progmem (including head tags, prevent favicon/robot/etc request, style, javascripts, frames, etc), there are a couple more pages stored in progmem as "F()" strings too, the "dynamic" data is stored in char arrays ... and the server must assemble the full response depending the situation
i think we can pass responses directly to esp from progmem or char arrays (several bytes), without any intermediate buffers for simplicity and optimization (the Stream.print() accept many parameter types)... all we have to know to pass data are the strings itselves (stored wherever), its total lengths, and the LINK ID
maybe adding some byte id() {return linkId;}
function =D
... and in the future something like new BufferedPrint(client)
object with addString(pstr)
and printAll()
functions
... or maybe something like Client::printAll(varargs...)
or Client::printf(types, params...)
=D
this library will not support 'open' command. it always handles the complete AT command and response before returning control to user code. and it is for beginners to use with the well know WiFi library API, which doesn't support the proposed approach
you still didn't check how many TCP packets are send (with WireShark for example). you could use faster UART baud rate to deliver the data to AT firmware faster so it can join them to less packets.
and you can do your web server with WiFiClient efficient. if you send a 500 bytes buffer, it will be CIPSEND as 500 bytes. and you can send a complete progmem string from progmem.
StreamLib's classes have printf
it is better to serve large html as file and request the data then with AJAX. see for example the WebServer in my Ragulator project
i agree with you about encapsulation of AT commands, i understood it is easier for beginners (like me) and more error proof, as i mentioned earlier
but i am not really talking about sending a large string directly from progmem to esp, but a composite one (many little strings, from wherever) in one single passthrough at command, to optimize process minimizing serial traffic and ram usage due buffers
in the meanwhile i will try to add the following function to library and tell you how it resulted (i know is not a very elegant solution but will do with the hope it serves me)
// WiFiClient.h
public:
uint8_t id();
// WiFiClient.cpp
uint8_t WiFiClient::id() {
return linkId;
}
my basic test was successful... the response took about 20ms ... it still have to handle request, caching options and block auto favicon req
(... but it overflown and dropped about the half of request headers :( ... did it use the new ATv1.7 2kb esp passive mode serial cache? )
esp serial console
Serial enabled, searching for ESP-01...
Waiting for communication with WiFi module
Waiting for connection to WiFi.
Connected to WiFi network, To access the server, enter "http://192.168.218.161/" in your browser.
new incoming client: 192.168.218.151
GET / HTTP/1.1
Host: 192.168.218.161
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36 OPR/65.0.3467.48
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate
Accept-Language: es-ES,es;q=0.9
client served and disconnected: 192.168.218.151
port 80 telnet
[root@testSrv ~]# echo foobarbaz | nc 192.168.218.161 80
HTTP/1.1 200 OK
Server: WiFiEspAT
your ip address is: 192.168.218.218
the sketch
// uses esp8266 LoBo firmware
// https://github.com/loboris/ESP8266_AT_LoBo
// uses patched WiFiEspAT library
// https://github.com/jandrassy/WiFiEspAT/issues/5#issuecomment-560554236
//--------- macros ------------
// to simulate Serial.println() concatenation, example: echo(F("ratio = ") _ val _ '%');
#define echo(args) do{Serial.print(args);Serial.println();}while(0)
#define _ );Serial.print(
// similar to F() but for "_P" pgmspace functions, example: strlen_P( P("foo") )
#define P(args) (PGM_P) F(args)
// for functions could accept or return F() strings, examples:
// - func(F("foo")) --> void func(S fstr){Serial.println(fstr);}
// - S sta = status() --> S status(){return F("OK"));}
// - S fstr = F("upper string");
#define S const __FlashStringHelper *
//--------- global --------
#include "SoftwareSerial.h"
#include <WiFiEspAT.h>
SoftwareSerial esp(7, 8); // RX, TX
WiFiServer server(80);
//--------- setup --------
void setup()
{
Serial.begin(9600); // enable serial console
/*DEBUG*/ while (!Serial);
delay(1000); // don't check serial connection to prevent stucks
echo(F("Serial enabled, searching for ESP-01...")); // echo
esp.begin(19200); // enable software serial, connected to esp-01
WiFi.init(esp);
Serial.print(F("Waiting for communication with WiFi module"));
while (WiFi.status() == WL_NO_MODULE)
{
delay(2000);
Serial.print('.');
}
// waiting for connection to Wifi network set with the SetupWiFiConnection sketch
Serial.print(F("\r\nWaiting for connection to WiFi"));
while (WiFi.status() != WL_CONNECTED) {
delay(2000);
Serial.print('.');
}
server.begin();
IPAddress ip = WiFi.localIP();
echo(F("\r\nConnected to WiFi network, To access the server, enter \"http://") _ ip _ F("/\" in your browser."));
}
//---------- loop -----------
void loop()
{
WiFiClient client = server.available();
if (!client)
return;
while (!client.status()); // i found calling remoteIP() too early gives 0.0.0.0
IPAddress ip = client.remoteIP();
echo(F("new incoming client: ") _ ip);
while (client.available())
Serial.print((char) client.read()); // flush the incoming data to the console, whatever it is (just for testing)
// process dymanic content
S headers = F("HTTP/1.1 200 OK\r\nServer: WiFiEspAT\r\n\r\n");
S htdoc = F("your ip address is: ");
char ipAddr[16]; // ip address could be variable length
sprintf_P(ipAddr, P("%u.%u.%u.%u"), ip[0], ip[1], ip[2], ip[3]);
// send response
byte link = client.id(); // this is the patch i am testing!
esp.print(F("AT+CIPSEND="));
esp.print(link);
esp.print(',');
esp.println(strlen_P((PGM_P) headers) + strlen_P((PGM_P) htdoc) + strlen(ipAddr));
esp.find('>');
esp.print(headers);
esp.print(htdoc);
esp.print(ipAddr);
esp.find("OK\r\n");
client.stop();
echo(F("client served and disconnected: ") _ ipAddr);
}
(... but it overflown and dropped about the half of request headers :( ... did it use the new ATv1.7 2kb esp passive mode serial cache? )
passive mode is activated in WiFi.init(). you can turn on debug to see all AT commands
what overflown, this?
while (client.available())
Serial.print((char) client.read());
EDIT: with SoftwareSerial use 9600 baud
i am very happy the test resulted ok =D ... there are however some other details outside this test
according you said, passive mode should be enabled with WiFi.init(esp);
as seen in setup() function (undocumented)
esp.begin(19200); // enable software serial, connected to esp-01
WiFi.init(esp);
until i suspected data loss, debug shows i was wrong (but i think is ok to have doubts and check them)
below are the headers sent by opera browser version 65.0.3467.48 (you can also find the user-agent header) ... i could clearly see the lib reads data in 32 bytes chunks =D
i did some researches (but not tests) and concluded the fastest reliable speed for SoftwareSerial is 19200, while Serial by convention could be as fast as 115200 ... so for this test i switched 19200 for swserial and 115200 for hwserial witn no problem... the response now took 926ms
GET / HTTP/1.1
Host: 192.168.218.161
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36 OPR/65.0.3467.48
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate
Accept-Language: es-ES,es;q=0.9
meanwhile in the serial console
Serial enabled, searching for ESP-01...
esp INFO: soft reset
esp> AT+RST ...sent
esp> OK ...ignored
esp>
#qX⸮z⸮⸮ ...ignored
esp> 1⸮⸮a⸮⸮P!⸮N⸮ ⸮!⸮K{{{{⸮⸮⸮#bRJ{{{⸮r⸮)⸮!kG⸮⸮Ja0 !H⸮H ...ignored
esp> ##⸮c⸮⸮ ...ignored
esp> ready ...matched
esp> ATE0 ...sent
esp> ATE0 ...ignored
esp> OK ...matched
esp> AT+CIPMUX=1 ...sent
esp> OK ...matched
esp> AT+CIPRECVMODE=1 ...sent
esp> OK ...matched
esp> AT+CWMODE? ...sent
esp> +CWMODE:1 ...matched
esp> OK ...matched
Waiting for communication with WiFi moduleesp INFO: wifi status
esp> AT+CIPSTATUS ...sent
esp> STATUS:5 ...matched
esp> OK ...matched
Waiting for connection to WiFiesp INFO: wifi status
esp> AT+CIPSTATUS ...sent
esp> STATUS:5 ...matched
esp> OK ...matched
.esp> WIFI CONNECTED ...ignored
esp> WIFI GOT IP ...ignored
esp INFO: wifi status
esp> AT+CIPSTATUS ...sent
esp> STATUS:2 ...matched
esp> OK ...matched
esp INFO: begin server at port 80
esp> AT+CIPSERVERMAXCONN=1 ...sent
esp> OK ...matched
esp> AT+CIPSERVER=1,80 ...sent
esp> OK ...matched
esp> AT+CIPSTO=60 ...sent
esp> OK ...matched
esp INFO: STA IP query
esp> AT+CIPSTA? ...sent
esp> +CIPSTA:ip:"192.168.218.161" ...matched
esp> +CIPSTA:gateway:"192.168.218.1" ...matched
esp> +CIPSTA:netmask:"255.255.255.0" ...matched
esp> OK ...matched
Connected to WiFi network, To access the server, enter "http://192.168.218.161/" in your browser.
esp> 0,CONNECT ...processed
esp> +IPD,0,458 ...processed
esp INFO: incoming linkId 0
esp INFO: status of link 0
esp> AT+CIPSTATUS ...sent
esp> STATUS:3 ...matched
esp> +CIPSTATUS:0,"TCP","192.168.218.151",52709,80,1 ...matched
esp> OK ...matched
new incoming client: 192.168.218.151
esp INFO: get data on link 0
esp> AT+CIPRECVDATA=0,32 ...sent
esp> +CIPRECVDATA,32 ...matched
esp> OK ...matched
esp INFO: got 32 bytes on link 0
GET / HTTP/1.1
Host: 192.168.21esp INFO: get data on link 0
esp> AT+CIPRECVDATA=0,32 ...sent
esp> +CIPRECVDATA,32 ...matched
esp> OK ...matched
esp INFO: got 32 bytes on link 0
8.161
Connection: keep-alive
Cesp INFO: get data on link 0
esp> AT+CIPRECVDATA=0,32 ...sent
esp> +CIPRECVDATA,32 ...matched
esp> OK ...matched
esp INFO: got 32 bytes on link 0
ache-Control: max-age=0
Upgradeesp INFO: get data on link 0
esp> AT+CIPRECVDATA=0,32 ...sent
esp> +CIPRECVDATA,32 ...matched
esp> OK ...matched
esp INFO: got 32 bytes on link 0
-Insecure-Requests: 1
User-Agenesp INFO: get data on link 0
esp> AT+CIPRECVDATA=0,32 ...sent
esp> +CIPRECVDATA,32 ...matched
esp> OK ...matched
esp INFO: got 32 bytes on link 0
t: Mozilla/5.0 (Windows NT 6.1; esp INFO: get data on link 0
esp> AT+CIPRECVDATA=0,32 ...sent
esp> +CIPRECVDATA,32 ...matched
esp> OK ...matched
esp INFO: got 32 bytes on link 0
Win64; x64) AppleWebKit/537.36 (esp INFO: get data on link 0
esp> AT+CIPRECVDATA=0,32 ...sent
esp> +CIPRECVDATA,32 ...matched
esp> OK ...matched
esp INFO: got 32 bytes on link 0
KHTML, like Gecko) Chrome/78.0.3esp INFO: get data on link 0
esp> AT+CIPRECVDATA=0,32 ...sent
esp> +CIPRECVDATA,32 ...matched
esp> OK ...matched
esp INFO: got 32 bytes on link 0
904.97 Safari/537.36 OPR/65.0.34esp INFO: get data on link 0
esp> AT+CIPRECVDATA=0,32 ...sent
esp> +CIPRECVDATA,32 ...matched
esp> OK ...matched
esp INFO: got 32 bytes on link 0
67.48
Accept: text/html,applicaesp INFO: get data on link 0
esp> AT+CIPRECVDATA=0,32 ...sent
esp> +CIPRECVDATA,32 ...matched
esp> OK ...matched
esp INFO: got 32 bytes on link 0
tion/xhtml+xml,application/xml;qesp INFO: get data on link 0
esp> AT+CIPRECVDATA=0,32 ...sent
esp> +CIPRECVDATA,32 ...matched
esp> OK ...matched
esp INFO: got 32 bytes on link 0
=0.9,image/webp,image/apng,*/*;qesp INFO: get data on link 0
esp> AT+CIPRECVDATA=0,32 ...sent
esp> +CIPRECVDATA,32 ...matched
esp> OK ...matched
esp INFO: got 32 bytes on link 0
=0.8,application/signed-exchangeesp INFO: get data on link 0
esp> AT+CIPRECVDATA=0,32 ...sent
esp> +CIPRECVDATA,32 ...matched
esp> OK ...matched
esp INFO: got 32 bytes on link 0
;v=b3
Accept-Encoding: gzip, deesp INFO: get data on link 0
esp> AT+CIPRECVDATA=0,32 ...sent
esp> +CIPRECVDATA,32 ...matched
esp> OK ...matched
esp INFO: got 32 bytes on link 0
flate
Accept-Language: es-ES,esesp INFO: get data on link 0
esp> AT+CIPRECVDATA=0,10 ...sent
esp> +CIPRECVDATA,10 ...matched
esp> OK ...matched
esp INFO: got 10 bytes on link 0
;q=0.9
esp INFO: sync
esp> AT+CIPSTATUS ...sent
esp> STATUS:3 ...matched
esp> +CIPSTATUS:0,"TCP","192.168.218.151",52709,80,1 ...matched
esp> OK ...end of list
esp> AT+CIPRECVLEN? ...sent
esp> +CIPRECVLEN:0,0,0,0,0 ...matched
esp> OK ...matched
esp INFO: close link 0
esp> AT+CIPCLOSE=0 ...sent
esp> 0,CLOSED ...processed
esp INFO: closed linkId 0
esp> OK ...matched
client served and disconnected: 192.168.218.151
thanks for all
you read the request byte by byte so the internal buffer is used. for ATmega with less then 2303 bytes of SRAM the buffer is 32 bytes large. for all other MCUs it is 64 bytes. the size is customizable as described in README.md
I run your test skech and the response was displayed in browser
i found calling remoteIP() too early gives 0.0.0.0
this is still valid? I don't have this problem (with Espressif AT 1.7.1)
i found calling remoteIP() too early gives 0.0.0.0
client.remoteIP();
always gave me 0.0.0.0, i had to look at the library code fo figure out what could be going on ... anyway, it run me flawless with while(!client.status());
note: the use of WiFi.init()
to enable passive mode is undocumented
note: the use of
WiFi.init()
to enable passive mode is undocumented
what is the problem with that? you can't avoid init(). and the doc everywhere has that the lib uses passive mode. where else would it be set?
relax ... you're right
i'm still very happy the test results .. now i am playing with webservers and webservices =D
thanks for all
void loop()
{
WiFiClient peer = webserver.available();
if (peer)
espParseRead(peer);
}
// util
void espSendLength(WiFiClient peer, int len)
{
esp.print(F("AT+CIPSEND="));
esp.print(peer.id());
esp.print(',');
esp.println(len);
esp.find("\r\n>");
}
int Fstrlen(S fString)
{
return strlen_P((PGM_P) fString);
}
// functions
void espParseRead(WiFiClient peer)
{
//*DEBUG*/ echo(F("|httpParseRead()"));
while (!peer.status()); // wait until connection ready
char input[32];
input[peer.readBytesUntil(' ', input, 31)] = '\0';
/*DEBUG*/ echo(F("|httpParseRead() input = ") _ input);
// EACH DAEMON IS RESPONSIBILE FOR FLUSH AND CLOSE ESP BUFFER
// GET: parse browser request as server
if ( httpd && !strcmp_P(input, P("GET")) )
httpParseRequest(peer, input); // recycle input
// HTTP/*: parse webserver response as client
else if ( !strncmp_P(input, P("HTTP/"), 5) )
;// httpParseResponse(link, dataLen, input); // recycle input
// flush and close
else
{
while (peer.available())
peer.read();
peer.stop();
echo(F("transaction finished"));
}
}
void httpParseRequest(WiFiClient browser, char *input)
{
//*DEBUG*/ echo(F("|httpParseRequest()"));
// read url
input[browser.readBytesUntil(' ', input, 31)] = '\0';
/*DEBUG*/ echo(F("|httpParseRequest() input = ") _ input);
if ( !strcmp_P(input, P("/mac")) )
htmlMac(browser, input);
else
httpSend404(browser);
// flush and close
while (browser.available())
browser.read();
browser.stop();
echo(F("transaction finished"));
}
void htmlMac(WiFiClient browser, char *input)
{
//*DEBUG*/ echo(F("|htmlMac()"));
S html1 = F(
"<html><head><link rel='icon' href='http://0.0.0.0/'/></head><body>\n"
"<iframe name='hf' style='display:none;'></iframe>\n"
"<p>dispositivo encontrado, conectese nuevamente a su wifi para continuar</p>\n"
"<button onclick='go();'>Go!</button>\n"
"<script type='text/javascript'>\n"
" //setInterval(function(){\n"
" function go(){\n"
" open('http://my.public.webserver/webservice/register-mac.php?"
);
espGetMac(input);
/*DEBUG*/ echo(F("|htmlMac() input = ") _ input);
S html2 = F(
"','hf');\n"
"// },2000);\n"
" }\n"
"</script>\n"
"</body></html>\n"
);
espSendLength(browser, Fstrlen(httpHeader200()) + Fstrlen(html1) + strlen(input) + Fstrlen(html2));
esp.print(httpHeader200());
esp.print(html1);
esp.print(input);
esp.print(html2);
esp.find(OK);
}
I added getLinkId
. It returns WIFIESPAT_NO_LINK if the client is unconnected. The valid range is from 0 to WIFIESPAT_LINKS_COUNT.
thanks
i know the trick i did to send compound data in one single command is a little dirty,. and until it works maybe is not the best ...
i think the right, clean, native, and a more elegant solution without "low-level" commands outside the library, instead a bunch of "cipsend(link, sum_length); print, print, print, print" etc. would be simply a "printAll(varargs...)" or some like "printf("format", params...)" ... but as my programming knowledge is limited, i had to be creative to achieve my goal :)
but in the other hand, as i think you are a pro developer., i knew you would do it a lot better than me... thanks again =D
I added two new functions client.write(file) and client.write(callback). the second enables to write data with one AT+CIPSENDEX. see the README and the new SDWebServer example
you are the best
hi... i miss the function WiFiClient.getLinkId() , it was very useful to build a compound response, then send total sizes, then send it all together though the link
in addition, currently i am trying to store WiFiClients in a global array as shown in AdvancedChatServer.ino example... when a client connects, i get it with WiFiServer.accept() to send a welcome/motd/status message, and iterate the global array to store client for later uses.... if i could get linkId i could use it as array index to directy store/retrieve client without the need for iterations !!! (and log events, and print server connections status, etc) =D
currently i know it can be done anyway with no need to modify the library, but in a very unoptimized way:
... i would like linkId back to avoid doing this
maybe you dropped getLinkId() to discourage its use in favor of bufferedPrint for the sake of robustness, but besides "compound prints" linkId could be (actually potentially IS) very useful and versatile for many other possible applications...
i think each user is responsibile to make use of the library in their most convenient way, you haven't to worry about that... i want to motivate you to give us more tools and flexibility to do better applications
thanks for your time and efforts
I had to remove access to linkId because it was not unique. if a WiFiClient has still data stored to read but is already closed the linkId is used by other connection.
you have the write(callback) to send with one CIPSEND.
surely are not unique, they hasn't to be either
... i understand linkId's are not a client property, but actually managed by the SERVER ... they are like reutilizable "pipes", "subprocesses" or "listening workers" that server opens to "link" tcp socket everytime the client sends a packet.... anyway, with this in mind it still can be useful for a lot of things
imagine a modular webserver running in esp8266 with your library.... a single webpage that requests a basic .css "file", some .js and maybe a .json, another basic page in a frame and maybe a little image
if "Connection: close" header is defined (maybe because is more complicated to implement persistent connections), when client retrieves the full webpage, every request will open a new pipelined "link", maybe even recycling them at some point, actually there is no way to manage the links.... so every link will be different and random althought all they are to render the same web page and comes from the same client, so can't rely on them to "identify" clients (what is an esp8266 "client" anyway... )
furthermore, imagine a telnet or simple messaging protocol..... the client can set transfer mode as character, block, line, delimiters or whatever... every "command" could be chunked and each one will open a new link, althought all they belong to the same sequence, command, client or the same session
anyway.... tcp is connectionless so there is no point in try to track links .... but if you really need it, the application itself can manage some kind of "session id" or "cookie" with the traffic, and you can track SESSIONS instead, for example (nothing to do with links)
so, bring back getLinkId() anyway pleeease xD ... it can still be very useful for may things
thanks for your patience
server.accept() returns a client (new TCP connection) only once. the AdvancedChatServer example shows how to manage individual TCP connections. additionally every concurrent TCP connection has unique remoteIP+remotePort.
I added a new example PagerServer. this is an example which doesn't care about individual clients.
you can add
uint8_t getLinkId() {
if (stream)
return stream->getLinkId();
return WIFIESPAT_NO_LINK;
}
in WiFiClient.h to get the linkId. but again, it can return the same linkId for two different connections
linkId is not a hash, what's the problem with two different sequential connections coming through the same link?
linkId is not a hash, what's the problem with two different sequential connections coming through the same link?
it depends how you want to use it. if as index in an array of WiFiClients you could overwrite a client which still has data to read even if it is closed in AT firmware
you are right
anyway, taking some precautions, with getLinkId() the read clients process can be greatly optimized, take the AdvancedChatServer.ino main loop for example (last commit prior 2020-10-08)
according your are talking, currently when a new client connects and there are "zombie" clients and no more free slots, you have to hold it until the next main loop(), because you try to store the new client BEFORE eat whole data from zombie client and release slot... this could be avoided with a bigger array
or with getLinkId() maybe the process could be rewritten this way, ensuring data processing and releasing slots before store a new client, just in 1 single clients loop:
could this be done?, would be better? (noticing the users)
hi ... i would like to get the client link id, to write dynamic content directly to esp someway like this...
how can i get client link id? (the example below is the simpler solution i thought, is a little difficult to me since i am not so skilled in programming)