Subversion Repositories ESP8266_P1_Meter

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 raymond 1
// SPDX-License-Identifier: LGPL-3.0-or-later
2
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
3
 
4
/*
5
  This example demonstrates how to create 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     "YourSSID"
40
#define WIFI_PASSWORD "YourPassword"
41
 
42
// This is the main asynchronous server object
43
AsyncServer *AsyncServerChargen = nullptr;
44
// This is the pointer to the single connected client
45
AsyncClient *AsyncClientChargen = nullptr;
46
// Tracks the current position in the pattern rotation for the Chargen protocol.
47
size_t startIndex = 0;
48
 
49
void makeAndSendLine();  // Forward declaration
50
 
51
// --- Callback Functions ---
52
 
53
// Called when the client acknowledges receiving data. We use this to send more.
54
void handleClientAck(void *arg, AsyncClient *client, size_t len, uint32_t time) {
55
  if (!client->disconnected() && client->space() >= (LINE_LENGTH + 2)) {
56
    makeAndSendLine();
57
  }
58
}
59
 
60
// Called periodically while the client is connected.
61
void handleClientPoll(void *arg, AsyncClient *client) {
62
  // We can reuse the same logic as the ACK handler.
63
  // Just try to send more data if there's space.
64
  handleClientAck(arg, client, 0, 0);
65
}
66
 
67
// It handles errors that are not normal disconnections.
68
void handleClientError(void *arg, AsyncClient *client, int error) {
69
  // The error codes are defined in esp_err.h
70
  Serial.printf("Client error! Code: %d, Message: %s\n", error, client->errorToString(error));
71
 
72
  // If the client is the one we have stored, clean it up.
73
  if (AsyncClientChargen == client) {
74
    Serial.println("Cleaning up global client pointer due to error.");
75
    AsyncClientChargen = nullptr;
76
  }
77
  // We do not need to call "delete client" here because onDisconnect will do it.
78
  // If the error is critical, we will do it.
79
  if (client->connected()) {
80
    client->close();
81
  }
82
}
83
 
84
// Called when the client disconnects.
85
void handleClientDisconnect(void *arg, AsyncClient *client) {
86
  Serial.println("Client disconnected.");
87
  // Set the global client pointer to null to allow a new client to connect.
88
  if (AsyncClientChargen == client) {
89
    AsyncClientChargen = nullptr;
90
  }
91
  delete client;
92
}
93
 
94
// Called when a new client tries to connect.
95
void handleClient(void *arg, AsyncClient *client) {
96
  // If there is already a client connected, reject the new one.
97
  if (AsyncClientChargen) {
98
    Serial.printf("New connection from %s rejected. Server is busy.\n", client->remoteIP().toString().c_str());
99
    client->close();
100
    return;
101
  }
102
 
103
  // Accept the new client.
104
  Serial.printf("New client connected from %s\n", client->remoteIP().toString().c_str());
105
  AsyncClientChargen = client;
106
  startIndex = 0;  // Reset pattern for the new client.
107
 
108
  // Called when previously sent data is acknowledged by the client.
109
  // This is the core engine for continuous data transmission (Chargen).
110
  AsyncClientChargen->onAck(handleClientAck, nullptr);
111
 
112
  // Called periodically by the AsyncTCP task.
113
  // Serves as a backup to resume transmission if the buffer was full and the ACK wasn't received.
114
  AsyncClientChargen->onPoll(handleClientPoll, nullptr);
115
 
116
  // Called when a communication error (e.g., protocol failure or timeout) occurs.
117
  // Essential for cleaning up the global client pointer and preventing resource leaks.
118
  AsyncClientChargen->onError(handleClientError, nullptr);
119
 
120
  // Called when the client actively closes the connection or if a fatal error occurs.
121
  // Responsible for resetting the global client pointer and freeing memory.
122
  AsyncClientChargen->onDisconnect(handleClientDisconnect, nullptr);
123
 
124
  // Start sending data immediately.
125
  makeAndSendLine();
126
}
127
 
128
void makeAndSendLine() {
129
  // Check if the client is valid and has enough space in its send buffer.
130
  if (AsyncClientChargen && AsyncClientChargen->canSend() && AsyncClientChargen->space() >= (LINE_LENGTH + 2)) {
131
    // Buffer for the line (72 characters + \r\n)
132
    char lineBuffer[LINE_LENGTH + 2];
133
 
134
    // 1. Construct the 72-character line using the rotating pattern.
135
    for (size_t i = 0; i < LINE_LENGTH; i++) {
136
      lineBuffer[i] = CHARGEN_PATTERN_FULL[(startIndex + i) % PATTERN_LENGTH_FULL];
137
    }
138
 
139
    // 2. Add the standard CHARGEN line terminator (\r\n).
140
    lineBuffer[LINE_LENGTH] = '\r';
141
    lineBuffer[LINE_LENGTH + 1] = '\n';
142
 
143
    // 3. Write data to the socket.
144
    AsyncClientChargen->write(lineBuffer, LINE_LENGTH + 2);
145
 
146
    // 4. Advance the starting index for the next line (rotation).
147
    startIndex = (startIndex + 1) % PATTERN_LENGTH_FULL;
148
  }
149
}
150
 
151
// ---------------------- SETUP & LOOP ----------------------
152
 
153
void setup() {
154
  Serial.begin(115200);
155
  while (!Serial) {
156
    continue;
157
  }
158
  // Connecting to WiFi...
159
  WiFi.mode(WIFI_STA);
160
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
161
  Serial.print("Connecting to WiFi...");
162
  while (WiFi.status() != WL_CONNECTED) {
163
    delay(500);
164
    Serial.print(".");
165
  }
166
  Serial.println();
167
 
168
  // Create the Async TCP Server
169
  AsyncServerChargen = new AsyncServer(CHARGEN_PORT);
170
  // Set up the callback for new client connections.
171
  AsyncServerChargen->onClient(&handleClient, nullptr);
172
  AsyncServerChargen->begin();
173
  Serial.printf("Chargen Server (%s) started on port %d\n", WiFi.localIP().toString().c_str(), CHARGEN_PORT);
174
}
175
 
176
void loop() {
177
  // The async library handles everything in the background.
178
  // No code is needed here for the server to run.
179
}