From 20e6639564c7a5438f3228d15e4c6fabc034a542 Mon Sep 17 00:00:00 2001 From: jnovonj Date: Thu, 11 Dec 2025 01:21:51 +0100 Subject: [PATCH 1/9] example: Simplified version of a TCP Chargen server. --- examples/ChargenServer/ChargenServer.ino | 152 +++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 examples/ChargenServer/ChargenServer.ino diff --git a/examples/ChargenServer/ChargenServer.ino b/examples/ChargenServer/ChargenServer.ino new file mode 100644 index 0000000..d8957e0 --- /dev/null +++ b/examples/ChargenServer/ChargenServer.ino @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Jorge Novo + +/* + This example demonstrates how to cretate a TCP Chargen server with the + AsyncTCP library. Run on the remote computer: + + $ nc 19 + + it shows a continuous stream of characters like this: + + #$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghij + $%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijk + %&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijkl + &'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklm + '()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmn + ()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmno + )*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnop + *+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopq + +,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqr + + If the pattern shows broken your ESP32 is probably too busy to serve the + data or the network is congested. + +*/ + +#include +#include +#include + +// The default TCP Chargen port number is 19, see RFC 864 (Character Generator Protocol) +#define CHARGEN_PORT 19 +const size_t LINE_LENGTH = 72; + +// Full pattern of printable ASCII characters (95 characters) +const char CHARGEN_PATTERN_FULL[] = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"; +const size_t PATTERN_LENGTH_FULL = 95; + +#define WIFI_SSID "Your_SSID" +#define WIFI_PASSWORD "Your_PASSWORD" + +// --- Global state for a SINGLE client --- +// This is the main asynchronous server object +AsyncServer* AsyncServerChargen = nullptr; +// This is the pointer to the single connected client +AsyncClient* AsyncClientChargen= nullptr; +// This is the pointer to the stream of data. +size_t startIndex = 0; + + +void makeAndSendLine(); // Forward declaration + +// --- Callback Functions --- + +// Called when the client acknowledges receiving data. We use this to send more. +void handleClientAck(void* arg, AsyncClient* client, size_t len, uint32_t time) { + if (!client->disconnected() && client->space() > (LINE_LENGTH + 2)) { + makeAndSendLine(); // <--- ¡Aquí está la cadena! + } +} + +// Called periodically while the client is connected. +// This function has the correct signature for onPoll: void(void*, AsyncClient*). +void handleClientPoll(void* arg, AsyncClient* client) { + // We can reuse the same logic as the ACK handler. + // Just try to send more data if there's space. + handleClientAck(arg, client, 0, 0); +} + +// Called when the client disconnects. +void handleClientDisconnect(void* arg, AsyncClient* client) { + Serial.println("Client disconnected."); + // Set the global client pointer to null to allow a new client to connect. + AsyncClientChargen = nullptr; +} + +// Called when a new client tries to connect. +void handleClient(void* arg, AsyncClient* client) { + // If there is already a client connected, reject the new one. + if (AsyncClientChargen) { + Serial.printf("New connection from %s rejected. Server is busy.\n", client->remoteIP().toString().c_str()); + client->close(); + return; + } + + // Accept the new client. + Serial.printf("New client connected from %s\n", client->remoteIP().toString().c_str()); + AsyncClientChargen = client; + startIndex = 0; // Reset pattern for the new client. + + // Set up callbacks for the new client. + AsyncClientChargen->onAck(handleClientAck, nullptr); + // onPoll is a good backup to send data if the buffer was full. + AsyncClientChargen->onPoll(handleClientPoll, nullptr); + AsyncClientChargen->onDisconnect(handleClientDisconnect, nullptr); + + // Start sending data immediately. + makeAndSendLine(); +} + +void makeAndSendLine() { + // Check if the client is valid and has enough space in its send buffer. + if (AsyncClientChargen && AsyncClientChargen->canSend() && AsyncClientChargen->space() >= (LINE_LENGTH + 2)) { + // Buffer for the line (72 characters + \r\n) + char lineBuffer[LINE_LENGTH + 2]; + + // 1. Construct the 72-character line using the rotating pattern. + for (size_t i = 0; i < LINE_LENGTH; i++) { + lineBuffer[i] = CHARGEN_PATTERN_FULL[(startIndex + i) % PATTERN_LENGTH_FULL]; + } + + // 2. Add the standard CHARGEN line terminator (\r\n). + lineBuffer[LINE_LENGTH] = '\r'; + lineBuffer[LINE_LENGTH + 1] = '\n'; + + // 3. Write data to the socket. + AsyncClientChargen->write(lineBuffer, LINE_LENGTH + 2); + + // 4. Advance the starting index for the next line (rotation). + startIndex = (startIndex + 1) % PATTERN_LENGTH_FULL; + } +} + +// ---------------------- SETUP & LOOP ---------------------- + +void setup() { + Serial.begin(115200); + while (!Serial) { + continue; + } + // Connecting to WiFi... + WiFi.mode(WIFI_STA); + WiFi.begin(WIFI_SSID, WIFI_PASSWORD); + Serial.print("Connecting to WiFi..."); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println(); + + // Create the Async TCP Server + AsyncServerChargen = new AsyncServer(CHARGEN_PORT); + // Set up the callback for new client connections. + AsyncServerChargen->onClient(&handleClient, nullptr); + AsyncServerChargen->begin(); + Serial.printf("Chargen Server (%s) started on port %d\n", WiFi.localIP().toString().c_str(), CHARGEN_PORT); +} + +void loop() { + // The async library handles everything in the background. + // No code is needed here for the server to run. +} \ No newline at end of file From 1154ea1e617f382fb1ea7e6a0b8b73a0d2a9ca51 Mon Sep 17 00:00:00 2001 From: jnovonj Date: Thu, 11 Dec 2025 15:03:40 +0100 Subject: [PATCH 2/9] Update examples/ChargenServer/ChargenServer.ino Missing space after variable name Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- examples/ChargenServer/ChargenServer.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/ChargenServer/ChargenServer.ino b/examples/ChargenServer/ChargenServer.ino index d8957e0..e09e541 100644 --- a/examples/ChargenServer/ChargenServer.ino +++ b/examples/ChargenServer/ChargenServer.ino @@ -43,7 +43,7 @@ const size_t PATTERN_LENGTH_FULL = 95; // This is the main asynchronous server object AsyncServer* AsyncServerChargen = nullptr; // This is the pointer to the single connected client -AsyncClient* AsyncClientChargen= nullptr; +AsyncClient* AsyncClientChargen = nullptr; // This is the pointer to the stream of data. size_t startIndex = 0; From 4fabbddf30e6bbb62ebce17fec457735d036eeec Mon Sep 17 00:00:00 2001 From: jnovonj Date: Thu, 11 Dec 2025 15:04:04 +0100 Subject: [PATCH 3/9] Update examples/ChargenServer/ChargenServer.ino Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- examples/ChargenServer/ChargenServer.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/ChargenServer/ChargenServer.ino b/examples/ChargenServer/ChargenServer.ino index e09e541..87e96fb 100644 --- a/examples/ChargenServer/ChargenServer.ino +++ b/examples/ChargenServer/ChargenServer.ino @@ -44,7 +44,7 @@ const size_t PATTERN_LENGTH_FULL = 95; AsyncServer* AsyncServerChargen = nullptr; // This is the pointer to the single connected client AsyncClient* AsyncClientChargen = nullptr; -// This is the pointer to the stream of data. +// Tracks the current position in the pattern rotation for the Chargen protocol. size_t startIndex = 0; From 7a9291c6b04ac51b2c748ad58be093292165b052 Mon Sep 17 00:00:00 2001 From: jnovonj Date: Thu, 11 Dec 2025 15:04:48 +0100 Subject: [PATCH 4/9] Update examples/ChargenServer/ChargenServer.ino Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- examples/ChargenServer/ChargenServer.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/ChargenServer/ChargenServer.ino b/examples/ChargenServer/ChargenServer.ino index 87e96fb..2b48651 100644 --- a/examples/ChargenServer/ChargenServer.ino +++ b/examples/ChargenServer/ChargenServer.ino @@ -2,7 +2,7 @@ // Copyright 2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Jorge Novo /* - This example demonstrates how to cretate a TCP Chargen server with the + This example demonstrates how to create a TCP Chargen server with the AsyncTCP library. Run on the remote computer: $ nc 19 From b7429940def76a577a05a6823f8f5d47f14d6e3e Mon Sep 17 00:00:00 2001 From: jnovonj Date: Thu, 11 Dec 2025 15:09:45 +0100 Subject: [PATCH 5/9] Update examples/ChargenServer/ChargenServer.ino Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- examples/ChargenServer/ChargenServer.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/ChargenServer/ChargenServer.ino b/examples/ChargenServer/ChargenServer.ino index 2b48651..766b79c 100644 --- a/examples/ChargenServer/ChargenServer.ino +++ b/examples/ChargenServer/ChargenServer.ino @@ -54,7 +54,7 @@ void makeAndSendLine(); // Forward declaration // Called when the client acknowledges receiving data. We use this to send more. void handleClientAck(void* arg, AsyncClient* client, size_t len, uint32_t time) { - if (!client->disconnected() && client->space() > (LINE_LENGTH + 2)) { + if (!client->disconnected() && client->space() >= (LINE_LENGTH + 2)) { makeAndSendLine(); // <--- ¡Aquí está la cadena! } } From a23e919d5719bdc23c97ad11b172c9f0d632f7da Mon Sep 17 00:00:00 2001 From: jnovonj Date: Thu, 11 Dec 2025 15:54:13 +0100 Subject: [PATCH 6/9] fix: PR problems solved (operador >= and pointers) --- examples/ChargenServer/ChargenServer.ino | 50 ++++++++++++++++++------ 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/examples/ChargenServer/ChargenServer.ino b/examples/ChargenServer/ChargenServer.ino index d8957e0..db0c693 100644 --- a/examples/ChargenServer/ChargenServer.ino +++ b/examples/ChargenServer/ChargenServer.ino @@ -36,10 +36,9 @@ const size_t LINE_LENGTH = 72; const char CHARGEN_PATTERN_FULL[] = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"; const size_t PATTERN_LENGTH_FULL = 95; -#define WIFI_SSID "Your_SSID" -#define WIFI_PASSWORD "Your_PASSWORD" +#define WIFI_SSID "WiFi_Pelicano" +#define WIFI_PASSWORD "AcDhDdg7smBLgnUtfcV5t" -// --- Global state for a SINGLE client --- // This is the main asynchronous server object AsyncServer* AsyncServerChargen = nullptr; // This is the pointer to the single connected client @@ -47,15 +46,14 @@ AsyncClient* AsyncClientChargen= nullptr; // This is the pointer to the stream of data. size_t startIndex = 0; - -void makeAndSendLine(); // Forward declaration +void makeAndSendLine(); // Forward declaration // --- Callback Functions --- // Called when the client acknowledges receiving data. We use this to send more. void handleClientAck(void* arg, AsyncClient* client, size_t len, uint32_t time) { - if (!client->disconnected() && client->space() > (LINE_LENGTH + 2)) { - makeAndSendLine(); // <--- ¡Aquí está la cadena! + if (!client->disconnected() && client->space() >= (LINE_LENGTH + 2)) { + makeAndSendLine(); } } @@ -64,14 +62,34 @@ void handleClientAck(void* arg, AsyncClient* client, size_t len, uint32_t time) void handleClientPoll(void* arg, AsyncClient* client) { // We can reuse the same logic as the ACK handler. // Just try to send more data if there's space. - handleClientAck(arg, client, 0, 0); + handleClientAck(arg, client, 0, 0); // Original call +} + +// It handles errors that are not normal disconnections. +void handleClientError(void* arg, AsyncClient* client, int error) { + // The error codes are defined in esp_err.h + Serial.printf("Client error! Code: %d, Message: %s\n", error, client->errorToString(error)); + + // If the client is the one we have stored, clean it up. + if (AsyncClientChargen == client) { + Serial.println("Cleaning up global client pointer due to error."); + AsyncClientChargen = nullptr; + } + // We do not need to call "delete client" here because onDisconnect will do it + // If the error is critical we will do it. + if (client->connected()) { + client->close(); + } } // Called when the client disconnects. void handleClientDisconnect(void* arg, AsyncClient* client) { Serial.println("Client disconnected."); // Set the global client pointer to null to allow a new client to connect. - AsyncClientChargen = nullptr; + if (AsyncClientChargen == client) { + AsyncClientChargen = nullptr; + } + delete client; } // Called when a new client tries to connect. @@ -88,10 +106,20 @@ void handleClient(void* arg, AsyncClient* client) { AsyncClientChargen = client; startIndex = 0; // Reset pattern for the new client. - // Set up callbacks for the new client. + // Called when previously sent data is acknowledged by the client. + // This is the core engine for continuous data transmission (Chargen). AsyncClientChargen->onAck(handleClientAck, nullptr); - // onPoll is a good backup to send data if the buffer was full. + + // Called periodically by the AsyncTCP task. + // Serves as a backup to resume transmission if the buffer was full and the ACK wasn't received. AsyncClientChargen->onPoll(handleClientPoll, nullptr); + + // Called when a communication error (e.g., protocol failure or timeout) occurs. + // Essential for cleaning up the global client pointer and preventing resource leaks. + AsyncClientChargen->onError(handleClientError, nullptr); + + // Called when the client actively closes the connection or if a fatal error occurs. + // Responsible for resetting the global client pointer and freeing memory. AsyncClientChargen->onDisconnect(handleClientDisconnect, nullptr); // Start sending data immediately. From 1f4ea9ccecabbc5519e8981271e8c40ba8e12edd Mon Sep 17 00:00:00 2001 From: jnovonj Date: Thu, 11 Dec 2025 16:06:32 +0100 Subject: [PATCH 7/9] fix: PR problems solved (operador >= and pointers) --- examples/ChargenServer/ChargenServer.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/ChargenServer/ChargenServer.ino b/examples/ChargenServer/ChargenServer.ino index f9b2b9d..d763bd6 100644 --- a/examples/ChargenServer/ChargenServer.ino +++ b/examples/ChargenServer/ChargenServer.ino @@ -75,8 +75,8 @@ void handleClientError(void* arg, AsyncClient* client, int error) { Serial.println("Cleaning up global client pointer due to error."); AsyncClientChargen = nullptr; } - // We do not need to call "delete client" here because onDisconnect will do it. - // If the error is critical, we will do it. + // We do not need to call "delete client" here because onDisconnect will do it + // If the error is critical we will do it. if (client->connected()) { client->close(); } From 887b72a3e9660ca9de9f55ce846da45dfe0fe97f Mon Sep 17 00:00:00 2001 From: jnovonj Date: Thu, 11 Dec 2025 16:19:38 +0100 Subject: [PATCH 8/9] fix: Delete my WiFi passwrod --- examples/ChargenServer/ChargenServer.ino | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/examples/ChargenServer/ChargenServer.ino b/examples/ChargenServer/ChargenServer.ino index d763bd6..41bf817 100644 --- a/examples/ChargenServer/ChargenServer.ino +++ b/examples/ChargenServer/ChargenServer.ino @@ -58,11 +58,10 @@ void handleClientAck(void* arg, AsyncClient* client, size_t len, uint32_t time) } // Called periodically while the client is connected. -// This function has the correct signature for onPoll: void(void*, AsyncClient*). void handleClientPoll(void* arg, AsyncClient* client) { // We can reuse the same logic as the ACK handler. // Just try to send more data if there's space. - handleClientAck(arg, client, 0, 0); // Original call + handleClientAck(arg, client, 0, 0); } // It handles errors that are not normal disconnections. @@ -75,8 +74,8 @@ void handleClientError(void* arg, AsyncClient* client, int error) { Serial.println("Cleaning up global client pointer due to error."); AsyncClientChargen = nullptr; } - // We do not need to call "delete client" here because onDisconnect will do it - // If the error is critical we will do it. + // We do not need to call "delete client" here because onDisconnect will do it. + // If the error is critical, we will do it. if (client->connected()) { client->close(); } From ebb15c44c989a1b10b2c356a46a6633c440ca4ca Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Thu, 11 Dec 2025 15:59:14 +0000 Subject: [PATCH 9/9] ci(pre-commit): Apply automatic fixes --- examples/ChargenServer/ChargenServer.ino | 214 +++++++++++------------ 1 file changed, 107 insertions(+), 107 deletions(-) diff --git a/examples/ChargenServer/ChargenServer.ino b/examples/ChargenServer/ChargenServer.ino index 41bf817..cdc48f9 100644 --- a/examples/ChargenServer/ChargenServer.ino +++ b/examples/ChargenServer/ChargenServer.ino @@ -2,11 +2,11 @@ // Copyright 2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Jorge Novo /* - This example demonstrates how to create a TCP Chargen server with the + This example demonstrates how to create a TCP Chargen server with the AsyncTCP library. Run on the remote computer: - + $ nc 19 - + it shows a continuous stream of characters like this: #$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghij @@ -21,7 +21,7 @@ If the pattern shows broken your ESP32 is probably too busy to serve the data or the network is congested. - + */ #include @@ -40,140 +40,140 @@ const size_t PATTERN_LENGTH_FULL = 95; #define WIFI_PASSWORD "YourPassword" // This is the main asynchronous server object -AsyncServer* AsyncServerChargen = nullptr; +AsyncServer *AsyncServerChargen = nullptr; // This is the pointer to the single connected client -AsyncClient* AsyncClientChargen = nullptr; +AsyncClient *AsyncClientChargen = nullptr; // Tracks the current position in the pattern rotation for the Chargen protocol. size_t startIndex = 0; -void makeAndSendLine(); // Forward declaration +void makeAndSendLine(); // Forward declaration // --- Callback Functions --- // Called when the client acknowledges receiving data. We use this to send more. -void handleClientAck(void* arg, AsyncClient* client, size_t len, uint32_t time) { - if (!client->disconnected() && client->space() >= (LINE_LENGTH + 2)) { - makeAndSendLine(); - } +void handleClientAck(void *arg, AsyncClient *client, size_t len, uint32_t time) { + if (!client->disconnected() && client->space() >= (LINE_LENGTH + 2)) { + makeAndSendLine(); + } } // Called periodically while the client is connected. -void handleClientPoll(void* arg, AsyncClient* client) { - // We can reuse the same logic as the ACK handler. - // Just try to send more data if there's space. - handleClientAck(arg, client, 0, 0); +void handleClientPoll(void *arg, AsyncClient *client) { + // We can reuse the same logic as the ACK handler. + // Just try to send more data if there's space. + handleClientAck(arg, client, 0, 0); } // It handles errors that are not normal disconnections. -void handleClientError(void* arg, AsyncClient* client, int error) { - // The error codes are defined in esp_err.h - Serial.printf("Client error! Code: %d, Message: %s\n", error, client->errorToString(error)); - - // If the client is the one we have stored, clean it up. - if (AsyncClientChargen == client) { - Serial.println("Cleaning up global client pointer due to error."); - AsyncClientChargen = nullptr; - } - // We do not need to call "delete client" here because onDisconnect will do it. - // If the error is critical, we will do it. - if (client->connected()) { - client->close(); - } +void handleClientError(void *arg, AsyncClient *client, int error) { + // The error codes are defined in esp_err.h + Serial.printf("Client error! Code: %d, Message: %s\n", error, client->errorToString(error)); + + // If the client is the one we have stored, clean it up. + if (AsyncClientChargen == client) { + Serial.println("Cleaning up global client pointer due to error."); + AsyncClientChargen = nullptr; + } + // We do not need to call "delete client" here because onDisconnect will do it. + // If the error is critical, we will do it. + if (client->connected()) { + client->close(); + } } // Called when the client disconnects. -void handleClientDisconnect(void* arg, AsyncClient* client) { - Serial.println("Client disconnected."); - // Set the global client pointer to null to allow a new client to connect. - if (AsyncClientChargen == client) { - AsyncClientChargen = nullptr; - } - delete client; +void handleClientDisconnect(void *arg, AsyncClient *client) { + Serial.println("Client disconnected."); + // Set the global client pointer to null to allow a new client to connect. + if (AsyncClientChargen == client) { + AsyncClientChargen = nullptr; + } + delete client; } // Called when a new client tries to connect. -void handleClient(void* arg, AsyncClient* client) { - // If there is already a client connected, reject the new one. - if (AsyncClientChargen) { - Serial.printf("New connection from %s rejected. Server is busy.\n", client->remoteIP().toString().c_str()); - client->close(); - return; - } - - // Accept the new client. - Serial.printf("New client connected from %s\n", client->remoteIP().toString().c_str()); - AsyncClientChargen = client; - startIndex = 0; // Reset pattern for the new client. - - // Called when previously sent data is acknowledged by the client. - // This is the core engine for continuous data transmission (Chargen). - AsyncClientChargen->onAck(handleClientAck, nullptr); - - // Called periodically by the AsyncTCP task. - // Serves as a backup to resume transmission if the buffer was full and the ACK wasn't received. - AsyncClientChargen->onPoll(handleClientPoll, nullptr); - - // Called when a communication error (e.g., protocol failure or timeout) occurs. - // Essential for cleaning up the global client pointer and preventing resource leaks. - AsyncClientChargen->onError(handleClientError, nullptr); - - // Called when the client actively closes the connection or if a fatal error occurs. - // Responsible for resetting the global client pointer and freeing memory. - AsyncClientChargen->onDisconnect(handleClientDisconnect, nullptr); - - // Start sending data immediately. - makeAndSendLine(); +void handleClient(void *arg, AsyncClient *client) { + // If there is already a client connected, reject the new one. + if (AsyncClientChargen) { + Serial.printf("New connection from %s rejected. Server is busy.\n", client->remoteIP().toString().c_str()); + client->close(); + return; + } + + // Accept the new client. + Serial.printf("New client connected from %s\n", client->remoteIP().toString().c_str()); + AsyncClientChargen = client; + startIndex = 0; // Reset pattern for the new client. + + // Called when previously sent data is acknowledged by the client. + // This is the core engine for continuous data transmission (Chargen). + AsyncClientChargen->onAck(handleClientAck, nullptr); + + // Called periodically by the AsyncTCP task. + // Serves as a backup to resume transmission if the buffer was full and the ACK wasn't received. + AsyncClientChargen->onPoll(handleClientPoll, nullptr); + + // Called when a communication error (e.g., protocol failure or timeout) occurs. + // Essential for cleaning up the global client pointer and preventing resource leaks. + AsyncClientChargen->onError(handleClientError, nullptr); + + // Called when the client actively closes the connection or if a fatal error occurs. + // Responsible for resetting the global client pointer and freeing memory. + AsyncClientChargen->onDisconnect(handleClientDisconnect, nullptr); + + // Start sending data immediately. + makeAndSendLine(); } void makeAndSendLine() { - // Check if the client is valid and has enough space in its send buffer. - if (AsyncClientChargen && AsyncClientChargen->canSend() && AsyncClientChargen->space() >= (LINE_LENGTH + 2)) { - // Buffer for the line (72 characters + \r\n) - char lineBuffer[LINE_LENGTH + 2]; - - // 1. Construct the 72-character line using the rotating pattern. - for (size_t i = 0; i < LINE_LENGTH; i++) { - lineBuffer[i] = CHARGEN_PATTERN_FULL[(startIndex + i) % PATTERN_LENGTH_FULL]; - } + // Check if the client is valid and has enough space in its send buffer. + if (AsyncClientChargen && AsyncClientChargen->canSend() && AsyncClientChargen->space() >= (LINE_LENGTH + 2)) { + // Buffer for the line (72 characters + \r\n) + char lineBuffer[LINE_LENGTH + 2]; + + // 1. Construct the 72-character line using the rotating pattern. + for (size_t i = 0; i < LINE_LENGTH; i++) { + lineBuffer[i] = CHARGEN_PATTERN_FULL[(startIndex + i) % PATTERN_LENGTH_FULL]; + } - // 2. Add the standard CHARGEN line terminator (\r\n). - lineBuffer[LINE_LENGTH] = '\r'; - lineBuffer[LINE_LENGTH + 1] = '\n'; + // 2. Add the standard CHARGEN line terminator (\r\n). + lineBuffer[LINE_LENGTH] = '\r'; + lineBuffer[LINE_LENGTH + 1] = '\n'; - // 3. Write data to the socket. - AsyncClientChargen->write(lineBuffer, LINE_LENGTH + 2); + // 3. Write data to the socket. + AsyncClientChargen->write(lineBuffer, LINE_LENGTH + 2); - // 4. Advance the starting index for the next line (rotation). - startIndex = (startIndex + 1) % PATTERN_LENGTH_FULL; - } + // 4. Advance the starting index for the next line (rotation). + startIndex = (startIndex + 1) % PATTERN_LENGTH_FULL; + } } // ---------------------- SETUP & LOOP ---------------------- void setup() { - Serial.begin(115200); - while (!Serial) { - continue; - } - // Connecting to WiFi... - WiFi.mode(WIFI_STA); - WiFi.begin(WIFI_SSID, WIFI_PASSWORD); - Serial.print("Connecting to WiFi..."); - while (WiFi.status() != WL_CONNECTED) { - delay(500); - Serial.print("."); - } - Serial.println(); - - // Create the Async TCP Server - AsyncServerChargen = new AsyncServer(CHARGEN_PORT); - // Set up the callback for new client connections. - AsyncServerChargen->onClient(&handleClient, nullptr); - AsyncServerChargen->begin(); - Serial.printf("Chargen Server (%s) started on port %d\n", WiFi.localIP().toString().c_str(), CHARGEN_PORT); + Serial.begin(115200); + while (!Serial) { + continue; + } + // Connecting to WiFi... + WiFi.mode(WIFI_STA); + WiFi.begin(WIFI_SSID, WIFI_PASSWORD); + Serial.print("Connecting to WiFi..."); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println(); + + // Create the Async TCP Server + AsyncServerChargen = new AsyncServer(CHARGEN_PORT); + // Set up the callback for new client connections. + AsyncServerChargen->onClient(&handleClient, nullptr); + AsyncServerChargen->begin(); + Serial.printf("Chargen Server (%s) started on port %d\n", WiFi.localIP().toString().c_str(), CHARGEN_PORT); } void loop() { - // The async library handles everything in the background. - // No code is needed here for the server to run. -} \ No newline at end of file + // The async library handles everything in the background. + // No code is needed here for the server to run. +}