@alteriom/painlessmesh 2.1.0 → 2.1.1
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 +67 -0
- package/CONTRIBUTING.md +8 -3
- package/examples/alteriom/platformio.ini +4 -1
- package/examples/basic/platformio.ini +4 -1
- package/keywords.txt +1 -0
- 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 +69 -19
- package/src/boost/asynctcp.hpp +1 -1
- package/src/painlessMeshSTA.h +5 -4
- package/src/painlessmesh/buffer.hpp +11 -11
- package/src/painlessmesh/gateway.hpp +7 -2
- package/src/painlessmesh/logger.hpp +1 -1
- package/src/painlessmesh/mesh.hpp +34 -19
- package/src/painlessmesh/message_queue.hpp +6 -6
- package/src/painlessmesh/metrics.hpp +1 -1
- package/src/painlessmesh/ota.hpp +1 -1
- package/src/painlessmesh/plugin.hpp +10 -7
- package/src/painlessmesh/protocol.hpp +10 -6
- package/src/painlessmesh/rtc.hpp +3 -3
- package/src/plugin/performance.hpp +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,73 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.1.1] - 2026-09-21
|
|
11
|
+
|
|
12
|
+
Two crashes the rig and a user found in 2.1.0, and the rig itself wired
|
|
13
|
+
in. A node could crash at start-up on an uninitialised listener pointer
|
|
14
|
+
(#466, reported by a user before the rig saw it), and an ESP8266 serving as
|
|
15
|
+
a shared gateway could run out of memory answering its own
|
|
16
|
+
`sendToInternet()` (#469, found by the rig the day the fix for #466 landed).
|
|
17
|
+
Both are fixed, CI now refuses the class of defect behind #466, and every
|
|
18
|
+
merge to `main` is flashed onto the rig from here on. **Upgrade if you run
|
|
19
|
+
2.1.0**: the #466 crash is at boot, on every node.
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- **Every merge to `main` runs on the hardware rig.**
|
|
24
|
+
`.github/workflows/farm-hil.yml` dispatches the Alteriom farm's HIL suite
|
|
25
|
+
once the CI/CD Pipeline has passed on `main`, and for a pull request a
|
|
26
|
+
maintainer labels `run-hil` -- a promise CONTRIBUTING.md had made that
|
|
27
|
+
nothing kept. The rig had been a manual gate, so #466 reached a user
|
|
28
|
+
before it reached a board. Needs the `FARM_DISPATCH_TOKEN` secret; without
|
|
29
|
+
it the job says so and passes. Release builds stay a person's decision on
|
|
30
|
+
the farm side.
|
|
31
|
+
|
|
32
|
+
- **`mesh.tcpListening()`** -- whether this node's TCP listener exists and
|
|
33
|
+
is in LISTEN, the state peers depend on and nothing else reported. A node
|
|
34
|
+
whose listener was never created (#466) or was re-created not listening
|
|
35
|
+
(#435) is reachable only through connections it made outbound, and nothing
|
|
36
|
+
can join through it; a health report can now say so on the node itself.
|
|
37
|
+
`PAINLESSMESH_HAS_TCP_LISTENING` is defined alongside it for code that
|
|
38
|
+
builds against older releases too.
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- **An ESP8266 shared gateway could run out of memory answering its own
|
|
43
|
+
`sendToInternet()`** (#469, found by the rig: both ESP8266 boards died of
|
|
44
|
+
`Unhandled C++ exception: OOM` in the `gateway.shared.internet` row, on a
|
|
45
|
+
76-byte allocation). For a request that originated on the gateway itself,
|
|
46
|
+
the reply travelled to the application as five copies of the ack --
|
|
47
|
+
copied into the deferred task's closure, copied again inside it,
|
|
48
|
+
serialised to JSON by the `Variant` and parsed back into a third package
|
|
49
|
+
by the handler -- then the pending request and the result were copied
|
|
50
|
+
once more on delivery, all on a heap the HTTP client had already spent.
|
|
51
|
+
The ack is now moved into one heap block and handed straight to the
|
|
52
|
+
handler, and the request and result are moved on delivery. What the
|
|
53
|
+
application receives is unchanged.
|
|
54
|
+
- **A node could crash before serving its first connection: `_tcpListener`
|
|
55
|
+
was never initialised** (#466). 2.1.0 taught `tcpServerInit()` to keep a
|
|
56
|
+
listener that already exists instead of re-binding the port, so it now
|
|
57
|
+
reads the pointer before anything has assigned it. On a stack that held
|
|
58
|
+
garbage there the null check passed and the `delete` that followed
|
|
59
|
+
crashed the node at start-up. The pointer defaults to `nullptr`.
|
|
60
|
+
- **The same gap closed everywhere it existed.** Twenty more pointer and
|
|
61
|
+
arithmetic members had no default and no constructor setting them: the
|
|
62
|
+
AP settings a node reports before `init()`; `from`/`dest` on a package
|
|
63
|
+
built by its type constructor and sent before the application filled
|
|
64
|
+
them; the fields of `SendStats`, `ConnectionInfo`, `BridgeCandidate`,
|
|
65
|
+
`MeshChannelCandidate` and `BridgeCoordinationState`; and the
|
|
66
|
+
book-keeping members of `RTCManager`, `MessageQueue`, `LogClass`,
|
|
67
|
+
`metrics::Timer` and the desktop `AsyncServer`. Each now has a default
|
|
68
|
+
member initializer, which holds under every constructor the class has or
|
|
69
|
+
gains later.
|
|
70
|
+
- **CI refuses the next one.** `test/ci/check_member_init.py` walks every
|
|
71
|
+
header under `src/` and fails on any pointer or arithmetic data member
|
|
72
|
+
declared without a default member initializer, whatever the class's
|
|
73
|
+
constructors do; `test/ci/test_check_member_init.sh` plants the #466
|
|
74
|
+
case and proves the checker catches it and passes clean code. Both run
|
|
75
|
+
in the Code Quality job.
|
|
76
|
+
|
|
10
77
|
## [2.1.0] - 2026-09-15
|
|
11
78
|
|
|
12
79
|
`sendToInternet()` you can build a notifier on (#463). One user's WhatsApp
|
package/CONTRIBUTING.md
CHANGED
|
@@ -94,9 +94,14 @@ Multi-node behaviour is tested with the external
|
|
|
94
94
|
[painlessMesh-simulator](https://github.com/Alteriom/painlessMesh-simulator);
|
|
95
95
|
scenarios for an example live under `examples/<example>/test/simulator/`
|
|
96
96
|
(see `examples/basic/test/simulator/`). Radio, routing, gateway, failover
|
|
97
|
-
and OTA behaviour is validated on the Alteriom hardware-in-the-loop farm
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
and OTA behaviour is validated on the Alteriom hardware-in-the-loop farm.
|
|
98
|
+
`.github/workflows/farm-hil.yml` sends the farm a run for **every merge to
|
|
99
|
+
`main` whose CI passed**, and for a pull request when a maintainer adds the
|
|
100
|
+
`run-hil` label; either needs the `FARM_DISPATCH_TOKEN` secret, and without
|
|
101
|
+
it the job says so and passes rather than pretending. A rig run is never a
|
|
102
|
+
release build from here -- one that may spend a real provider message is
|
|
103
|
+
started by a person from the farm's own workflow -- and the release gate is
|
|
104
|
+
three consecutive clean runs of the whole suite.
|
|
100
105
|
|
|
101
106
|
### The HTTP test point
|
|
102
107
|
|
|
@@ -17,7 +17,10 @@ lib_deps =
|
|
|
17
17
|
esp32async/ESPAsyncTCP@^2.0.0 ; Only for ESP8266
|
|
18
18
|
|
|
19
19
|
[env:esp32]
|
|
20
|
-
|
|
20
|
+
; Pinned to the ESP32 Arduino 2.x core, which compiles as gnu++11: a struct
|
|
21
|
+
; with default member initializers is not an aggregate there, and this is the
|
|
22
|
+
; one CI build that proves the library still builds on it (#466 review).
|
|
23
|
+
platform = espressif32@7.1.3
|
|
21
24
|
board = esp32dev
|
|
22
25
|
framework = arduino
|
|
23
26
|
lib_extra_dirs = ../../ ; Load the local copy of painlessmesh. For your own example add painlessmesh to the lib_deps
|
|
@@ -17,7 +17,10 @@ lib_deps =
|
|
|
17
17
|
esp32async/ESPAsyncTCP@^2.0.0 ; Only for ESP8266
|
|
18
18
|
|
|
19
19
|
[env:esp32]
|
|
20
|
-
|
|
20
|
+
; Pinned to the ESP32 Arduino 2.x core, which compiles as gnu++11: a struct
|
|
21
|
+
; with default member initializers is not an aggregate there, and this is the
|
|
22
|
+
; one CI build that proves the library still builds on it (#466 review).
|
|
23
|
+
platform = espressif32@7.1.3
|
|
21
24
|
board = esp32dev
|
|
22
25
|
framework = arduino
|
|
23
26
|
lib_extra_dirs = ../../
|
package/keywords.txt
CHANGED
package/library.json
CHANGED
package/library.properties
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
name=Alteriom PainlessMesh
|
|
2
|
-
version=2.1.
|
|
2
|
+
version=2.1.1
|
|
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": "2.1.
|
|
3
|
+
"version": "2.1.1",
|
|
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 "2.1.
|
|
32
|
+
#define ALTERIOM_PAINLESS_MESH_VERSION "2.1.1"
|
|
33
33
|
#define ALTERIOM_PAINLESS_MESH_VERSION_MAJOR 2
|
|
34
34
|
#define ALTERIOM_PAINLESS_MESH_VERSION_MINOR 1
|
|
35
|
-
#define ALTERIOM_PAINLESS_MESH_VERSION_PATCH
|
|
35
|
+
#define ALTERIOM_PAINLESS_MESH_VERSION_PATCH 1
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* @brief Library description and usage information
|
package/src/arduino/wifi.hpp
CHANGED
|
@@ -23,6 +23,14 @@
|
|
|
23
23
|
|
|
24
24
|
extern painlessmesh::logger::LogClass Log;
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Defined when Mesh::tcpListening() exists, so a node can be asked whether
|
|
28
|
+
* its TCP listener is in LISTEN rather than having that inferred from a
|
|
29
|
+
* peer eventually connecting. Code that must build against older releases
|
|
30
|
+
* too can test for it with #ifdef.
|
|
31
|
+
*/
|
|
32
|
+
#define PAINLESSMESH_HAS_TCP_LISTENING 1
|
|
33
|
+
|
|
26
34
|
namespace painlessmesh {
|
|
27
35
|
namespace wifi {
|
|
28
36
|
class Mesh : public painlessmesh::Mesh<Connection> {
|
|
@@ -883,7 +891,7 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
883
891
|
// then had a bridge's listener. AsyncTCP keeps its pcb private, so
|
|
884
892
|
// SO_REUSEADDR cannot be set from here; not re-binding is the fix.
|
|
885
893
|
if (_tcpListener != nullptr) {
|
|
886
|
-
if (_tcpListener->status() ==
|
|
894
|
+
if (_tcpListener->status() == TCP_STATE_LISTEN) {
|
|
887
895
|
Log(CONNECTION,
|
|
888
896
|
"tcpServerInit(): listener on port %d already listening, kept\n",
|
|
889
897
|
_meshPort);
|
|
@@ -905,6 +913,28 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
905
913
|
return;
|
|
906
914
|
}
|
|
907
915
|
|
|
916
|
+
/**
|
|
917
|
+
* Whether this node's TCP listener exists and is in LISTEN.
|
|
918
|
+
*
|
|
919
|
+
* The state peers depend on and nothing else reports: a node whose
|
|
920
|
+
* listener was never created (#466 crashed before it could be) or was
|
|
921
|
+
* re-created not listening (#435's promoted bridge served nothing for two
|
|
922
|
+
* minutes) is reachable only through connections it made outbound, and
|
|
923
|
+
* nothing can join through it. Cheap enough to put in a health report.
|
|
924
|
+
*/
|
|
925
|
+
bool tcpListening() {
|
|
926
|
+
return _tcpListener != nullptr && _tcpListener->status() == TCP_STATE_LISTEN;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
/**
|
|
930
|
+
* lwIP's `LISTEN`, which AsyncServer::status() returns as the pcb's state
|
|
931
|
+
* on both cores (ESPAsyncTCP and ESP32Async/AsyncTCP alike). Named rather
|
|
932
|
+
* than written as 1 because status() also answers 0 -- lwIP's CLOSED --
|
|
933
|
+
* when the server holds no pcb at all, so the literal read as if it
|
|
934
|
+
* conflated the two. It does not: a listener is listening, or it is not.
|
|
935
|
+
*/
|
|
936
|
+
static constexpr uint8_t TCP_STATE_LISTEN = 1;
|
|
937
|
+
|
|
908
938
|
/**
|
|
909
939
|
* Establish TCP connection to mesh network
|
|
910
940
|
*
|
|
@@ -1521,12 +1551,15 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
1521
1551
|
|
|
1522
1552
|
protected:
|
|
1523
1553
|
friend class ::StationScan;
|
|
1554
|
+
// init() sets all of these; the defaults are init()'s own, so a node
|
|
1555
|
+
// that is asked about its AP before init() answers something true
|
|
1556
|
+
// rather than whatever the stack held.
|
|
1524
1557
|
TSTRING _meshSSID;
|
|
1525
1558
|
TSTRING _meshPassword;
|
|
1526
|
-
uint8_t _meshChannel;
|
|
1527
|
-
uint8_t _meshHidden;
|
|
1528
|
-
uint8_t _meshMaxConn;
|
|
1529
|
-
uint16_t _meshPort;
|
|
1559
|
+
uint8_t _meshChannel = 1;
|
|
1560
|
+
uint8_t _meshHidden = 0;
|
|
1561
|
+
uint8_t _meshMaxConn = MAX_CONN;
|
|
1562
|
+
uint16_t _meshPort = 5555;
|
|
1530
1563
|
|
|
1531
1564
|
IPAddress _apIp;
|
|
1532
1565
|
StationScan stationScan;
|
|
@@ -2467,7 +2500,7 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
2467
2500
|
// stop/re-init, was not listening, and nothing looked. This task runs
|
|
2468
2501
|
// every thirty seconds on a bridge: if the listener is not in LISTEN
|
|
2469
2502
|
// (1 on both cores) it is re-created, and the log says so.
|
|
2470
|
-
if (_tcpListener != nullptr && _tcpListener->status() !=
|
|
2503
|
+
if (_tcpListener != nullptr && _tcpListener->status() != TCP_STATE_LISTEN) {
|
|
2471
2504
|
Log(ERROR,
|
|
2472
2505
|
"sendBridgeStatus(): TCP listener on port %d is in state %u, not "
|
|
2473
2506
|
"LISTEN; re-creating it\n",
|
|
@@ -3236,16 +3269,25 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
3236
3269
|
}
|
|
3237
3270
|
|
|
3238
3271
|
#ifdef ESP32
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3272
|
+
// Event ids the core hands back from onEvent(); stop() removes all four.
|
|
3273
|
+
// 0 is never issued (the core's ids start at 1 and 0 means "no
|
|
3274
|
+
// callback"), so removeEvent(0) is a no-op on a node stopped before
|
|
3275
|
+
// init() rather than a walk of the core's callback list for garbage.
|
|
3276
|
+
WiFiEventId_t eventScanDoneHandler = 0;
|
|
3277
|
+
WiFiEventId_t eventSTAStartHandler = 0;
|
|
3278
|
+
WiFiEventId_t eventSTADisconnectedHandler = 0;
|
|
3279
|
+
WiFiEventId_t eventSTAGotIPHandler = 0;
|
|
3243
3280
|
#elif defined(ESP8266)
|
|
3244
3281
|
WiFiEventHandler eventSTAConnectedHandler;
|
|
3245
3282
|
WiFiEventHandler eventSTADisconnectedHandler;
|
|
3246
3283
|
WiFiEventHandler eventSTAGotIPHandler;
|
|
3247
3284
|
#endif // ESP8266
|
|
3248
|
-
|
|
3285
|
+
// Null until tcpServerInit() creates it. Every reader checks for null
|
|
3286
|
+
// first, and one of them (tcpServerInit() itself, since 2.1.0) runs before
|
|
3287
|
+
// anything has assigned it -- on an uninitialised pointer that check
|
|
3288
|
+
// passed on garbage and the delete that followed crashed the node before
|
|
3289
|
+
// it served a single connection (#466).
|
|
3290
|
+
AsyncServer* _tcpListener = nullptr;
|
|
3249
3291
|
std::shared_ptr<Task> bridgeStatusTask;
|
|
3250
3292
|
// millis() of the last status broadcast, periodic or brought forward by a
|
|
3251
3293
|
// topology change; the latter is held to one every five seconds.
|
|
@@ -3258,11 +3300,11 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
3258
3300
|
enum ElectionState { ELECTION_IDLE, ELECTION_SCANNING, ELECTION_COLLECTING };
|
|
3259
3301
|
|
|
3260
3302
|
struct BridgeCandidate {
|
|
3261
|
-
uint32_t nodeId;
|
|
3262
|
-
int8_t routerRSSI;
|
|
3263
|
-
uint8_t routerChannel;
|
|
3264
|
-
uint32_t uptime;
|
|
3265
|
-
uint32_t freeMemory;
|
|
3303
|
+
uint32_t nodeId = 0;
|
|
3304
|
+
int8_t routerRSSI = 0;
|
|
3305
|
+
uint8_t routerChannel = 0;
|
|
3306
|
+
uint32_t uptime = 0;
|
|
3307
|
+
uint32_t freeMemory = 0;
|
|
3266
3308
|
};
|
|
3267
3309
|
|
|
3268
3310
|
bool bridgeFailoverEnabled = true;
|
|
@@ -3317,11 +3359,19 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
3317
3359
|
size_t lastSelectedBridgeIndex = 0; // For round-robin selection
|
|
3318
3360
|
|
|
3319
3361
|
// Bridge coordination monitoring callbacks and state
|
|
3362
|
+
// Constructed by name, not as an aggregate: with default member
|
|
3363
|
+
// initializers this is not an aggregate under gnu++11, which the ESP32
|
|
3364
|
+
// Arduino 2.x core still builds with, and `= {priority, role, load,
|
|
3365
|
+
// millis()}` below needs a constructor to land on there.
|
|
3320
3366
|
struct BridgeCoordinationState {
|
|
3321
|
-
uint8_t priority;
|
|
3367
|
+
uint8_t priority = 0;
|
|
3322
3368
|
TSTRING role;
|
|
3323
|
-
uint8_t load;
|
|
3324
|
-
uint32_t lastSeen;
|
|
3369
|
+
uint8_t load = 0;
|
|
3370
|
+
uint32_t lastSeen = 0;
|
|
3371
|
+
BridgeCoordinationState() {}
|
|
3372
|
+
BridgeCoordinationState(uint8_t priority_, const TSTRING& role_,
|
|
3373
|
+
uint8_t load_, uint32_t lastSeen_)
|
|
3374
|
+
: priority(priority_), role(role_), load(load_), lastSeen(lastSeen_) {}
|
|
3325
3375
|
};
|
|
3326
3376
|
std::map<uint32_t, BridgeCoordinationState> lastBridgeCoordinationState;
|
|
3327
3377
|
std::function<void(const plugin::BridgeCoordinationPackage&, uint32_t)> bridgeCoordinationCallback;
|
package/src/boost/asynctcp.hpp
CHANGED
package/src/painlessMeshSTA.h
CHANGED
|
@@ -125,10 +125,11 @@ class StationScan {
|
|
|
125
125
|
protected:
|
|
126
126
|
TSTRING ssid;
|
|
127
127
|
TSTRING password;
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
128
|
+
// init() sets these; until then the scan belongs to no mesh and no port.
|
|
129
|
+
painlessMesh *mesh = nullptr;
|
|
130
|
+
uint16_t port = 0;
|
|
131
|
+
uint8_t channel = 0;
|
|
132
|
+
bool hidden = false;
|
|
132
133
|
std::list<WiFi_AP_Record_t> aps;
|
|
133
134
|
|
|
134
135
|
void requestIP(WiFi_AP_Record_t &ap);
|
|
@@ -124,7 +124,7 @@ inline void ReceiveBuffer<std::string>::stringAppend(std::string &buffer,
|
|
|
124
124
|
template <class T>
|
|
125
125
|
struct PrioritizedMessage {
|
|
126
126
|
T message;
|
|
127
|
-
uint8_t priority; // 0=CRITICAL, 1=HIGH, 2=NORMAL, 3=LOW
|
|
127
|
+
uint8_t priority = 2; // 0=CRITICAL, 1=HIGH, 2=NORMAL, 3=LOW
|
|
128
128
|
|
|
129
129
|
PrioritizedMessage(const T& msg, uint8_t prio = 2) : message(msg), priority(prio) {}
|
|
130
130
|
};
|
|
@@ -318,18 +318,18 @@ class SentBuffer {
|
|
|
318
318
|
* Get statistics about queued and sent messages
|
|
319
319
|
*/
|
|
320
320
|
struct SendStats {
|
|
321
|
-
uint32_t totalQueued;
|
|
322
|
-
uint32_t criticalQueued;
|
|
323
|
-
uint32_t highQueued;
|
|
324
|
-
uint32_t normalQueued;
|
|
325
|
-
uint32_t lowQueued;
|
|
326
|
-
uint32_t criticalSent;
|
|
327
|
-
uint32_t highSent;
|
|
328
|
-
uint32_t normalSent;
|
|
329
|
-
uint32_t lowSent;
|
|
321
|
+
uint32_t totalQueued = 0;
|
|
322
|
+
uint32_t criticalQueued = 0;
|
|
323
|
+
uint32_t highQueued = 0;
|
|
324
|
+
uint32_t normalQueued = 0;
|
|
325
|
+
uint32_t lowQueued = 0;
|
|
326
|
+
uint32_t criticalSent = 0;
|
|
327
|
+
uint32_t highSent = 0;
|
|
328
|
+
uint32_t normalSent = 0;
|
|
329
|
+
uint32_t lowSent = 0;
|
|
330
330
|
// Messages lost to the PAINLESSMESH_MAX_SENT_BUFFER_MESSAGES cap:
|
|
331
331
|
// evicted from the buffer or rejected on push (issue #388)
|
|
332
|
-
uint32_t dropped;
|
|
332
|
+
uint32_t dropped = 0;
|
|
333
333
|
};
|
|
334
334
|
|
|
335
335
|
SendStats getStats() const {
|
|
@@ -67,8 +67,13 @@ constexpr bool shouldFollowBridgeChannel(uint32_t localNodeId,
|
|
|
67
67
|
* @brief One occurrence of the mesh SSID seen by an all-channel scan.
|
|
68
68
|
*/
|
|
69
69
|
struct MeshChannelCandidate {
|
|
70
|
-
uint8_t channel;
|
|
71
|
-
int32_t rssi;
|
|
70
|
+
uint8_t channel = 0;
|
|
71
|
+
int32_t rssi = 0;
|
|
72
|
+
// `push_back({channel, rssi})` needs a constructor under gnu++11 (the
|
|
73
|
+
// ESP32 Arduino 2.x core), where a struct with default member
|
|
74
|
+
// initializers is not an aggregate.
|
|
75
|
+
MeshChannelCandidate() {}
|
|
76
|
+
MeshChannelCandidate(uint8_t channel_, int32_t rssi_) : channel(channel_), rssi(rssi_) {}
|
|
72
77
|
};
|
|
73
78
|
|
|
74
79
|
/**
|
|
@@ -2162,11 +2162,21 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
2162
2162
|
// application's callback runs on what is left: on the HIL rig an
|
|
2163
2163
|
// ESP8266 shared gateway with ~11 KB free delivered its own result
|
|
2164
2164
|
// into a callback that could no longer allocate the event it built.
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2165
|
+
//
|
|
2166
|
+
// The ack is moved into one heap block and handed to the handler as
|
|
2167
|
+
// it is. It used to be copied into the task's closure, copied again
|
|
2168
|
+
// inside it, serialised into a JSON document by the Variant and
|
|
2169
|
+
// parsed back into a third package by the handler -- the reply
|
|
2170
|
+
// excerpt and the error text five times over on the heap the
|
|
2171
|
+
// handler had already spent -- and on the rig the closure's own
|
|
2172
|
+
// 76-byte allocation was the one that failed (#469). Nothing but
|
|
2173
|
+
// handleGatewayAck() listens for GATEWAY_ACK, and a request that
|
|
2174
|
+
// originated here exists only if enableSendToInternet() registered
|
|
2175
|
+
// it, so the round trip through the callback list bought nothing.
|
|
2176
|
+
// A shared_ptr rather than a C++14 init-capture: the ESP32 Arduino
|
|
2177
|
+
// 2.x core still builds as gnu++11.
|
|
2178
|
+
auto held = std::make_shared<gateway::GatewayAckPackage>(std::move(ack));
|
|
2179
|
+
this->addTask([this, held]() { this->handleGatewayAck(*held); });
|
|
2170
2180
|
Log(COMMUNICATION,
|
|
2171
2181
|
"Completing local GATEWAY_ACK from the scheduler (success=%d, http=%d)\n",
|
|
2172
2182
|
success, httpStatus);
|
|
@@ -2214,9 +2224,13 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
2214
2224
|
|
|
2215
2225
|
// Check if this is a success response
|
|
2216
2226
|
if (ack.success) {
|
|
2217
|
-
|
|
2227
|
+
// Taken out of the map, not copied out of it, and the result handed
|
|
2228
|
+
// on rather than copied: on an ESP8266 shared gateway every extra
|
|
2229
|
+
// copy of the destination, the payload and the reply excerpt lands
|
|
2230
|
+
// on a heap the request itself has nearly spent (#469).
|
|
2231
|
+
PendingInternetRequest done = std::move(request);
|
|
2218
2232
|
pendingInternetRequests.erase(it);
|
|
2219
|
-
deliverInternetResult(done, result);
|
|
2233
|
+
deliverInternetResult(done, std::move(result));
|
|
2220
2234
|
return;
|
|
2221
2235
|
}
|
|
2222
2236
|
|
|
@@ -2305,9 +2319,9 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
2305
2319
|
Log(COMMUNICATION, "handleGatewayAck(): Final failure for msgId=%u (HTTP %u)\n",
|
|
2306
2320
|
ack.messageId, ack.httpStatus);
|
|
2307
2321
|
}
|
|
2308
|
-
|
|
2322
|
+
PendingInternetRequest done = std::move(request);
|
|
2309
2323
|
pendingInternetRequests.erase(it);
|
|
2310
|
-
deliverInternetResult(done, result);
|
|
2324
|
+
deliverInternetResult(done, std::move(result));
|
|
2311
2325
|
}
|
|
2312
2326
|
}
|
|
2313
2327
|
|
|
@@ -3092,15 +3106,15 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
3092
3106
|
* Structure to hold detailed connection information
|
|
3093
3107
|
*/
|
|
3094
3108
|
struct ConnectionInfo {
|
|
3095
|
-
uint32_t nodeId; // Connected node ID
|
|
3096
|
-
uint32_t lastSeen; // Timestamp of last message (ms)
|
|
3097
|
-
int rssi; // Signal strength (dBm)
|
|
3098
|
-
int avgLatency; // Average round-trip time (ms)
|
|
3099
|
-
int hopCount; // Hops from current node
|
|
3100
|
-
int quality; // Connection quality (0-100)
|
|
3101
|
-
uint32_t messagesRx; // Messages received
|
|
3102
|
-
uint32_t messagesTx; // Messages sent
|
|
3103
|
-
uint32_t messagesDropped; // Failed transmissions
|
|
3109
|
+
uint32_t nodeId = 0; // Connected node ID
|
|
3110
|
+
uint32_t lastSeen = 0; // Timestamp of last message (ms)
|
|
3111
|
+
int rssi = 0; // Signal strength (dBm)
|
|
3112
|
+
int avgLatency = 0; // Average round-trip time (ms)
|
|
3113
|
+
int hopCount = 0; // Hops from current node
|
|
3114
|
+
int quality = 0; // Connection quality (0-100)
|
|
3115
|
+
uint32_t messagesRx = 0; // Messages received
|
|
3116
|
+
uint32_t messagesTx = 0; // Messages sent
|
|
3117
|
+
uint32_t messagesDropped = 0; // Failed transmissions
|
|
3104
3118
|
};
|
|
3105
3119
|
|
|
3106
3120
|
/**
|
|
@@ -4098,7 +4112,8 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
4098
4112
|
/// Is the node a root node
|
|
4099
4113
|
bool shouldContainRoot = false;
|
|
4100
4114
|
|
|
4101
|
-
|
|
4115
|
+
// init() either creates one or takes the caller's; null until then.
|
|
4116
|
+
Scheduler *mScheduler = nullptr;
|
|
4102
4117
|
|
|
4103
4118
|
public: // Windows MSVC: lambdas in friend functions need public access
|
|
4104
4119
|
|
|
@@ -37,10 +37,10 @@ enum QueueState {
|
|
|
37
37
|
* Contains all metadata needed for reliable message delivery
|
|
38
38
|
*/
|
|
39
39
|
struct QueuedMessage {
|
|
40
|
-
uint32_t id;
|
|
41
|
-
MessagePriority priority; // Message priority level
|
|
42
|
-
uint32_t timestamp;
|
|
43
|
-
uint32_t attempts;
|
|
40
|
+
uint32_t id = 0; // Unique message ID
|
|
41
|
+
MessagePriority priority = PRIORITY_NORMAL; // Message priority level
|
|
42
|
+
uint32_t timestamp = 0; // When message was queued (millis)
|
|
43
|
+
uint32_t attempts = 0; // Number of send attempts
|
|
44
44
|
TSTRING payload; // Message content
|
|
45
45
|
TSTRING destination; // Cloud endpoint/topic (optional metadata)
|
|
46
46
|
|
|
@@ -298,8 +298,8 @@ public:
|
|
|
298
298
|
|
|
299
299
|
private:
|
|
300
300
|
std::vector<QueuedMessage> messages;
|
|
301
|
-
uint32_t maxQueueSize;
|
|
302
|
-
uint32_t nextMessageId;
|
|
301
|
+
uint32_t maxQueueSize = 1000;
|
|
302
|
+
uint32_t nextMessageId = 1;
|
|
303
303
|
QueueStats stats;
|
|
304
304
|
QueueState currentState = QUEUE_EMPTY;
|
|
305
305
|
queueStateChangedCallback_t stateChangedCallback;
|
package/src/painlessmesh/ota.hpp
CHANGED
|
@@ -39,10 +39,13 @@ namespace plugin {
|
|
|
39
39
|
|
|
40
40
|
class SinglePackage : public protocol::PackageInterface {
|
|
41
41
|
public:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
// `from` and `dest` are the application's to fill; a package built by
|
|
43
|
+
// the type constructor and sent before they are set addresses node 0,
|
|
44
|
+
// not whatever the stack held.
|
|
45
|
+
uint32_t from = 0;
|
|
46
|
+
uint32_t dest = 0;
|
|
47
|
+
router::Type routing = router::SINGLE;
|
|
48
|
+
int type = 0;
|
|
46
49
|
int noJsonFields = 4;
|
|
47
50
|
|
|
48
51
|
SinglePackage(int type) : routing(router::SINGLE), type(type) {}
|
|
@@ -65,9 +68,9 @@ class SinglePackage : public protocol::PackageInterface {
|
|
|
65
68
|
|
|
66
69
|
class BroadcastPackage : public protocol::PackageInterface {
|
|
67
70
|
public:
|
|
68
|
-
uint32_t from;
|
|
69
|
-
router::Type routing;
|
|
70
|
-
int type;
|
|
71
|
+
uint32_t from = 0;
|
|
72
|
+
router::Type routing = router::BROADCAST;
|
|
73
|
+
int type = 0;
|
|
71
74
|
int noJsonFields = 3;
|
|
72
75
|
|
|
73
76
|
BroadcastPackage(int type) : routing(router::BROADCAST), type(type) {}
|
|
@@ -75,6 +75,10 @@ constexpr uint8_t PRIORITY_LOW = 3;
|
|
|
75
75
|
|
|
76
76
|
class PackageInterface {
|
|
77
77
|
public:
|
|
78
|
+
// A base with virtual functions destroyed through a derived pointer needs
|
|
79
|
+
// this, and a package held in a shared_ptr is exactly that case (#469);
|
|
80
|
+
// clang refuses the build without it under -Werror.
|
|
81
|
+
virtual ~PackageInterface() = default;
|
|
78
82
|
virtual JsonObject addTo(JsonObject&& jsonObj) const = 0;
|
|
79
83
|
#if ARDUINOJSON_VERSION_MAJOR < 7
|
|
80
84
|
virtual size_t jsonObjectSize() const = 0;
|
|
@@ -89,8 +93,8 @@ class PackageInterface {
|
|
|
89
93
|
class Single : public PackageInterface {
|
|
90
94
|
public:
|
|
91
95
|
int type = SINGLE;
|
|
92
|
-
uint32_t from;
|
|
93
|
-
uint32_t dest;
|
|
96
|
+
uint32_t from = 0;
|
|
97
|
+
uint32_t dest = 0;
|
|
94
98
|
TSTRING msg = "";
|
|
95
99
|
// Non-zero when the sender requested a delivery acknowledgment. Only
|
|
96
100
|
// serialized when set, so plain sends carry zero overhead.
|
|
@@ -273,8 +277,8 @@ class NodeTree : public PackageInterface {
|
|
|
273
277
|
class NodeSyncRequest : public NodeTree {
|
|
274
278
|
public:
|
|
275
279
|
int type = NODE_SYNC_REQUEST;
|
|
276
|
-
uint32_t from;
|
|
277
|
-
uint32_t dest;
|
|
280
|
+
uint32_t from = 0;
|
|
281
|
+
uint32_t dest = 0;
|
|
278
282
|
|
|
279
283
|
NodeSyncRequest() {}
|
|
280
284
|
NodeSyncRequest(uint32_t fromID, uint32_t destID, std::list<NodeTree> subTree,
|
|
@@ -351,8 +355,8 @@ struct time_sync_msg_t {
|
|
|
351
355
|
class TimeSync : public PackageInterface {
|
|
352
356
|
public:
|
|
353
357
|
int type = TIME_SYNC;
|
|
354
|
-
uint32_t dest;
|
|
355
|
-
uint32_t from;
|
|
358
|
+
uint32_t dest = 0;
|
|
359
|
+
uint32_t from = 0;
|
|
356
360
|
time_sync_msg_t msg;
|
|
357
361
|
|
|
358
362
|
TimeSync() {}
|
package/src/painlessmesh/rtc.hpp
CHANGED
|
@@ -192,9 +192,9 @@ class RTCManager {
|
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
private:
|
|
195
|
-
RTCInterface* rtcInterface;
|
|
196
|
-
bool rtcEnabled;
|
|
197
|
-
uint32_t lastSyncTime;
|
|
195
|
+
RTCInterface* rtcInterface = nullptr;
|
|
196
|
+
bool rtcEnabled = false;
|
|
197
|
+
uint32_t lastSyncTime = 0;
|
|
198
198
|
};
|
|
199
199
|
|
|
200
200
|
} // namespace rtc
|
|
@@ -53,10 +53,10 @@ class Stats {
|
|
|
53
53
|
|
|
54
54
|
class PerformancePackage : public plugin::BroadcastPackage {
|
|
55
55
|
public:
|
|
56
|
-
int id = 0;
|
|
57
|
-
int time; // Get an idea of the delay, by comparing it with nodetime
|
|
58
|
-
int stability; // stability of the sending node
|
|
59
|
-
int freeMemory; // memory of the sending node
|
|
56
|
+
int id = 0; // Can see if we missed values
|
|
57
|
+
int time = 0; // Get an idea of the delay, by comparing it with nodetime
|
|
58
|
+
int stability = 0; // stability of the sending node
|
|
59
|
+
int freeMemory = 0; // memory of the sending node
|
|
60
60
|
#ifdef ESP32
|
|
61
61
|
TSTRING hardware = "ESP32";
|
|
62
62
|
#else
|
|
@@ -93,7 +93,7 @@ class PerformancePackage : public plugin::BroadcastPackage {
|
|
|
93
93
|
/// Numbers to track for each node we receive PerformancePackages from
|
|
94
94
|
class Track {
|
|
95
95
|
public:
|
|
96
|
-
uint32_t nodeId;
|
|
96
|
+
uint32_t nodeId = 0;
|
|
97
97
|
TSTRING hardware;
|
|
98
98
|
uint32_t hits = 0;
|
|
99
99
|
uint32_t misses = 0;
|