Licence: Creative Commons: Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) Legal Text
If you are able, please Support me on Patreon and/or subscribe to my Youtube channel (instructional videos)
- What does it do?
- Where does it fit in "The IOT hierarchy of needs"?
- Prerequisites
- Installation
- Raising Issues
- Known Problems
- Usage
- API
H4AsyncTCP provides two simple-to-use classes which handle all aspects of Asynchronous TCP Rx/Tx. While it can be used "standalone" by experienced coders, its main purpose is to function as the base of all other H4Async< xxx > libraries (see below).
For this reason, the Usage/API documentation here is deliberately minimal.
Name | Provides | Notes | |
---|---|---|---|
H4 | Scheduler/Async Timers - core of all multitasking functions | ||
H4Tools | '32/'8266 HAL and utility functions | ||
H4AsyncTCP | Asynchronous TCP RX/TX | 👈 YOU ARE HERE | |
H4AsyncMQTT | Asynchronous MQTT c/w auto-reconnect and full QoS0/1/2 | ||
H4AsyncHTTP | Asynchronous remote GET / POST etc | ||
H4AsyncWebServer | Asynchronous Web Server + fast webSockets + SSE | ||
H4Plugins | Fully-featured IOT Apps multitasking framework |
The library has been tested using the following firmware. Please do not even think about raising anhy issues unless you have the following correctly installed.
N.B.
Note that PlatformIO is not in the above list. Many folk do use it, but you will need to create your own installation configuration. I am currently in discussions to add a PIO install to the standard H4 Installer. If you are able to help / contribute to this, please get in touch!
Soon* all H4 libraries will use the H4 Installer. This ensures that all versions match and that other additional special functions are included e.g. Addition of optimised board definitions in H4Plugins...
...Until that's ready, install this library manually by downloading the zip file and using the ArduinoIDE to "add zip library". (Luckily, it has no extra tasks that would require the full H4 installer)
- = Don't ask :)
-
As with all H4xxx libraries, please make sure you have read all the relevant documentation relating to the issue and watched any videos on the Youtube channel (instructional videos). Please also subscribe to the channel for notifications of news and updates.
-
If you still think there is a problem, then join the Facebook H4 Support / Discussion group and report the issue briefly there. This is because I visit the group every day, whereas I do not have time to visit dozens of github repos every day. Furthermore, it alerts other users to potential problems and allows a rapid initial assessment.
-
If there is a genuine issue then you will be referred to Raising H4/H4Plugins issues after which you are advised to create a full github issue report.
-
Failing to make an initial report in the Facebook H4 Support / Discussion group and simply starting with a github issue, or failing to include all of the information required in Raising H4/H4Plugins issues is likely to result in a long delay before it gets picked up.
The basic structure of your code will be:
- Instantiate
H4AsyncClient
object - Declare event handlers for connect/disconnect/RX data/error
- Connect to remote server
- Synchronously TX some data...
- ...and/or asynchronously RX some data
- onConnect - void function
- onDisconnect - void function
- onRX -
void myRxFunction(const uint8_t* data, size_t len);
- onError -
bool myErrorFunction(int e,int i);
e
is the error and will be one of the values below.
i
occasionaly provides additional infor for some errors.
// Errors passed up directly from LwIP
{ERR_OK,"No error, everything OK"},
{ERR_MEM,"Out of memory error"}, // -1
{ERR_BUF,"Buffer error"},
{ERR_TIMEOUT,"Timeout"},
{ERR_RTE,"Routing problem"},
{ERR_INPROGRESS,"Operation in progress"}, // -5
{ERR_VAL,"Illegal value"},
{ERR_WOULDBLOCK,"Operation would block"},
{ERR_USE,"Address in use"},
{ERR_ALREADY,"Already connecting"},
{ERR_ISCONN,"Conn already established"}, // -10
{ERR_CONN,"Not connected"}, // -11
{ERR_IF,"Low-level netif error"}, // -12
{ERR_ABRT,"Connection aborted"}, // -13
{ERR_RST,"Connection reset"}, // -14
{ERR_CLSD,"Connection closed"},
{ERR_ARG,"Illegal argument"},
// Errors generated by H4AsyncTCP itself
{H4AT_ERR_DNS_FAIL,"DNS Fail"},
{H4AT_ERR_DNS_NF,"Remote Host not found"},
{H4AT_HEAP_LIMITER_ON,"Heap Limiter ON"},
{H4AT_HEAP_LIMITER_OFF,"Heap Limiter OFF"},
{H4AT_HEAP_LIMITER_LOST,"Heap Limiter: packet discarded"},
{H4AT_INPUT_TOO_BIG,"Input exceeds safe heap"},
{H4AT_CLOSING,"Client closing"},
{H4AT_OUTPUT_TOO_BIG,"Output exceeds safe heap"}
Your handler funtion can decide whether the error is fatal or not: if it returns true
, then the connection will be closed (if not already). If there is an actual closure then the onDisconnect
event will fire, so be careful not to cause an infinite loop.
In the early stages, it is best to always return true and have all errors close the connection.
The main prerequisite of Example sketch is that there is an "Echo Server" somehwre on the network. The two easiest ways of achieving this are:
- Windows10 systems In control panel / programs / turn Windows features on or off, select "Simple TCPIP services..."
After reboot you will find an Echo server on port 7
- On any system having Python3 installed
Run the provided Python Echo Server which listens on 8007
In all cases, edit the code of the sketch for the IP/Port of your echo server.
The sketch runs a series of tests in which a number of specifically-sized TCP packets are sent to the echo server at a chosen rate ("stagger" value in mS, default=0 i.e. immediately consecutive) and the amount of data echoed back is totalled up.
Where no data is lost / discarded the amount RXed will - of course - be the same as the total amount TXed and the test passes, otherwise it fails.
By changing the size, number and stagger value, tou can estabish "safe" limits for your own system...
WHEN USE_ASYNC_CLIENT
IS SET TO 0
If you set USE_ASYNC_CLIENT
to 1, the sketch will compile using the ESPAsyncTCP and the tests will almost certainly fail!
Results of running the code against both libraries on both ESP8266 and ESP32 can be found here
H4AsyncServer
is highly abstract - it has only one non-virtual function onError
.
To create your own server then you need to define virtual overrides for:
_instantiateRequest
This is only required if you have subclassed H4AsyncClient
. Here you instantiate your server handler object and return a pointer to its base H4AsyncClient
. In most cases, the default H4AsyncClient
will suffice
-
begin
Call the base classbegin
to set up incoming connection handling on the designated port, then do anything your own server needs during initialisation. -
reset
Deallocate any open resources / handles / memory etc and set your class so that a subsequent call tobegin
will bring server to intial state as it would have done on 1st call. -
route(void* c,const uint8_t* data,size_t len
This is the main "handler" for your server.c
is a client* and will beH4AsyncClient*
unless you have subclassed it in_instantiateRequest
.data
andlen
describe the raw data of the message that cause the connection.Here you will analyse, parse, act upon the incoming message in whatever way defines your server and then reply to the incoming request with e.g.
c->TX(someReplyDataPointer,someReplyDataLength);
See the fully-functional example below in the Server API section
void cbConnect(void)
void cbDisconnect(void)
bool cbError(int e,int i)
void rxFunction(const uint8_t* data, size_t len)
void close(); // force immediate connection close
void connect(const std::string& host,uint16_t port);
void connect(IPAddress ip,uint16_t port);
void connect(const std::string& url); // e.g. http://insecure.remote.ru:12345/long/resource/path/?data=123&moredata=456" (see below)
bool connected(); // true if it is
std::string errorstring(int e); // if H4AT_DEBUG set, translates e into human-readable error
uint32_t localAddress();
IPAddress localIP();
std::string localIPstring();
uint16_t localPort();
size_t maxPacket(); // future use (see "know problems")
void nagle(bool b=true); // sets / unsets nagle algorithm on the connection
void onConnect(H4_FN_VOID cbConnect); // define onConnect event handler
void onDisconnect(H4_FN_VOID cbDisconnect); // define onDisconnect event handler
void onError(H4AT_FN_ERROR cbError); // define onError event handler
void onRX(H4AT_FN_RXDATA rxFunction); // define data receive handler
uint32_t remoteAddress();
IPAddress remoteIP();
std::string remoteIPstring();
uint16_t remotePort();
void TX(const uint8_t* d,size_t len,bool copy=true); // send data. If AND ONLY IF data is static, copy=false is more efficient
The url must be specified in the following general form. The extended path and query portions are optional, as is the port. If the port is omitted it will default to 80 for URLs starting http
http://hostname:port/path/to/resource?a=b&c=d"
or
https://hostname:port/path/to/resource?a=b&c=d"
The hostname
portion my be specified as a "dotted quad" IP address e.g. "172.103.22.14" or a publicly resolvable DNS name e.g. somehost.co.uk
ESP8266 targets will happily resolve .local
names. See "Known Issues" re ESP32
http://192.168.1.15
// defaults to port 80http://mosquitto.local:8883
// .local only works on ESP8266 at the momenthttp://insecure.remote.ru:12345/long/resource/path/?data=123&moredata=456
void cbError(int e,int i)
// Constructor
H4AsyncServer(uint16_t port);
// See "usage" above for descriptions
void begin();
void onError(H4AT_FN_ERROR f);
void reset();
void route(void* c,const uint8_t* data,size_t len)=0;
H4AsyncClient* _instantiateRequest(struct tcp_pcb *p);
Since the server API has been described under Usage above, the following code of a simple Echo server should "pull together" server creation and handling.
The server simply returns whatever it receives to the client, i.e. it echoes back the input
class EchoServer: public H4AsyncServer {
public:
EchoServer(uint16_t port): H4AsyncServer(port){}
void route(void* c,const uint8_t* data,size_t len) override {
reinterpret_cast<H4AsyncClient*>(c)->TX(data,len);
};
};
//
// which may then be instantiated as:
//
EchoServer echo(7); // listen on port 7 and echo any input
-
ESP32 version cannot resolve
.local
addresses so you will need to specify or resolve the IP address yourself. -
Heap protection is not yet implemented, so you are recommended to stay within size / speed limits as much as possible. There is a problem somewhere in that once the users goes beyond the "window size", certain combinations of TX / RX size, nagle on/off, network speed? cause some packets not to get ACKed by the remote server.
Hence until this is solved, one cannot safely determine the "max safe packet size" :( required for the heap protection mechanism. [ I currently suspect LwIP - any help in this area would be most welcome! (and will almost certainly turn out to be H4 code of course... )]
Until then, try to keep packets smaller than a SND_BUF on TX and WND on RX when sent slowly, with these values scaled back proprtionately if sent in rapid bursts. Ideally the cpmbination of size / speed should stay within the known "safe effective kB/s" - which has been determined during testing, (but is almost certain to differ on your network)
Platform | TCP_MSS | TCP_SND_BUF | TCP_WND | Safe kBpsEff |
---|---|---|---|---|
ESP32 | 1436 | 5744 | 5744 | 425 |
ESP8266 Low Memory | 536 | 1072 | 2144 | 150 |
ESP8266 High Bandwidth | 1460 | 2920 | 5840 | 225 |
N.B. Once the source of "the lost ACK" has been found, you won't need to worry about any of the above as your code will either "just work" when within safe limits or cause an error ( H4AT_INPUT_TOO_BIG
or H4AT_OUTPUT_TOO_BIG
) if outside those limits.
(c) 2021 Phil Bowles [email protected]