@alteriom/painlessmesh 1.9.11 → 1.9.12

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 CHANGED
@@ -19,6 +19,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
19
19
 
20
20
  - TBD
21
21
 
22
+ ## [1.9.12] - 2025-12-18
23
+
24
+ ### Fixed
25
+
26
+ - **Hard Reset During Bridge Operations - AsyncClient abort() Timing Issue** - Fixed ESP32/ESP8266 heap corruption crashes during TCP connection failures and bridge operations
27
+ - **Root Cause**: Calling `client->abort()` synchronously before scheduling deferred AsyncClient deletion (1000ms+ later) left the client in an inconsistent state where AsyncTCP's internal cleanup tried to access the aborted client
28
+ - **Symptom**: Device crashes with "CORRUPT HEAP: Bad head at 0x40838cdc. Expected 0xabba1234 got 0x4200822e" and "assert failed: multi_heap_free multi_heap_poisoning.c:279" during connection cleanup
29
+ - **Solution**: Removed synchronous `abort()` call from `~BufferedConnection()` destructor
30
+ - The existing `close()` and `close(true)` calls are sufficient for connection termination
31
+ - According to AsyncTCP best practices, `abort()` should only be called immediately before `delete`, not before a deferred deletion
32
+ - Eliminates the 1000ms window where AsyncTCP tries to clean up an aborted but not-yet-deleted client
33
+ - **Testing**: All test suites pass (1000+ assertions), including TCP retry, connection, and mesh connectivity tests
34
+ - **Documentation**: Added ISSUE_ABORT_TIMING_FIX.md with detailed AsyncTCP best practices analysis
35
+ - **Impact**: Completes the AsyncClient lifecycle management improvements, eliminating the last known heap corruption scenario in connection cleanup
36
+
22
37
  ## [1.9.11] - 2025-12-18
23
38
 
24
39
  ### Fixed
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  <div align="center">
6
6
 
7
- **Version 1.9.11** - Latest release with critical stability fixes for bridge promotion and AsyncClient cleanup
7
+ **Version 1.9.12** - Latest release with AsyncClient abort() timing fix for heap corruption prevention
8
8
 
9
9
  [![CI/CD Pipeline](https://github.com/Alteriom/painlessMesh/actions/workflows/ci.yml/badge.svg)](https://github.com/Alteriom/painlessMesh/actions/workflows/ci.yml)
10
10
  [![Documentation](https://github.com/Alteriom/painlessMesh/actions/workflows/docs.yml/badge.svg)](https://github.com/Alteriom/painlessMesh/actions/workflows/docs.yml)
@@ -146,6 +146,23 @@ mesh.sendToInternet("https://api.callmebot.com/...", "", callback);
146
146
  3. Check HTTP status code in callback (200 = success)
147
147
  4. URL-encode special characters in the message
148
148
 
149
+ ### Understanding HTTP Status Codes
150
+
151
+ The callback provides `httpStatus` to indicate the result:
152
+
153
+ **SUCCESS (success = true):**
154
+ - `200 OK` - Standard success (most common for WhatsApp API)
155
+ - `201 Created` - Resource successfully created
156
+ - `202 Accepted` - Request accepted for processing
157
+ - `204 No Content` - Successful with no response body
158
+
159
+ **FAILURE (success = false):**
160
+ - `203 Non-Authoritative Information` - **Cached/proxied response, NOT actual delivery**
161
+ - `4xx` - Client error (bad request, unauthorized, not found, etc.)
162
+ - `5xx` - Server error (service unavailable, gateway timeout, etc.)
163
+
164
+ ⚠️ **Important:** HTTP 203 is treated as **FAILURE** because it indicates the response came from a cache or proxy, not from the actual WhatsApp API server. If you see `HTTP Status: 203`, the message was **NOT delivered**.
165
+
149
166
  ## Files
150
167
 
151
168
  - `sendToInternet.ino` - Main example sketch
@@ -184,6 +184,13 @@ void sendAlertToWhatsApp(String message) {
184
184
 
185
185
  // Use sendToInternet() to route the request through a gateway
186
186
  // The callback will be invoked when we get a response (or timeout)
187
+ //
188
+ // SUCCESS CODES: Only specific HTTP codes indicate genuine delivery:
189
+ // - 200 OK: Standard success (most common for WhatsApp API)
190
+ // - 201 Created, 202 Accepted, 204 No Content
191
+ //
192
+ // FAILURE: HTTP 203 (Non-Authoritative) is treated as FAILURE because
193
+ // it indicates a cached/proxied response, not actual delivery to WhatsApp.
187
194
  uint32_t msgId = mesh.sendToInternet(
188
195
  url,
189
196
  "", // No payload needed for GET request - params are in URL
package/library.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "type": "git",
7
7
  "url": "https://github.com/Alteriom/painlessMesh"
8
8
  },
9
- "version": "1.9.11",
9
+ "version": "1.9.12",
10
10
  "frameworks": [
11
11
  "arduino"
12
12
  ],
@@ -1,5 +1,5 @@
1
1
  name=Alteriom PainlessMesh
2
- version=1.9.11
2
+ version=1.9.12
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.11",
3
+ "version": "1.9.12",
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.11"
32
+ #define ALTERIOM_PAINLESS_MESH_VERSION "1.9.12"
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 11
35
+ #define ALTERIOM_PAINLESS_MESH_VERSION_PATCH 12
36
36
 
37
37
  /**
38
38
  * @brief Library description and usage information
@@ -2158,10 +2158,37 @@ class Mesh : public painlessmesh::Mesh<Connection> {
2158
2158
  }
2159
2159
 
2160
2160
  if (httpCode > 0) {
2161
- // Only 2xx status codes are treated as success
2161
+ // Only specific 2xx status codes indicate genuine success
2162
+ // 200 OK: Standard successful response
2163
+ // 201 Created: Resource successfully created
2164
+ // 202 Accepted: Request accepted for processing
2165
+ // 204 No Content: Successful with no response body
2166
+ //
2167
+ // Other 2xx codes like 203 (Non-Authoritative Information) often
2168
+ // indicate cached/proxied responses that may not represent actual
2169
+ // delivery to the destination service (e.g., WhatsApp API).
2170
+ //
2162
2171
  // 3xx redirects are not automatically followed
2163
- success = (httpCode >= 200 && httpCode < 300);
2164
- Log(COMMUNICATION, "HTTP request completed: code=%d\n", httpCode);
2172
+ success = (httpCode == 200 || httpCode == 201 ||
2173
+ httpCode == 202 || httpCode == 204);
2174
+
2175
+ if (success) {
2176
+ Log(COMMUNICATION, "HTTP request completed: code=%d\n", httpCode);
2177
+ } else if (httpCode >= 200 && httpCode < 300) {
2178
+ // Other 2xx codes - ambiguous success
2179
+ char errorBuf[128];
2180
+ snprintf(errorBuf, sizeof(errorBuf),
2181
+ "Ambiguous response - HTTP %d may indicate cached/proxied response, not actual delivery",
2182
+ httpCode);
2183
+ error = TSTRING(errorBuf);
2184
+ Log(ERROR, "HTTP request ambiguous: code=%d (treated as failure)\n", httpCode);
2185
+ } else {
2186
+ // 1xx, 3xx, 4xx, 5xx
2187
+ char errorBuf[32];
2188
+ snprintf(errorBuf, sizeof(errorBuf), "HTTP %d", httpCode);
2189
+ error = TSTRING(errorBuf);
2190
+ Log(ERROR, "HTTP request failed: code=%d\n", httpCode);
2191
+ }
2165
2192
  } else {
2166
2193
  error = http.errorToString(httpCode);
2167
2194
  Log(ERROR, "HTTP request failed: %s\n", error.c_str());
@@ -5,7 +5,7 @@
5
5
  * @file painlessMesh.h
6
6
  * @brief Main header file for Alteriom painlessMesh library
7
7
  *
8
- * @version 1.9.11
8
+ * @version 1.9.12
9
9
  * @date 2025-12-18
10
10
  *
11
11
  * painlessMesh is a user-friendly library for creating mesh networks with
@@ -28,11 +28,18 @@ static const uint32_t TCP_CLIENT_CLEANUP_DELAY_MS = 1000; // 1000ms delay before
28
28
  // This spacing ensures each deletion completes before the next one begins
29
29
  static const uint32_t TCP_CLIENT_DELETION_SPACING_MS = 250; // 250ms spacing between deletions
30
30
 
31
- // Global state to track AsyncClient deletion scheduling
31
+ // Global state to track AsyncClient deletion scheduling and execution
32
32
  // This ensures deletions are spaced out even when multiple deletion requests arrive simultaneously
33
- // Note: Thread safety is not required - ESP32/ESP8266 mesh runs single-threaded in Arduino framework
34
- // All mesh operations occur in the main loop or scheduler callbacks, never concurrently
35
- static uint32_t lastScheduledDeletionTime = 0; // Timestamp when last deletion was scheduled (milliseconds)
33
+ // The timestamp is updated both when a deletion is SCHEDULED and when it EXECUTES, providing
34
+ // double protection against concurrent cleanup operations even with scheduler jitter
35
+ //
36
+ // THREAD SAFETY: No synchronization needed because:
37
+ // - ESP32/ESP8266 Arduino framework is single-threaded by design
38
+ // - TaskScheduler executes callbacks sequentially in the main loop
39
+ // - The scheduler never runs tasks concurrently within the same mesh instance
40
+ // - All mesh operations (including deletion callbacks) execute in the same thread
41
+ // - Even when multiple tasks are ready, they execute one-at-a-time via scheduler->execute()
42
+ static uint32_t lastScheduledDeletionTime = 0; // Timestamp of last deletion scheduled/executed (milliseconds)
36
43
 
37
44
  // Shared buffer for reading/writing to the buffer
38
45
  static painlessmesh::buffer::temp_buffer_t shared_buffer;
@@ -98,6 +105,9 @@ inline void scheduleAsyncClientDeletion(Scheduler* scheduler, AsyncClient* clien
98
105
  uint32_t actualDelay = targetDeletionTime - currentTime;
99
106
 
100
107
  // Update the last scheduled deletion time
108
+ // IMPORTANT: We update this when scheduling, which provides a minimum guaranteed spacing
109
+ // between scheduled deletions. This ensures even with scheduler jitter, deletions won't
110
+ // be scheduled too close together.
101
111
  lastScheduledDeletionTime = targetDeletionTime;
102
112
 
103
113
  Log(CONNECTION, "%s: Scheduling AsyncClient deletion in %u ms (spaced from previous deletions)\n",
@@ -113,6 +123,12 @@ inline void scheduleAsyncClientDeletion(Scheduler* scheduler, AsyncClient* clien
113
123
  Task* cleanupTask = new Task(actualDelay * TASK_MILLISECOND, TASK_ONCE, [client, logPrefix]() {
114
124
  using namespace logger;
115
125
  Log(CONNECTION, "%s: Deferred cleanup of AsyncClient executing now\n", logPrefix);
126
+
127
+ // Update the last deletion time when the deletion actually executes
128
+ // This ensures subsequent deletions are spaced from the actual execution time,
129
+ // not just the scheduled time, preventing concurrent cleanup operations
130
+ lastScheduledDeletionTime = millis();
131
+
116
132
  delete client;
117
133
  });
118
134
 
@@ -149,7 +165,12 @@ class BufferedConnection
149
165
  if (!client->freeable()) {
150
166
  client->close(true);
151
167
  }
152
- client->abort();
168
+ // Note: client->abort() removed - calling it before deferred deletion
169
+ // can leave the client in an inconsistent state where AsyncTCP is still
170
+ // trying to clean up the aborted connection. The close() and close(true)
171
+ // calls above are sufficient for connection termination.
172
+ // See: AsyncTCP best practices - abort() should only be called immediately
173
+ // before delete, not before a deferred deletion.
153
174
 
154
175
  // Defer deletion of the AsyncClient to prevent heap corruption
155
176
  // Use the centralized deletion scheduler to ensure proper spacing between deletions