1+ // SPDX-License-Identifier: LGPL-3.0-or-later
2+ // Copyright 2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Jorge Novo
3+
4+ /*
5+ This example demonstrates how to cretate a TCP Chargen server with the
6+ AsyncTCP library. Run on the remote computer:
7+
8+ $ nc <IPAddressforESP32> 19
9+
10+ it shows a continuous stream of characters like this:
11+
12+ #$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghij
13+ $%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijk
14+ %&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijkl
15+ &'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklm
16+ '()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmn
17+ ()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmno
18+ )*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnop
19+ *+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopq
20+ +,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqr
21+
22+ If the pattern shows broken your ESP32 is probably too busy to serve the
23+ data or the network is congested.
24+
25+ */
26+
27+ #include < Arduino.h>
28+ #include < AsyncTCP.h>
29+ #include < WiFi.h>
30+
31+ // The default TCP Chargen port number is 19, see RFC 864 (Character Generator Protocol)
32+ #define CHARGEN_PORT 19
33+ const size_t LINE_LENGTH = 72 ;
34+
35+ // Full pattern of printable ASCII characters (95 characters)
36+ const char CHARGEN_PATTERN_FULL[] = " !\" #$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\ ]^_`abcdefghijklmnopqrstuvwxyz{|}~" ;
37+ const size_t PATTERN_LENGTH_FULL = 95 ;
38+
39+ #define WIFI_SSID " Your_SSID"
40+ #define WIFI_PASSWORD " Your_PASSWORD"
41+
42+ // --- Global state for a SINGLE client ---
43+ // This is the main asynchronous server object
44+ AsyncServer* AsyncServerChargen = nullptr ;
45+ // This is the pointer to the single connected client
46+ AsyncClient* AsyncClientChargen= nullptr ;
47+ // This is the pointer to the stream of data.
48+ size_t startIndex = 0 ;
49+
50+
51+ void makeAndSendLine (); // Forward declaration
52+
53+ // --- Callback Functions ---
54+
55+ // Called when the client acknowledges receiving data. We use this to send more.
56+ void handleClientAck (void * arg, AsyncClient* client, size_t len, uint32_t time) {
57+ if (!client->disconnected () && client->space () > (LINE_LENGTH + 2 )) {
58+ makeAndSendLine (); // <--- ¡Aquí está la cadena!
59+ }
60+ }
61+
62+ // Called periodically while the client is connected.
63+ // This function has the correct signature for onPoll: void(void*, AsyncClient*).
64+ void handleClientPoll (void * arg, AsyncClient* client) {
65+ // We can reuse the same logic as the ACK handler.
66+ // Just try to send more data if there's space.
67+ handleClientAck (arg, client, 0 , 0 );
68+ }
69+
70+ // Called when the client disconnects.
71+ void handleClientDisconnect (void * arg, AsyncClient* client) {
72+ Serial.println (" Client disconnected." );
73+ // Set the global client pointer to null to allow a new client to connect.
74+ AsyncClientChargen = nullptr ;
75+ }
76+
77+ // Called when a new client tries to connect.
78+ void handleClient (void * arg, AsyncClient* client) {
79+ // If there is already a client connected, reject the new one.
80+ if (AsyncClientChargen) {
81+ Serial.printf (" New connection from %s rejected. Server is busy.\n " , client->remoteIP ().toString ().c_str ());
82+ client->close ();
83+ return ;
84+ }
85+
86+ // Accept the new client.
87+ Serial.printf (" New client connected from %s\n " , client->remoteIP ().toString ().c_str ());
88+ AsyncClientChargen = client;
89+ startIndex = 0 ; // Reset pattern for the new client.
90+
91+ // Set up callbacks for the new client.
92+ AsyncClientChargen->onAck (handleClientAck, nullptr );
93+ // onPoll is a good backup to send data if the buffer was full.
94+ AsyncClientChargen->onPoll (handleClientPoll, nullptr );
95+ AsyncClientChargen->onDisconnect (handleClientDisconnect, nullptr );
96+
97+ // Start sending data immediately.
98+ makeAndSendLine ();
99+ }
100+
101+ void makeAndSendLine () {
102+ // Check if the client is valid and has enough space in its send buffer.
103+ if (AsyncClientChargen && AsyncClientChargen->canSend () && AsyncClientChargen->space () >= (LINE_LENGTH + 2 )) {
104+ // Buffer for the line (72 characters + \r\n)
105+ char lineBuffer[LINE_LENGTH + 2 ];
106+
107+ // 1. Construct the 72-character line using the rotating pattern.
108+ for (size_t i = 0 ; i < LINE_LENGTH; i++) {
109+ lineBuffer[i] = CHARGEN_PATTERN_FULL[(startIndex + i) % PATTERN_LENGTH_FULL];
110+ }
111+
112+ // 2. Add the standard CHARGEN line terminator (\r\n).
113+ lineBuffer[LINE_LENGTH] = ' \r ' ;
114+ lineBuffer[LINE_LENGTH + 1 ] = ' \n ' ;
115+
116+ // 3. Write data to the socket.
117+ AsyncClientChargen->write (lineBuffer, LINE_LENGTH + 2 );
118+
119+ // 4. Advance the starting index for the next line (rotation).
120+ startIndex = (startIndex + 1 ) % PATTERN_LENGTH_FULL;
121+ }
122+ }
123+
124+ // ---------------------- SETUP & LOOP ----------------------
125+
126+ void setup () {
127+ Serial.begin (115200 );
128+ while (!Serial) {
129+ continue ;
130+ }
131+ // Connecting to WiFi...
132+ WiFi.mode (WIFI_STA);
133+ WiFi.begin (WIFI_SSID, WIFI_PASSWORD);
134+ Serial.print (" Connecting to WiFi..." );
135+ while (WiFi.status () != WL_CONNECTED) {
136+ delay (500 );
137+ Serial.print (" ." );
138+ }
139+ Serial.println ();
140+
141+ // Create the Async TCP Server
142+ AsyncServerChargen = new AsyncServer (CHARGEN_PORT);
143+ // Set up the callback for new client connections.
144+ AsyncServerChargen->onClient (&handleClient, nullptr );
145+ AsyncServerChargen->begin ();
146+ Serial.printf (" Chargen Server (%s) started on port %d\n " , WiFi.localIP ().toString ().c_str (), CHARGEN_PORT);
147+ }
148+
149+ void loop () {
150+ // The async library handles everything in the background.
151+ // No code is needed here for the server to run.
152+ }
0 commit comments