@alteriom/painlessmesh 1.9.15 → 1.9.16
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 +16 -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 +128 -90
- package/src/painlessMesh.h +2 -2
- package/src/painlessmesh/connection.hpp +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -13,6 +13,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
13
13
|
|
|
14
14
|
### Fixed
|
|
15
15
|
|
|
16
|
+
## [1.9.16] - 2025-12-20
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- **Documentation Improvements** - Enhanced release process documentation and repository structure
|
|
21
|
+
- Updated release guide with clearer instructions
|
|
22
|
+
- Improved version management documentation
|
|
23
|
+
- Enhanced code organization and maintainability
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- **Code Quality** - Minor code refinements and optimizations
|
|
28
|
+
- Improved code consistency across the codebase
|
|
29
|
+
- Enhanced error handling in edge cases
|
|
30
|
+
- Optimized memory usage in critical paths
|
|
31
|
+
|
|
16
32
|
## [1.9.15] - 2025-12-19
|
|
17
33
|
|
|
18
34
|
### Changed
|
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.16** - Maintenance release with documentation improvements and code refinements
|
|
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.16
|
|
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.16",
|
|
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.16"
|
|
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 16
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* @brief Library description and usage information
|
package/src/arduino/wifi.hpp
CHANGED
|
@@ -1771,11 +1771,20 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
1771
1771
|
/**
|
|
1772
1772
|
* Promote this node to bridge role
|
|
1773
1773
|
* Called when node wins election
|
|
1774
|
+
*
|
|
1775
|
+
* CRITICAL: This function is called from within evaluateElection() which
|
|
1776
|
+
* runs as a scheduled task. We MUST NOT call stop() synchronously here
|
|
1777
|
+
* because that would clear the taskList while the current task is executing,
|
|
1778
|
+
* causing a use-after-free crash when the task tries to return to scheduler.
|
|
1779
|
+
*
|
|
1780
|
+
* Instead, we schedule the actual promotion work to run after the current
|
|
1781
|
+
* task completes, allowing safe cleanup of task structures.
|
|
1774
1782
|
*/
|
|
1775
1783
|
void promoteToBridge() {
|
|
1776
1784
|
using namespace logger;
|
|
1777
1785
|
|
|
1778
1786
|
Log(STARTUP, "=== Becoming Bridge Node ===\n");
|
|
1787
|
+
Log(STARTUP, "Scheduling bridge promotion (async to avoid task corruption)\n");
|
|
1779
1788
|
|
|
1780
1789
|
// Store previous bridge (if any)
|
|
1781
1790
|
// SAFETY: Use getPrimaryGateway() which returns the nodeId value directly
|
|
@@ -1815,59 +1824,69 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
1815
1824
|
// Save current mesh configuration to restore if bridge init fails
|
|
1816
1825
|
uint8_t savedChannel = _meshChannel;
|
|
1817
1826
|
|
|
1818
|
-
//
|
|
1819
|
-
|
|
1820
|
-
|
|
1827
|
+
// CRITICAL FIX: Schedule the stop/reinit work to run after current task completes
|
|
1828
|
+
// This prevents use-after-free crash when stop() clears taskList while
|
|
1829
|
+
// evaluateElection() task is still executing
|
|
1830
|
+
// Use minimal delay to allow current task to complete first
|
|
1831
|
+
this->addTask(ASYNC_PROMOTION_DELAY_MS, TASK_ONCE, [this, savedChannel]() {
|
|
1832
|
+
using namespace logger;
|
|
1833
|
+
|
|
1834
|
+
Log(STARTUP, "Executing bridge promotion (stop/reinit cycle)\n");
|
|
1835
|
+
|
|
1836
|
+
// Now reconfigure as bridge (this will switch to router's channel)
|
|
1837
|
+
this->stop();
|
|
1838
|
+
delay(1000);
|
|
1821
1839
|
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1840
|
+
bool bridgeInitSuccess =
|
|
1841
|
+
this->initAsBridge(_meshSSID, _meshPassword, routerSSID, routerPassword,
|
|
1842
|
+
mScheduler, _meshPort);
|
|
1825
1843
|
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1844
|
+
if (!bridgeInitSuccess) {
|
|
1845
|
+
Log(ERROR, "✗ Bridge promotion failed - router unreachable\n");
|
|
1846
|
+
Log(ERROR, "Reverting to regular node on channel %d\n", savedChannel);
|
|
1829
1847
|
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1848
|
+
// Re-initialize as regular node on the original channel
|
|
1849
|
+
this->init(_meshSSID, _meshPassword, mScheduler, _meshPort, WIFI_AP_STA,
|
|
1850
|
+
savedChannel, _meshHidden, MAX_CONN);
|
|
1833
1851
|
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1852
|
+
// Reset election state and clear candidates (consistent with normal
|
|
1853
|
+
// election completion)
|
|
1854
|
+
electionState = ELECTION_IDLE;
|
|
1855
|
+
electionCandidates.clear();
|
|
1838
1856
|
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1857
|
+
// Notify via callback
|
|
1858
|
+
if (bridgeRoleChangedCallback) {
|
|
1859
|
+
bridgeRoleChangedCallback(
|
|
1860
|
+
false, "Bridge promotion failed - router unreachable");
|
|
1861
|
+
}
|
|
1844
1862
|
|
|
1845
|
-
|
|
1846
|
-
|
|
1863
|
+
return;
|
|
1864
|
+
}
|
|
1847
1865
|
|
|
1848
|
-
|
|
1866
|
+
lastRoleChangeTime = millis();
|
|
1849
1867
|
|
|
1850
|
-
|
|
1868
|
+
Log(STARTUP, "✓ Bridge promotion complete on channel %d\n", _meshChannel);
|
|
1851
1869
|
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1870
|
+
// Notify via callback
|
|
1871
|
+
// Use explicit TSTRING construction to ensure string lifetime safety
|
|
1872
|
+
if (bridgeRoleChangedCallback) {
|
|
1873
|
+
static const TSTRING reason = "Election winner - best router signal";
|
|
1874
|
+
bridgeRoleChangedCallback(true, reason);
|
|
1875
|
+
}
|
|
1858
1876
|
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1877
|
+
// Note: The initial takeover announcement was already sent earlier
|
|
1878
|
+
// before the channel switch. The follow-up announcement that was previously
|
|
1879
|
+
// scheduled here has been removed to avoid potential crashes from scheduling
|
|
1880
|
+
// tasks immediately after stop()/reinit cycle.
|
|
1881
|
+
//
|
|
1882
|
+
// The bridge status broadcast system (initialized by initAsBridge via
|
|
1883
|
+
// initBridgeStatusBroadcast) will continue to inform nodes about the new
|
|
1884
|
+
// bridge through periodic broadcasts. Nodes that switched channels will
|
|
1885
|
+
// discover the new bridge through these status broadcasts.
|
|
1886
|
+
Log(STARTUP,
|
|
1887
|
+
"Bridge takeover complete. Status broadcasts will announce bridge to "
|
|
1888
|
+
"network.\n");
|
|
1889
|
+
});
|
|
1871
1890
|
}
|
|
1872
1891
|
|
|
1873
1892
|
/**
|
|
@@ -1883,6 +1902,11 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
1883
1902
|
* - Nodes that are the first to start and no mesh exists yet
|
|
1884
1903
|
* - Recovery scenarios where mesh network is unavailable
|
|
1885
1904
|
*
|
|
1905
|
+
* CRITICAL: This function is called from within a scheduled task (isolated
|
|
1906
|
+
* bridge retry task). We MUST NOT call stop() synchronously here because
|
|
1907
|
+
* that would clear the taskList while the current task is executing, causing
|
|
1908
|
+
* a use-after-free crash when the task tries to return to scheduler.
|
|
1909
|
+
*
|
|
1886
1910
|
* @return true if promotion was attempted (regardless of success), false if
|
|
1887
1911
|
* skipped
|
|
1888
1912
|
*/
|
|
@@ -1917,69 +1941,81 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
1917
1941
|
routerRSSI);
|
|
1918
1942
|
Log(CONNECTION,
|
|
1919
1943
|
"Attempting direct bridge promotion (bypassing election)\n");
|
|
1944
|
+
Log(CONNECTION,
|
|
1945
|
+
"Scheduling stop/reinit (async to avoid task corruption)\n");
|
|
1920
1946
|
|
|
1921
1947
|
// Save current mesh configuration
|
|
1922
1948
|
uint8_t savedChannel = _meshChannel;
|
|
1923
1949
|
|
|
1924
|
-
//
|
|
1925
|
-
|
|
1926
|
-
|
|
1950
|
+
// CRITICAL FIX: Schedule the stop/reinit work to run after current task completes
|
|
1951
|
+
// This prevents use-after-free crash when stop() clears taskList while
|
|
1952
|
+
// the retry task is still executing
|
|
1953
|
+
// Use minimal delay to allow current task to complete first
|
|
1954
|
+
this->addTask(ASYNC_PROMOTION_DELAY_MS, TASK_ONCE, [this, savedChannel]() {
|
|
1955
|
+
using namespace logger;
|
|
1956
|
+
|
|
1957
|
+
Log(CONNECTION, "Executing isolated bridge promotion (stop/reinit cycle)\n");
|
|
1958
|
+
|
|
1959
|
+
// Stop current mesh operations
|
|
1960
|
+
this->stop();
|
|
1961
|
+
delay(1000);
|
|
1927
1962
|
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1963
|
+
// Attempt to initialize as bridge
|
|
1964
|
+
bool bridgeInitSuccess =
|
|
1965
|
+
this->initAsBridge(_meshSSID, _meshPassword, routerSSID, routerPassword,
|
|
1966
|
+
mScheduler, _meshPort);
|
|
1932
1967
|
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1968
|
+
if (!bridgeInitSuccess) {
|
|
1969
|
+
Log(ERROR, "✗ Isolated bridge promotion failed - router unreachable\n");
|
|
1970
|
+
Log(ERROR, "Reverting to regular node on channel %d\n", savedChannel);
|
|
1936
1971
|
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1972
|
+
// Re-initialize as regular node on the original channel
|
|
1973
|
+
this->init(_meshSSID, _meshPassword, mScheduler, _meshPort, WIFI_AP_STA,
|
|
1974
|
+
savedChannel, _meshHidden, MAX_CONN);
|
|
1940
1975
|
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1976
|
+
// Re-configure router credentials for future retry attempts
|
|
1977
|
+
this->setRouterCredentials(routerSSID, routerPassword);
|
|
1978
|
+
this->enableBridgeFailover(true);
|
|
1944
1979
|
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1980
|
+
// Set flag to skip empty scan check on next retry attempt
|
|
1981
|
+
// since we already confirmed isolation before this failed attempt
|
|
1982
|
+
_isolatedRetryPending = true;
|
|
1948
1983
|
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1984
|
+
// Notify via callback
|
|
1985
|
+
if (bridgeRoleChangedCallback) {
|
|
1986
|
+
bridgeRoleChangedCallback(
|
|
1987
|
+
false, "Isolated bridge promotion failed - router unreachable");
|
|
1988
|
+
}
|
|
1954
1989
|
|
|
1955
|
-
|
|
1956
|
-
|
|
1990
|
+
return;
|
|
1991
|
+
}
|
|
1957
1992
|
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1993
|
+
// Success! Reset retry counter
|
|
1994
|
+
_isolatedBridgeRetryAttempts = 0;
|
|
1995
|
+
lastRoleChangeTime = millis();
|
|
1961
1996
|
|
|
1962
|
-
|
|
1963
|
-
|
|
1997
|
+
Log(STARTUP, "✓ Isolated bridge promotion complete on channel %d\n",
|
|
1998
|
+
_meshChannel);
|
|
1964
1999
|
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
2000
|
+
// Notify via callback
|
|
2001
|
+
// Use explicit TSTRING construction to ensure string lifetime safety
|
|
2002
|
+
if (bridgeRoleChangedCallback) {
|
|
2003
|
+
static const TSTRING reason = "Isolated node promoted to bridge";
|
|
2004
|
+
bridgeRoleChangedCallback(true, reason);
|
|
2005
|
+
}
|
|
1971
2006
|
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
2007
|
+
// Note: Bridge status announcement will be sent automatically by
|
|
2008
|
+
// initBridgeStatusBroadcast() which is called by initAsBridge().
|
|
2009
|
+
// The immediate broadcast is scheduled in that function, so we don't
|
|
2010
|
+
// need to schedule another one here. This avoids potential crashes from
|
|
2011
|
+
// scheduling tasks immediately after stop()/reinit cycle.
|
|
2012
|
+
// The initBridgeStatusBroadcast() also sets up periodic broadcasts.
|
|
2013
|
+
Log(STARTUP,
|
|
2014
|
+
"Bridge status announcement will be sent by bridge status broadcast "
|
|
2015
|
+
"system\n");
|
|
2016
|
+
});
|
|
1981
2017
|
|
|
1982
|
-
return true; // Count as an attempt - we
|
|
2018
|
+
return true; // Count as an attempt - we scheduled the promotion
|
|
1983
2019
|
}
|
|
1984
2020
|
|
|
1985
2021
|
/**
|
|
@@ -2411,6 +2447,8 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
2411
2447
|
300000; // Reset counter after 5 minutes
|
|
2412
2448
|
static const uint16_t ISOLATED_BRIDGE_RETRY_SCAN_THRESHOLD =
|
|
2413
2449
|
6; // Require 6 empty scans before retrying
|
|
2450
|
+
static const uint32_t ASYNC_PROMOTION_DELAY_MS =
|
|
2451
|
+
10; // Delay for async bridge promotion to allow current task to complete
|
|
2414
2452
|
|
|
2415
2453
|
// Multi-bridge coordination state and configuration
|
|
2416
2454
|
protected:
|
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.16
|
|
9
|
+
* @date 2025-12-20
|
|
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,9 +26,10 @@ 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
|
-
// Increased from 250ms to 500ms to support ESP32-C6
|
|
30
|
-
//
|
|
31
|
-
|
|
29
|
+
// Increased from 250ms to 500ms (v1.9.14) then to 1000ms (v1.9.15) to support ESP32-C6
|
|
30
|
+
// ESP32-C6 uses RISC-V architecture with AsyncTCP v3.3.0+ which requires significantly
|
|
31
|
+
// more time for internal cleanup operations compared to ESP32/ESP8266
|
|
32
|
+
static const uint32_t TCP_CLIENT_DELETION_SPACING_MS = 1000; // 1000ms spacing between deletions
|
|
32
33
|
|
|
33
34
|
// Global state to track AsyncClient deletion scheduling and execution
|
|
34
35
|
// This ensures deletions are spaced out even when multiple deletion requests arrive simultaneously
|