@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 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; a
98
- maintainer runs it on a pull request by adding the `run-hil` label, and the
99
- release gate is three consecutive clean runs of the whole suite.
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
- platform = espressif32
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
- platform = espressif32
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
@@ -55,6 +55,7 @@ getPrimaryBridge KEYWORD2
55
55
  getPrimaryGateway KEYWORD2
56
56
  enableSendToInternet KEYWORD2
57
57
  sendToInternet KEYWORD2
58
+ tcpListening KEYWORD2
58
59
  enableMessageQueue KEYWORD2
59
60
  queueMessage KEYWORD2
60
61
  flushMessageQueue KEYWORD2
package/library.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "type": "git",
7
7
  "url": "https://github.com/Alteriom/painlessMesh"
8
8
  },
9
- "version": "2.1.0",
9
+ "version": "2.1.1",
10
10
  "frameworks": [
11
11
  "arduino"
12
12
  ],
@@ -1,5 +1,5 @@
1
1
  name=Alteriom PainlessMesh
2
- version=2.1.0
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.0",
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.0"
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 0
35
+ #define ALTERIOM_PAINLESS_MESH_VERSION_PATCH 1
36
36
 
37
37
  /**
38
38
  * @brief Library description and usage information
@@ -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() == 1) {
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() != 1) {
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
- WiFiEventId_t eventScanDoneHandler;
3240
- WiFiEventId_t eventSTAStartHandler;
3241
- WiFiEventId_t eventSTADisconnectedHandler;
3242
- WiFiEventId_t eventSTAGotIPHandler;
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
- AsyncServer* _tcpListener;
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;
@@ -258,7 +258,7 @@ class AsyncServer {
258
258
 
259
259
  protected:
260
260
  boost::asio::io_context& _io_service;
261
- uint16_t _port;
261
+ uint16_t _port = 0;
262
262
  tcp::acceptor mAcceptor;
263
263
  AcConnectHandler _connect_cb = 0;
264
264
  void* _connect_cb_arg = 0;
@@ -125,10 +125,11 @@ class StationScan {
125
125
  protected:
126
126
  TSTRING ssid;
127
127
  TSTRING password;
128
- painlessMesh *mesh;
129
- uint16_t port;
130
- uint8_t channel;
131
- bool hidden;
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
  /**
@@ -183,7 +183,7 @@ class LogClass {
183
183
  Sink sink = nullptr;
184
184
  char str[200];
185
185
  std::list<std::pair<uint32_t, TSTRING>> remote_queue;
186
- uint32_t remote_uuid;
186
+ uint32_t remote_uuid = 0;
187
187
  };
188
188
 
189
189
  } // namespace logger
@@ -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
- this->addTask([this, ack]() {
2166
- gateway::GatewayAckPackage local = ack;
2167
- protocol::Variant variant(&local);
2168
- this->callbackList.execute(protocol::GATEWAY_ACK, variant, nullptr, 0);
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
- const PendingInternetRequest done = request;
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
- const PendingInternetRequest done = request;
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
- Scheduler *mScheduler;
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; // Unique message ID
41
- MessagePriority priority; // Message priority level
42
- uint32_t timestamp; // When message was queued (millis)
43
- uint32_t attempts; // Number of send 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;
@@ -51,7 +51,7 @@ class Timer {
51
51
  #endif
52
52
  }
53
53
 
54
- uint32_t start_time_;
54
+ uint32_t start_time_ = 0;
55
55
  #ifdef ESP32
56
56
  int64_t start_time_us_ = esp_timer_get_time();
57
57
  #endif
@@ -112,7 +112,7 @@ class Announce : public BroadcastPackage {
112
112
  */
113
113
  bool compressed = false;
114
114
 
115
- size_t noPart;
115
+ size_t noPart = 0;
116
116
 
117
117
  Announce() : BroadcastPackage(10) {}
118
118
 
@@ -39,10 +39,13 @@ namespace plugin {
39
39
 
40
40
  class SinglePackage : public protocol::PackageInterface {
41
41
  public:
42
- uint32_t from;
43
- uint32_t dest;
44
- router::Type routing;
45
- int type;
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() {}
@@ -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; // Can see if we missed values
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;