@alteriom/painlessmesh 1.9.12 → 1.9.14
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 +61 -0
- package/README.md +1 -1
- package/library.json +1 -1
- package/library.properties +1 -1
- package/package.json +1 -1
- package/src/AlteriomPainlessMesh.h +2 -2
- package/src/arduino/wifi.hpp +29 -5
- package/src/painlessMesh.h +2 -2
- package/src/painlessmesh/connection.hpp +7 -5
- package/src/painlessmesh/mesh.hpp +55 -5
package/CHANGELOG.md
CHANGED
|
@@ -19,6 +19,67 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
19
19
|
|
|
20
20
|
- TBD
|
|
21
21
|
|
|
22
|
+
## [1.9.14] - 2025-12-19
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- **Gateway Connection Timeout During Long HTTP Requests** - Fixed `sendToInternet()` timeouts with slow APIs like CallmeBot WhatsApp
|
|
27
|
+
- **Issue**: Mesh connections closed after 10s while HTTP requests could take 30s, causing "Request timed out" errors even when messages were successfully delivered
|
|
28
|
+
- **Fix**: Bridge now disables connection timeout during HTTP request processing, ensuring ACK delivery regardless of request duration
|
|
29
|
+
- **Impact**: WhatsApp/CallmeBot users and other slow API integrations now receive proper acknowledgments
|
|
30
|
+
- **Compatibility**: Fully backward compatible, no API changes required
|
|
31
|
+
- See GATEWAY_CONNECTION_TIMEOUT_FIX.md for detailed analysis
|
|
32
|
+
- **Hard Reset on ESP32-C6 - Insufficient AsyncClient Deletion Spacing** - Fixed ESP32-C6 heap corruption crashes caused by insufficient spacing between AsyncClient deletions during network disruptions
|
|
33
|
+
- **Root Cause**: The 250ms spacing between AsyncClient deletions was marginally insufficient for ESP32-C6 hardware, which uses AsyncTCP v3.3.0+ and has different timing characteristics due to RISC-V architecture and enhanced cleanup validation
|
|
34
|
+
- **Symptom**: Device crashes with "CORRUPT HEAP: Bad head at 0x40838a24. Expected 0xabba1234 got 0x4081fae4" even with deletions spaced 264ms apart (only 14ms above minimum), particularly during TCP retries, channel changes, and sendToInternet() operations
|
|
35
|
+
- **Hardware Specific**: ESP32-C6 requires more cleanup time due to:
|
|
36
|
+
- AsyncTCP v3.3.0+ with additional validation steps
|
|
37
|
+
- RISC-V architecture vs. Xtensa (different instruction timing)
|
|
38
|
+
- Enhanced heap allocator with more aggressive corruption detection
|
|
39
|
+
- Different WiFi stack and memory management implementation
|
|
40
|
+
- **Solution**: Increased `TCP_CLIENT_DELETION_SPACING_MS` from 250ms to 500ms
|
|
41
|
+
- Provides 2x safety margin for universal ESP32 compatibility
|
|
42
|
+
- Accommodates ESP32-C6, ESP32-S2/S3, ESP32-C3, ESP32-H2, and future variants
|
|
43
|
+
- Maintains backward compatibility with ESP32/ESP8266
|
|
44
|
+
- Minimal performance impact (250ms additional delay in worst-case scenarios)
|
|
45
|
+
- **Analysis**: Crash occurred at 264ms spacing, demonstrating 250ms was too close to minimum requirement for ESP32-C6; 500ms provides adequate safety margin (89% buffer vs. observed crash)
|
|
46
|
+
- **Testing**: All test suites pass (68+ assertions in TCP/connection tests, 1000+ total assertions)
|
|
47
|
+
- **Documentation**: Added ISSUE_HARD_RESET_ESP32C6_SPACING_FIX.md with detailed ESP32-C6 analysis, architecture comparison, and AsyncTCP v3.3.0+ timing requirements
|
|
48
|
+
- **Impact**: Eliminates critical heap corruption on ESP32-C6 and provides enhanced safety for all ESP32 variants
|
|
49
|
+
|
|
50
|
+
## [1.9.13] - 2025-12-19
|
|
51
|
+
|
|
52
|
+
### Fixed
|
|
53
|
+
|
|
54
|
+
- **HTTP 203 "Permanent Response" Issue with sendToInternet()** - Fixed issue where HTTP 203 (Non-Authoritative Information) responses from APIs like Callmebot WhatsApp appeared "permanent" without automatic recovery
|
|
55
|
+
- **Root Cause**: HTTP 203 responses (indicating cached/proxied responses) were correctly identified as failures but treated as terminal - requests were immediately removed from the pending queue without retry
|
|
56
|
+
- **Symptom**: User sees repeated "❌ Failed to send WhatsApp: Ambiguous response - HTTP 203..." messages with no automatic recovery, requiring manual intervention
|
|
57
|
+
- **Solution**: Modified `handleGatewayAck()` to implement intelligent retry logic for retryable failure types:
|
|
58
|
+
- HTTP 203 (Non-Authoritative Information) - cached/proxied responses, often temporary
|
|
59
|
+
- HTTP 5xx (Server Errors) - transient server issues (500, 502, 503, 504, etc.)
|
|
60
|
+
- HTTP 429 (Too Many Requests) - rate limiting with exponential backoff
|
|
61
|
+
- HTTP 0 (Network Errors) - connection failures, timeouts
|
|
62
|
+
- Non-retryable: HTTP 4xx client errors (except 429), HTTP 3xx redirects
|
|
63
|
+
- **Retry Behavior**: Uses exponential backoff (2s, 4s, 8s, 16s...) with configurable max retries (default: 3)
|
|
64
|
+
- **Testing**: Added comprehensive test coverage (50 assertions in 7 test cases) for retry classification and behavior
|
|
65
|
+
- **Documentation**: Added ISSUE_HTTP_203_RETRY_FIX.md with detailed analysis, examples, and HTTP 203 explanation
|
|
66
|
+
- **Impact**: Automatic recovery from temporary API caching issues, eliminating the "permanent" failure problem
|
|
67
|
+
- **API Compatibility**: Fully backward compatible, no breaking changes, existing code works unchanged
|
|
68
|
+
|
|
69
|
+
- **Hard Reset on Node - AsyncClient Deletion Race Condition** - Fixed ESP32/ESP8266 heap corruption crashes caused by race condition in deletion spacing logic during network disruptions
|
|
70
|
+
- **Root Cause**: The `scheduleAsyncClientDeletion()` function was updating `lastScheduledDeletionTime` at BOTH scheduling time (line 111) and execution time (line 130), creating a race condition where scheduler jitter could cause deletions to execute with insufficient spacing
|
|
71
|
+
- **Symptom**: Device crashes with "CORRUPT HEAP: Bad head at 0x40831d54. Expected 0xabba1234 got 0x4081fae4" even with 250ms spacing constant, particularly during network disruptions, TCP retries, or WiFi reconnection cycles
|
|
72
|
+
- **Race Condition Scenario**: When Task A scheduled for time T1 executes late at T1+jitter, it updates `lastScheduledDeletionTime` to T1+jitter, potentially AFTER Task B's scheduled time (which was calculated based on T1), causing Task B to execute with less than 250ms spacing
|
|
73
|
+
- **Solution**: Removed the execution-time update of `lastScheduledDeletionTime` in the task callback, relying solely on the scheduling-time update
|
|
74
|
+
- Ensures consistent, predictable spacing based on planned execution times
|
|
75
|
+
- Eliminates race condition where execution-time updates could "rewind" the timestamp
|
|
76
|
+
- Makes spacing calculation immune to scheduler jitter
|
|
77
|
+
- Guarantees minimum 250ms spacing between AsyncClient deletions in all scenarios
|
|
78
|
+
- **Why This Works**: By only updating at scheduling time, subsequent deletions are always spaced from the PREVIOUS deletion's planned time, not its actual execution time, providing conservative spacing guarantees even with scheduler jitter
|
|
79
|
+
- **Testing**: All test suites pass (1000+ assertions), including TCP retry (52), connection (3), and timing tests (7)
|
|
80
|
+
- **Documentation**: Added ISSUE_HARD_RESET_DELETION_RACE_FIX.md with detailed mathematical proof and race condition analysis
|
|
81
|
+
- **Impact**: Eliminates heap corruption crashes during network disruptions, enables stable operation through TCP retries and WiFi reconnection cycles
|
|
82
|
+
|
|
22
83
|
## [1.9.12] - 2025-12-18
|
|
23
84
|
|
|
24
85
|
### Fixed
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
<div align="center">
|
|
6
6
|
|
|
7
|
-
**Version 1.9.
|
|
7
|
+
**Version 1.9.14** - Latest release with ESP32-C6 spacing fix and gateway timeout improvements
|
|
8
8
|
|
|
9
9
|
[](https://github.com/Alteriom/painlessMesh/actions/workflows/ci.yml)
|
|
10
10
|
[](https://github.com/Alteriom/painlessMesh/actions/workflows/docs.yml)
|
package/library.json
CHANGED
package/library.properties
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
name=Alteriom PainlessMesh
|
|
2
|
-
version=1.9.
|
|
2
|
+
version=1.9.14
|
|
3
3
|
author=Coopdis,Scotty Franzyshen,Edwin van Leeuwen,Germán Martín,Maximilian Schwarz,Doanh Doanh,Alteriom
|
|
4
4
|
maintainer=Alteriom
|
|
5
5
|
sentence=A painless way to setup a mesh with ESP8266 and ESP32 devices with Alteriom extensions
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alteriom/painlessmesh",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.14",
|
|
4
4
|
"description": "painlessMesh is a user-friendly library for creating mesh networks with ESP8266 and ESP32 devices. This Alteriom fork includes additional packages for sensor data (SensorPackage), device commands (CommandPackage), and status monitoring (StatusPackage). It handles routing and network management automatically, so you can focus on your application. The library uses JSON-based messaging and syncs time across all nodes, making it ideal for coordinated behaviour like synchronized light displays or sensor networks reporting to a central node.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"arduino",
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
/**
|
|
30
30
|
* @brief AlteriomPainlessMesh library version information
|
|
31
31
|
*/
|
|
32
|
-
#define ALTERIOM_PAINLESS_MESH_VERSION "1.9.
|
|
32
|
+
#define ALTERIOM_PAINLESS_MESH_VERSION "1.9.14"
|
|
33
33
|
#define ALTERIOM_PAINLESS_MESH_VERSION_MAJOR 1
|
|
34
34
|
#define ALTERIOM_PAINLESS_MESH_VERSION_MINOR 9
|
|
35
|
-
#define ALTERIOM_PAINLESS_MESH_VERSION_PATCH
|
|
35
|
+
#define ALTERIOM_PAINLESS_MESH_VERSION_PATCH 14
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* @brief Library description and usage information
|
package/src/arduino/wifi.hpp
CHANGED
|
@@ -1246,9 +1246,16 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
1246
1246
|
Log(STARTUP,
|
|
1247
1247
|
"initBridgeStatusBroadcast(): Setting up bridge status broadcast\n");
|
|
1248
1248
|
|
|
1249
|
+
// CRITICAL FIX: Schedule tasks with a small delay to avoid crashes when
|
|
1250
|
+
// called immediately after stop()/init cycle. The delay allows the scheduler
|
|
1251
|
+
// and internal task structures to stabilize before adding new tasks.
|
|
1252
|
+
// This fixes the "Load access fault" Guru Meditation error that occurred
|
|
1253
|
+
// when promoting to bridge role.
|
|
1254
|
+
const uint32_t INIT_DELAY_MS = 100;
|
|
1255
|
+
|
|
1249
1256
|
// Register ourselves as a bridge in the knownBridges list
|
|
1250
1257
|
// This ensures the bridge knows about itself and reports correct status
|
|
1251
|
-
this->addTask([this]() {
|
|
1258
|
+
this->addTask(INIT_DELAY_MS, TASK_ONCE, [this]() {
|
|
1252
1259
|
// Check Internet connectivity: WiFi connected AND valid IP address
|
|
1253
1260
|
bool hasInternet = (WiFi.status() == WL_CONNECTED) &&
|
|
1254
1261
|
(WiFi.localIP() != IPAddress(0, 0, 0, 0));
|
|
@@ -1269,12 +1276,16 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
1269
1276
|
});
|
|
1270
1277
|
|
|
1271
1278
|
// Create periodic task to broadcast bridge status
|
|
1272
|
-
|
|
1273
|
-
|
|
1279
|
+
// Schedule with delay to avoid crashes during stop/init cycle
|
|
1280
|
+
this->addTask(INIT_DELAY_MS, TASK_ONCE, [this]() {
|
|
1281
|
+
bridgeStatusTask = this->addTask(this->bridgeStatusIntervalMs, TASK_FOREVER,
|
|
1282
|
+
[this]() { this->sendBridgeStatus(); });
|
|
1283
|
+
});
|
|
1274
1284
|
|
|
1275
1285
|
// Send immediate broadcast so nodes can discover this bridge right away
|
|
1276
1286
|
// This ensures bridge is discoverable before the first periodic broadcast
|
|
1277
|
-
|
|
1287
|
+
// Use slightly larger delay to allow bridge status task to be set up first
|
|
1288
|
+
this->addTask(INIT_DELAY_MS + 50, TASK_ONCE, [this]() {
|
|
1278
1289
|
Log(STARTUP, "Sending initial bridge status broadcast\n");
|
|
1279
1290
|
this->sendBridgeStatus();
|
|
1280
1291
|
});
|
|
@@ -2101,13 +2112,26 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
2101
2112
|
|
|
2102
2113
|
this->callbackList.onPackage(
|
|
2103
2114
|
protocol::GATEWAY_DATA, [this](protocol::Variant& variant,
|
|
2104
|
-
std::shared_ptr<Connection
|
|
2115
|
+
std::shared_ptr<Connection> connection, uint32_t) {
|
|
2105
2116
|
auto pkg = variant.to<gateway::GatewayDataPackage>();
|
|
2106
2117
|
|
|
2107
2118
|
Log(COMMUNICATION,
|
|
2108
2119
|
"Gateway received Internet request: msgId=%u dest=%s\n",
|
|
2109
2120
|
pkg.messageId, pkg.destination.c_str());
|
|
2110
2121
|
|
|
2122
|
+
// Disable connection timeout during HTTP request processing
|
|
2123
|
+
// HTTP requests can take up to 30 seconds (GATEWAY_HTTP_TIMEOUT_MS)
|
|
2124
|
+
// but mesh connections timeout after 10 seconds (NODE_TIMEOUT).
|
|
2125
|
+
// We disable the timeout here to prevent connection drop during
|
|
2126
|
+
// long-running HTTP requests. The timeout will be automatically
|
|
2127
|
+
// re-enabled when the next sync packet is received.
|
|
2128
|
+
if (connection) {
|
|
2129
|
+
connection->timeOutTask.disable();
|
|
2130
|
+
Log(COMMUNICATION,
|
|
2131
|
+
"Gateway disabled connection timeout for node %u during HTTP request\n",
|
|
2132
|
+
connection->nodeId);
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2111
2135
|
// Check Internet connectivity
|
|
2112
2136
|
if (WiFi.status() != WL_CONNECTED) {
|
|
2113
2137
|
sendGatewayAck(pkg, false, 0, "Gateway not connected to Internet");
|
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.
|
|
9
|
-
* @date 2025-12-
|
|
8
|
+
* @version 1.9.14
|
|
9
|
+
* @date 2025-12-19
|
|
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
|
|
@@ -26,7 +26,9 @@ static const uint32_t TCP_CLIENT_CLEANUP_DELAY_MS = 1000; // 1000ms delay before
|
|
|
26
26
|
// When multiple AsyncClients are deleted in rapid succession, the AsyncTCP library's
|
|
27
27
|
// internal cleanup routines can interfere with each other, causing heap corruption
|
|
28
28
|
// This spacing ensures each deletion completes before the next one begins
|
|
29
|
-
|
|
29
|
+
// Increased from 250ms to 500ms to support ESP32-C6 and other ESP32 variants which
|
|
30
|
+
// require more time for AsyncTCP internal cleanup operations
|
|
31
|
+
static const uint32_t TCP_CLIENT_DELETION_SPACING_MS = 500; // 500ms spacing between deletions
|
|
30
32
|
|
|
31
33
|
// Global state to track AsyncClient deletion scheduling and execution
|
|
32
34
|
// This ensures deletions are spaced out even when multiple deletion requests arrive simultaneously
|
|
@@ -124,10 +126,10 @@ inline void scheduleAsyncClientDeletion(Scheduler* scheduler, AsyncClient* clien
|
|
|
124
126
|
using namespace logger;
|
|
125
127
|
Log(CONNECTION, "%s: Deferred cleanup of AsyncClient executing now\n", logPrefix);
|
|
126
128
|
|
|
127
|
-
//
|
|
128
|
-
// This ensures
|
|
129
|
-
//
|
|
130
|
-
|
|
129
|
+
// Note: lastScheduledDeletionTime is updated at scheduling time (before this task runs), not here
|
|
130
|
+
// This ensures consistent spacing based on when deletions were scheduled, preventing
|
|
131
|
+
// the race condition where execution-time updates could "rewind" the timestamp
|
|
132
|
+
// and cause subsequent deletions to be scheduled too close together
|
|
131
133
|
|
|
132
134
|
delete client;
|
|
133
135
|
});
|
|
@@ -1503,13 +1503,63 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
1503
1503
|
|
|
1504
1504
|
PendingInternetRequest& request = it->second;
|
|
1505
1505
|
|
|
1506
|
-
//
|
|
1507
|
-
if (
|
|
1508
|
-
|
|
1506
|
+
// Check if this is a success response
|
|
1507
|
+
if (ack.success) {
|
|
1508
|
+
// Success - call callback and remove request
|
|
1509
|
+
if (request.callback) {
|
|
1510
|
+
request.callback(ack.success, ack.httpStatus, ack.error);
|
|
1511
|
+
}
|
|
1512
|
+
pendingInternetRequests.erase(it);
|
|
1513
|
+
return;
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
// Failure response - determine if retryable
|
|
1517
|
+
bool isRetryable = false;
|
|
1518
|
+
|
|
1519
|
+
// HTTP 203 (Non-Authoritative Information) indicates cached/proxied response
|
|
1520
|
+
// This is often temporary and retrying may succeed when cache expires
|
|
1521
|
+
if (ack.httpStatus == 203) {
|
|
1522
|
+
isRetryable = true;
|
|
1523
|
+
Log(COMMUNICATION, "handleGatewayAck(): HTTP 203 detected, marking as retryable\n");
|
|
1524
|
+
}
|
|
1525
|
+
// HTTP 5xx server errors are typically transient
|
|
1526
|
+
else if (ack.httpStatus >= 500 && ack.httpStatus < 600) {
|
|
1527
|
+
isRetryable = true;
|
|
1528
|
+
Log(COMMUNICATION, "handleGatewayAck(): HTTP 5xx server error, marking as retryable\n");
|
|
1529
|
+
}
|
|
1530
|
+
// HTTP 429 (Too Many Requests) should be retried with backoff
|
|
1531
|
+
else if (ack.httpStatus == 429) {
|
|
1532
|
+
isRetryable = true;
|
|
1533
|
+
Log(COMMUNICATION, "handleGatewayAck(): HTTP 429 rate limit, marking as retryable\n");
|
|
1509
1534
|
}
|
|
1535
|
+
// Network errors (httpStatus == 0) are retryable
|
|
1536
|
+
else if (ack.httpStatus == 0) {
|
|
1537
|
+
isRetryable = true;
|
|
1538
|
+
Log(COMMUNICATION, "handleGatewayAck(): Network error, marking as retryable\n");
|
|
1539
|
+
}
|
|
1540
|
+
// HTTP 4xx client errors (except 429) are NOT retryable
|
|
1541
|
+
// HTTP 3xx redirects are NOT retryable (should be followed by HTTPClient)
|
|
1542
|
+
// Other status codes are NOT retryable
|
|
1510
1543
|
|
|
1511
|
-
//
|
|
1512
|
-
|
|
1544
|
+
// If retryable and have retries left, schedule retry
|
|
1545
|
+
if (isRetryable && request.retryCount < request.maxRetries) {
|
|
1546
|
+
Log(COMMUNICATION, "handleGatewayAck(): Scheduling retry for msgId=%u (attempt %u/%u)\n",
|
|
1547
|
+
ack.messageId, request.retryCount + 1, request.maxRetries);
|
|
1548
|
+
scheduleInternetRetry(ack.messageId);
|
|
1549
|
+
} else {
|
|
1550
|
+
// Not retryable or max retries reached - call callback and remove
|
|
1551
|
+
if (request.retryCount >= request.maxRetries) {
|
|
1552
|
+
Log(ERROR, "handleGatewayAck(): Max retries reached for msgId=%u\n", ack.messageId);
|
|
1553
|
+
} else {
|
|
1554
|
+
Log(COMMUNICATION, "handleGatewayAck(): Non-retryable failure for msgId=%u (HTTP %u)\n",
|
|
1555
|
+
ack.messageId, ack.httpStatus);
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
if (request.callback) {
|
|
1559
|
+
request.callback(ack.success, ack.httpStatus, ack.error);
|
|
1560
|
+
}
|
|
1561
|
+
pendingInternetRequests.erase(it);
|
|
1562
|
+
}
|
|
1513
1563
|
}
|
|
1514
1564
|
|
|
1515
1565
|
/**
|