@alteriom/painlessmesh 1.9.5 → 1.9.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +78 -0
- package/CONTRIBUTING.md +3 -3
- package/README.md +60 -184
- package/docs/README.md +1 -1
- package/docs/troubleshooting/common-issues.md +101 -0
- package/docs/troubleshooting/external-device-connection.md +283 -0
- package/examples/bridge/bridge.ino +23 -0
- package/examples/bridge_failover/bridge_failover.ino +27 -0
- package/examples/sendToInternet/sendToInternet.ino +21 -3
- package/library.json +1 -1
- package/library.properties +1 -1
- package/package.json +4 -4
- package/src/AlteriomPainlessMesh.h +3 -3
- package/src/arduino/wifi.hpp +874 -535
- package/src/painlessMesh.h +2 -2
- package/src/painlessMeshSTA.cpp +11 -2
- package/src/painlessmesh/mesh.hpp +6 -2
- package/src/painlessmesh/tcp.hpp +103 -4
package/src/painlessMesh.h
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* @file painlessMesh.h
|
|
6
6
|
* @brief Main header file for Alteriom painlessMesh library
|
|
7
7
|
*
|
|
8
|
-
* @version 1.
|
|
9
|
-
* @date 2025-
|
|
8
|
+
* @version 1.9.7
|
|
9
|
+
* @date 2025-12-13
|
|
10
10
|
*
|
|
11
11
|
* painlessMesh is a user-friendly library for creating mesh networks with
|
|
12
12
|
* ESP8266 and ESP32 devices. This Alteriom fork includes additional packages
|
package/src/painlessMeshSTA.cpp
CHANGED
|
@@ -233,10 +233,19 @@ void ICACHE_FLASH_ATTR StationScan::connectToAP() {
|
|
|
233
233
|
Log(CONNECTION,
|
|
234
234
|
"connectToAP(): Restarting AP from channel %d to channel %d\n",
|
|
235
235
|
oldChannel, detectedChannel);
|
|
236
|
-
|
|
237
|
-
|
|
236
|
+
|
|
237
|
+
// Disconnect AP and allow WiFi stack to fully reset
|
|
238
|
+
// Using true parameter ensures DHCP server is properly stopped
|
|
239
|
+
WiFi.softAPdisconnect(true);
|
|
240
|
+
delay(200); // Increased delay to ensure complete WiFi stack reset
|
|
241
|
+
|
|
238
242
|
// Call apInit via friend class access (StationScan is friend of wifi::Mesh)
|
|
239
243
|
mesh->apInit(mesh->getNodeId());
|
|
244
|
+
|
|
245
|
+
// Additional stabilization delay after AP restart
|
|
246
|
+
// This ensures DHCP server is fully initialized before clients connect
|
|
247
|
+
delay(100);
|
|
248
|
+
|
|
240
249
|
Log(CONNECTION, "connectToAP(): AP restarted on channel %d\n", detectedChannel);
|
|
241
250
|
}
|
|
242
251
|
// Reset counter only when mesh is found on a new channel
|
|
@@ -3134,7 +3134,11 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
3134
3134
|
using namespace logger;
|
|
3135
3135
|
Log(CONNECTION, "eraseClosedConnections():\n");
|
|
3136
3136
|
this->subs.remove_if(
|
|
3137
|
-
[](const std::shared_ptr<T> &conn) {
|
|
3137
|
+
[](const std::shared_ptr<T> &conn) {
|
|
3138
|
+
// Null check for safety - should never happen but prevents crashes
|
|
3139
|
+
if (!conn) return true;
|
|
3140
|
+
return !conn->connected();
|
|
3141
|
+
});
|
|
3138
3142
|
}
|
|
3139
3143
|
|
|
3140
3144
|
public: // Windows MSVC: TCP lambdas need access to droppedConnectionCallbacks
|
|
@@ -3238,7 +3242,7 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
3238
3242
|
Mesh &, protocol::NodeTree, std::shared_ptr<T> conn);
|
|
3239
3243
|
friend void painlessmesh::tcp::initServer<T, Mesh>(AsyncServer &, Mesh &);
|
|
3240
3244
|
friend void painlessmesh::tcp::connect<T, Mesh>(AsyncClient &, IPAddress,
|
|
3241
|
-
uint16_t, Mesh
|
|
3245
|
+
uint16_t, Mesh &, uint8_t);
|
|
3242
3246
|
};
|
|
3243
3247
|
|
|
3244
3248
|
class Connection : public painlessmesh::layout::Neighbour,
|
package/src/painlessmesh/tcp.hpp
CHANGED
|
@@ -10,6 +10,17 @@
|
|
|
10
10
|
|
|
11
11
|
namespace painlessmesh {
|
|
12
12
|
namespace tcp {
|
|
13
|
+
|
|
14
|
+
// TCP connection retry configuration
|
|
15
|
+
// These can be tuned for different network conditions
|
|
16
|
+
// Increased values to better handle real-world mesh network conditions where:
|
|
17
|
+
// - TCP server may need more time to be ready after AP initialization
|
|
18
|
+
// - Network stack stabilization takes longer on some hardware
|
|
19
|
+
// - Multiple nodes connecting simultaneously can cause temporary overload
|
|
20
|
+
static const uint8_t TCP_CONNECT_MAX_RETRIES = 5; // Max retry attempts before giving up
|
|
21
|
+
static const uint32_t TCP_CONNECT_RETRY_DELAY_MS = 1000; // Delay between retry attempts (1 second)
|
|
22
|
+
static const uint32_t TCP_CONNECT_STABILIZATION_DELAY_MS = 500; // Delay after IP acquisition (500ms)
|
|
23
|
+
|
|
13
24
|
inline uint32_t encodeNodeId(const uint8_t *hwaddr) {
|
|
14
25
|
using namespace painlessmesh::logger;
|
|
15
26
|
Log(GENERAL, "encodeNodeId():\n");
|
|
@@ -41,13 +52,101 @@ void initServer(AsyncServer &server, M &mesh) {
|
|
|
41
52
|
server.begin();
|
|
42
53
|
}
|
|
43
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Establish TCP connection with retry mechanism and exponential backoff
|
|
57
|
+
*
|
|
58
|
+
* This function attempts to connect to the mesh network via TCP.
|
|
59
|
+
* If the connection fails (error -14 ERR_CONN or other errors), it will
|
|
60
|
+
* retry up to TCP_CONNECT_MAX_RETRIES times before triggering a full
|
|
61
|
+
* WiFi reconnection cycle.
|
|
62
|
+
*
|
|
63
|
+
* The retry mechanism helps handle timing issues where:
|
|
64
|
+
* - The TCP server may not be immediately ready after AP initialization
|
|
65
|
+
* - Network stack may need time to stabilize after IP acquisition
|
|
66
|
+
* - Transient network conditions may cause temporary connection failures
|
|
67
|
+
* - Multiple nodes connecting simultaneously may cause temporary overload
|
|
68
|
+
*
|
|
69
|
+
* Exponential backoff is used to increase delay between retries, which:
|
|
70
|
+
* - Gives the TCP server more time to recover from overload
|
|
71
|
+
* - Reduces network contention when multiple nodes are retrying
|
|
72
|
+
* - Improves overall connection success rate in congested networks
|
|
73
|
+
*
|
|
74
|
+
* @param client AsyncClient to use for connection
|
|
75
|
+
* @param ip Target IP address
|
|
76
|
+
* @param port Target port
|
|
77
|
+
* @param mesh Reference to mesh instance for callbacks
|
|
78
|
+
* @param retryCount Current retry attempt (default 0, used internally for recursion)
|
|
79
|
+
*/
|
|
44
80
|
template <class T, class M>
|
|
45
|
-
void connect(AsyncClient &client, IPAddress ip, uint16_t port, M &mesh
|
|
81
|
+
void connect(AsyncClient &client, IPAddress ip, uint16_t port, M &mesh,
|
|
82
|
+
uint8_t retryCount = 0) {
|
|
46
83
|
using namespace logger;
|
|
47
|
-
|
|
84
|
+
|
|
85
|
+
Log(CONNECTION, "tcp::connect(): Attempting connection to port %d (attempt %d/%d)\n",
|
|
86
|
+
port, retryCount + 1, TCP_CONNECT_MAX_RETRIES + 1);
|
|
87
|
+
|
|
88
|
+
// Store retry count and connection parameters for the error handler
|
|
89
|
+
// We need to capture these by value since they're used in the lambda
|
|
90
|
+
client.onError([&mesh, ip, port, retryCount](void *, AsyncClient *client, int8_t err) {
|
|
48
91
|
if (mesh.semaphoreTake()) {
|
|
49
|
-
Log(CONNECTION, "tcp_err(): error trying to connect %d\n",
|
|
50
|
-
|
|
92
|
+
Log(CONNECTION, "tcp_err(): error trying to connect %d (attempt %d/%d)\n",
|
|
93
|
+
err, retryCount + 1, TCP_CONNECT_MAX_RETRIES + 1);
|
|
94
|
+
|
|
95
|
+
// Check if we have retries left - retry logic only works on real hardware
|
|
96
|
+
// In test environment (PAINLESSMESH_BOOST), fall through to dropped connection
|
|
97
|
+
// Note: ip and port are used in retry logic below, suppress unused warnings for test builds
|
|
98
|
+
(void)ip;
|
|
99
|
+
(void)port;
|
|
100
|
+
#if !defined(PAINLESSMESH_BOOST) && (defined(ESP32) || defined(ESP8266))
|
|
101
|
+
if (retryCount < TCP_CONNECT_MAX_RETRIES) {
|
|
102
|
+
// Calculate delay with exponential backoff: base_delay * 2^retryCount
|
|
103
|
+
// This gives increasing time between retries as failures accumulate:
|
|
104
|
+
// - retryCount=0: 1000ms * 1 = 1s
|
|
105
|
+
// - retryCount=1: 1000ms * 2 = 2s
|
|
106
|
+
// - retryCount=2: 1000ms * 4 = 4s
|
|
107
|
+
// - retryCount=3: 1000ms * 8 = 8s (capped at 8)
|
|
108
|
+
// - retryCount=4: 1000ms * 8 = 8s (capped at 8)
|
|
109
|
+
// Cap multiplier at 8 to prevent excessive delays
|
|
110
|
+
uint8_t backoffMultiplier = (retryCount < 3) ? (1U << retryCount) : 8;
|
|
111
|
+
uint32_t retryDelay = TCP_CONNECT_RETRY_DELAY_MS * backoffMultiplier;
|
|
112
|
+
|
|
113
|
+
Log(CONNECTION, "tcp_err(): Scheduling retry in %u ms (backoff x%d)\n",
|
|
114
|
+
retryDelay, backoffMultiplier);
|
|
115
|
+
|
|
116
|
+
// Schedule a retry after a delay using the mesh's task scheduler
|
|
117
|
+
// Note: &mesh is captured by reference because:
|
|
118
|
+
// 1. Mesh is a singleton that lives for the program's lifetime
|
|
119
|
+
// 2. The task scheduler belongs to the mesh, so mesh is always valid when task runs
|
|
120
|
+
// 3. Copying the mesh object is not possible/allowed
|
|
121
|
+
// Recursion depth is strictly bounded by TCP_CONNECT_MAX_RETRIES (default: 5)
|
|
122
|
+
mesh.addTask([&mesh, ip, port, retryCount]() {
|
|
123
|
+
Log(CONNECTION, "tcp_err(): Retrying TCP connection...\n");
|
|
124
|
+
|
|
125
|
+
// Create a new AsyncClient for the retry
|
|
126
|
+
// On success, the client is managed by the Connection object
|
|
127
|
+
// On failure, the onError handler for the new client will handle cleanup
|
|
128
|
+
AsyncClient *pRetryConn = new AsyncClient();
|
|
129
|
+
connect<T, M>((*pRetryConn), ip, port, mesh, retryCount + 1);
|
|
130
|
+
}, retryDelay);
|
|
131
|
+
|
|
132
|
+
// Delete the current failed client to prevent memory leak
|
|
133
|
+
// The AsyncClient is no longer needed after connection failure
|
|
134
|
+
delete client;
|
|
135
|
+
|
|
136
|
+
mesh.semaphoreGive();
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// All retries exhausted - clean up the failed client and trigger full reconnection
|
|
141
|
+
Log(CONNECTION, "tcp_err(): All %d retries exhausted, triggering WiFi reconnection\n",
|
|
142
|
+
TCP_CONNECT_MAX_RETRIES + 1);
|
|
143
|
+
delete client;
|
|
144
|
+
#endif
|
|
145
|
+
// Defer callback execution to avoid crashes in error handler context
|
|
146
|
+
// Execute callbacks after semaphore is released and error handler completes
|
|
147
|
+
mesh.addTask([&mesh]() {
|
|
148
|
+
mesh.droppedConnectionCallbacks.execute(0, true);
|
|
149
|
+
});
|
|
51
150
|
mesh.semaphoreGive();
|
|
52
151
|
}
|
|
53
152
|
});
|