@alteriom/painlessmesh 1.10.0 → 2.0.0
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/BRIDGE_TO_INTERNET.md +167 -29
- package/CHANGELOG.md +483 -0
- package/CONTRIBUTING.md +56 -53
- package/README.md +100 -95
- package/RELEASE_GUIDE.md +81 -780
- package/examples/alteriom/README.md +8 -10
- package/examples/alteriom/alteriom.ino +2 -2
- package/examples/alteriom/alteriom_sensor_package.hpp +17 -11
- package/examples/alteriom/mppt_example/alteriom_custom_package_template.hpp +320 -0
- package/examples/alteriom/mppt_example/alteriom_sensor_package.hpp +1389 -0
- package/examples/alteriom/mppt_example/{alteriom_mppt_example.ino → mppt_example.ino} +4 -0
- package/examples/basic/test/simulator/README.md +3 -3
- package/examples/bridge_failover/README.md +51 -14
- package/examples/commandControl/commandControl.ino +86 -0
- package/examples/commandControl/platformio.ini +26 -0
- package/examples/mqttBridge/mqttBridge.ino +4 -0
- package/examples/mqttBridge/platformio.ini +1 -1
- package/examples/otaSender/otaSender.ino +5 -1
- package/examples/priority/README.md +1 -1
- package/examples/priority/{priority_basic_example.ino → priority_basic_example/priority_basic_example.ino} +4 -4
- package/examples/priority/{priority_with_queue.ino → priority_with_queue/priority_with_queue.ino} +20 -2
- package/examples/reliableSensorLogging/platformio.ini +26 -0
- package/examples/reliableSensorLogging/reliableSensorLogging.ino +151 -0
- package/examples/sendToInternet/README.md +12 -5
- package/examples/sendToInternet/{CMakeLists.txt → pc_node/CMakeLists.txt} +7 -7
- package/examples/sendToInternet/{PC_NODE_README.md → pc_node/PC_NODE_README.md} +15 -15
- package/examples/sendToInternet/{build.sh → pc_node/build.sh} +5 -5
- package/examples/sendToInternet/{pc_mesh_node.cpp → pc_node/pc_mesh_node.cpp} +12 -1
- package/examples/sharedGateway/README.md +1 -2
- package/keywords.txt +50 -1
- package/library.json +8 -6
- package/library.properties +2 -2
- package/package.json +3 -3
- package/src/AlteriomPainlessMesh.h +3 -3
- package/src/arduino/wifi.hpp +556 -126
- package/src/painlessMesh.h +2 -2
- package/src/painlessMeshSTA.cpp +607 -87
- package/src/painlessMeshSTA.h +135 -3
- package/src/painlessmesh/ack.hpp +283 -0
- package/src/painlessmesh/buffer.hpp +70 -8
- package/src/painlessmesh/callback.hpp +38 -5
- package/src/painlessmesh/configuration.hpp +69 -1
- package/src/painlessmesh/connection.hpp +12 -5
- package/src/painlessmesh/gateway.hpp +270 -5
- package/src/painlessmesh/layout.hpp +70 -2
- package/src/painlessmesh/logger.hpp +15 -0
- package/src/painlessmesh/mesh.hpp +552 -48
- package/src/painlessmesh/ntp.hpp +2 -4
- package/src/painlessmesh/plugin.hpp +30 -6
- package/src/painlessmesh/protocol.hpp +55 -2
- package/src/painlessmesh/router.hpp +192 -77
- package/src/painlessmesh/tcp.hpp +10 -0
- package/src/painlessmesh/message_tracker.hpp +0 -311
- /package/examples/sendToInternet/{mock_server_test.ino → mock_server_test/mock_server_test.ino} +0 -0
|
@@ -7,19 +7,21 @@
|
|
|
7
7
|
#include <map>
|
|
8
8
|
#include <set>
|
|
9
9
|
#include <queue>
|
|
10
|
+
#include <type_traits>
|
|
10
11
|
|
|
11
12
|
#include "painlessmesh/configuration.hpp"
|
|
12
13
|
|
|
14
|
+
#include "painlessmesh/ack.hpp"
|
|
13
15
|
#include "painlessmesh/connection.hpp"
|
|
14
16
|
#include "painlessmesh/gateway.hpp"
|
|
15
17
|
#include "painlessmesh/logger.hpp"
|
|
16
18
|
#include "painlessmesh/message_queue.hpp"
|
|
17
|
-
#include "painlessmesh/message_tracker.hpp"
|
|
18
19
|
#include "painlessmesh/ntp.hpp"
|
|
19
20
|
#include "painlessmesh/plugin.hpp"
|
|
20
21
|
#include "painlessmesh/protocol.hpp"
|
|
21
22
|
#include "painlessmesh/rtc.hpp"
|
|
22
23
|
#include "painlessmesh/tcp.hpp"
|
|
24
|
+
#include "painlessmesh/validation.hpp"
|
|
23
25
|
|
|
24
26
|
#ifdef PAINLESSMESH_ENABLE_OTA
|
|
25
27
|
#include "painlessmesh/ota.hpp"
|
|
@@ -37,6 +39,38 @@ typedef std::function<void(uint32_t timestamp)> rtcSyncCompleteCallback_t;
|
|
|
37
39
|
typedef std::function<void(bool available)> localInternetChangedCallback_t;
|
|
38
40
|
typedef std::function<void(uint32_t oldPrimary, uint32_t newPrimary)> gatewayChangedCallback_t;
|
|
39
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Options for sendSingle() / sendBroadcast() (issue #384)
|
|
44
|
+
*
|
|
45
|
+
* Bundles the outbound queue priority and the optional delivery confirmation
|
|
46
|
+
* into one composable struct, so a message can be both prioritized and
|
|
47
|
+
* acknowledgment-tracked in a single call — something the separate priority
|
|
48
|
+
* and ack overloads cannot express:
|
|
49
|
+
*
|
|
50
|
+
* \code
|
|
51
|
+
* painlessmesh::SendOptions options;
|
|
52
|
+
* options.priority = painlessmesh::protocol::PRIORITY_HIGH;
|
|
53
|
+
* options.ackCallback = [](uint32_t nodeId, bool delivered, uint32_t ms) {};
|
|
54
|
+
* mesh.sendSingle(dest, msg, options);
|
|
55
|
+
* \endcode
|
|
56
|
+
*
|
|
57
|
+
* The priority is carried on the wire (serialized as "prio" only when it
|
|
58
|
+
* deviates from PRIORITY_NORMAL), so intermediate nodes re-enqueue a
|
|
59
|
+
* forwarded package at the sender's priority instead of dropping it to
|
|
60
|
+
* NORMAL after the first hop.
|
|
61
|
+
*/
|
|
62
|
+
struct SendOptions {
|
|
63
|
+
/** Outbound queue priority: PRIORITY_CRITICAL (0), PRIORITY_HIGH (1),
|
|
64
|
+
* PRIORITY_NORMAL (2, default) or PRIORITY_LOW (3). */
|
|
65
|
+
uint8_t priority = protocol::PRIORITY_NORMAL;
|
|
66
|
+
/** Optional delivery confirmation callback; fires once per expected node
|
|
67
|
+
* with delivered = true and the round-trip latency, or delivered = false
|
|
68
|
+
* on timeout. nullptr (default) disables tracking. */
|
|
69
|
+
ack::deliveryCallback_t ackCallback = nullptr;
|
|
70
|
+
/** How long to wait for acknowledgments before reporting failure. */
|
|
71
|
+
uint32_t ackTimeoutMs = 5000;
|
|
72
|
+
};
|
|
73
|
+
|
|
40
74
|
/**
|
|
41
75
|
* Callback type for Internet request results
|
|
42
76
|
*
|
|
@@ -206,10 +240,56 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
206
240
|
#endif
|
|
207
241
|
|
|
208
242
|
// Add package handlers
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
243
|
+
painlessmesh::ntp::addPackageCallback(this->callbackList, (*this));
|
|
244
|
+
painlessmesh::router::addPackageCallback(this->callbackList, (*this));
|
|
245
|
+
|
|
246
|
+
// Seed delivery-confirmation ids with a random value so they do not
|
|
247
|
+
// restart at 1 after a reboot — a delayed ACK for a pre-reboot
|
|
248
|
+
// message could otherwise match a fresh message's id and report a
|
|
249
|
+
// false delivered = true.
|
|
250
|
+
ackTracker.seed(validation::SecureRandom::generate());
|
|
251
|
+
|
|
252
|
+
// Per-message delivery confirmation (issue #379): reply with an ACK
|
|
253
|
+
// when a received application message carries a msgId. Uses the
|
|
254
|
+
// Variant field peeks — the common no-ack case must not pay for a
|
|
255
|
+
// full package materialization on every received message.
|
|
256
|
+
auto autoAck = [this](protocol::Variant& variant, std::shared_ptr<T>,
|
|
257
|
+
uint32_t) {
|
|
258
|
+
auto msgId = variant.msgId();
|
|
259
|
+
if (msgId == 0) return false;
|
|
260
|
+
auto origin = variant.from();
|
|
261
|
+
if (origin == 0 || origin == this->nodeId) return false;
|
|
262
|
+
if (variant.type() == protocol::BROADCAST) {
|
|
263
|
+
// Stagger broadcast ACKs by nodeId so N nodes do not fire N
|
|
264
|
+
// unicast ACKs at the sender in the same instant
|
|
265
|
+
this->queueBroadcastAck(origin, msgId);
|
|
266
|
+
} else {
|
|
267
|
+
auto ackPkg = ack::MessageAckPackage(this->nodeId, origin, msgId);
|
|
268
|
+
this->sendPackage(&ackPkg);
|
|
269
|
+
}
|
|
270
|
+
return false;
|
|
271
|
+
};
|
|
272
|
+
this->callbackList.onPackage(protocol::SINGLE, autoAck);
|
|
273
|
+
this->callbackList.onPackage(protocol::BROADCAST, autoAck);
|
|
274
|
+
|
|
275
|
+
// Match incoming ACKs to messages awaiting delivery confirmation
|
|
276
|
+
this->callbackList.onPackage(
|
|
277
|
+
protocol::MESSAGE_ACK,
|
|
278
|
+
[this](protocol::Variant& variant, std::shared_ptr<T>, uint32_t) {
|
|
279
|
+
std::list<ack::DeliveryResult> results;
|
|
280
|
+
this->ackTracker.collectAck(variant.msgId(), variant.from(),
|
|
281
|
+
static_cast<uint32_t>(millis()),
|
|
282
|
+
results);
|
|
283
|
+
if (!results.empty()) {
|
|
284
|
+
this->addTask([results]() {
|
|
285
|
+
for (const auto& result : results) {
|
|
286
|
+
result.callback(result.nodeId, result.delivered,
|
|
287
|
+
result.latencyMs);
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
return false;
|
|
292
|
+
});
|
|
213
293
|
|
|
214
294
|
// Add bridge status package handler (Type BRIDGE_STATUS)
|
|
215
295
|
// This will be called when any node receives a bridge status broadcast
|
|
@@ -226,6 +306,14 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
226
306
|
|
|
227
307
|
if (obj["internetConnected"].is<bool>()) {
|
|
228
308
|
uint32_t bridgeNodeId = obj["from"];
|
|
309
|
+
if (obj["leaving"] | false) {
|
|
310
|
+
// A bridge stepping down says so, and is forgotten at once
|
|
311
|
+
// rather than when its last status ages out — a minute and
|
|
312
|
+
// more during which every candidate held it healthy.
|
|
313
|
+
Log(GENERAL, "Bridge %u is stepping down\n", bridgeNodeId);
|
|
314
|
+
this->forgetBridge(bridgeNodeId);
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
229
317
|
bool internetConnected = obj["internetConnected"];
|
|
230
318
|
int8_t routerRSSI = obj["routerRSSI"] | 0;
|
|
231
319
|
uint8_t routerChannel = obj["routerChannel"] | 0;
|
|
@@ -344,6 +432,17 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
344
432
|
(*conn)->close();
|
|
345
433
|
this->eraseClosedConnections();
|
|
346
434
|
}
|
|
435
|
+
// Disable the ack task BEFORE nulling the member: if stop() was
|
|
436
|
+
// reached from inside the task's own callback, the lambda's
|
|
437
|
+
// this->ackCheckTask guard is useless once the member is null and
|
|
438
|
+
// the task would keep firing forever on an external scheduler.
|
|
439
|
+
if (ackCheckTask) ackCheckTask->disable();
|
|
440
|
+
if (broadcastAckTask) broadcastAckTask->disable();
|
|
441
|
+
ackTracker.clear();
|
|
442
|
+
pendingBroadcastAcks.clear();
|
|
443
|
+
ackCheckTask = nullptr;
|
|
444
|
+
broadcastAckTask = nullptr;
|
|
445
|
+
|
|
347
446
|
plugin::PackageHandler<T>::stop(mScheduler);
|
|
348
447
|
|
|
349
448
|
newConnectionCallbacks.clear();
|
|
@@ -351,8 +450,18 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
351
450
|
changedConnectionCallbacks.clear();
|
|
352
451
|
|
|
353
452
|
if (!isExternalScheduler) {
|
|
354
|
-
|
|
355
|
-
|
|
453
|
+
if (mScheduler && mScheduler->getCurrentTask() != nullptr) {
|
|
454
|
+
// stop() was reached from inside a scheduler callback (e.g. a
|
|
455
|
+
// delivery/timeout callback). Deleting the scheduler here would
|
|
456
|
+
// free the object whose execute() is still on the call stack —
|
|
457
|
+
// the same bug class as issue #373. Retire it until execute()
|
|
458
|
+
// unwinds, then reclaim it during the next update.
|
|
459
|
+
retiredSchedulers.push_back(mScheduler);
|
|
460
|
+
mScheduler = nullptr;
|
|
461
|
+
} else {
|
|
462
|
+
delete mScheduler;
|
|
463
|
+
mScheduler = nullptr;
|
|
464
|
+
}
|
|
356
465
|
}
|
|
357
466
|
}
|
|
358
467
|
|
|
@@ -363,9 +472,16 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
363
472
|
*/
|
|
364
473
|
void update(void) {
|
|
365
474
|
if (semaphoreTake()) {
|
|
475
|
+
plugin::PackageHandler<T>::reclaimQuarantinedTasks();
|
|
476
|
+
reclaimRetiredSchedulers();
|
|
366
477
|
// Check if something is executed (returns false)
|
|
367
478
|
if (!mScheduler->execute())
|
|
368
|
-
|
|
479
|
+
// DEBUG, not GENERAL: this fires every time any task runs, which on a
|
|
480
|
+
// busy node is thousands of lines a second. At GENERAL it drowned the
|
|
481
|
+
// level that carries the bridge and gateway diagnostics —
|
|
482
|
+
// sendBridgeStatus(), "Bridge status received from %u" — so nobody
|
|
483
|
+
// can turn those on to investigate a failover without being flooded.
|
|
484
|
+
Log(logger::DEBUG, "update(): Scheduler executed a task\n");
|
|
369
485
|
semaphoreGive();
|
|
370
486
|
}
|
|
371
487
|
return;
|
|
@@ -379,12 +495,9 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
379
495
|
* @return true if everything works, false if not.
|
|
380
496
|
*/
|
|
381
497
|
bool sendSingle(uint32_t destId, TSTRING msg) {
|
|
382
|
-
|
|
383
|
-
msg.c_str());
|
|
384
|
-
auto single = painlessmesh::protocol::Single(this->nodeId, destId, msg);
|
|
385
|
-
return painlessmesh::router::send<T>(single, (*this));
|
|
498
|
+
return sendSingle(destId, msg, SendOptions());
|
|
386
499
|
}
|
|
387
|
-
|
|
500
|
+
|
|
388
501
|
/** Send message to a specific node with priority
|
|
389
502
|
*
|
|
390
503
|
* @param destId The nodeId of the node to send it to.
|
|
@@ -394,12 +507,85 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
394
507
|
* @return true if everything works, false if not.
|
|
395
508
|
*/
|
|
396
509
|
bool sendSingle(uint32_t destId, TSTRING msg, uint8_t priorityLevel) {
|
|
397
|
-
|
|
398
|
-
|
|
510
|
+
SendOptions options;
|
|
511
|
+
options.priority = priorityLevel;
|
|
512
|
+
return sendSingle(destId, msg, options);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/** Send message to a specific node — unified path (issue #384)
|
|
516
|
+
*
|
|
517
|
+
* All sendSingle() overloads funnel into this one. Priority and delivery
|
|
518
|
+
* confirmation compose freely; see SendOptions for the semantics of each
|
|
519
|
+
* field.
|
|
520
|
+
*
|
|
521
|
+
* @param destId The nodeId of the node to send it to.
|
|
522
|
+
* @param msg The message to send
|
|
523
|
+
* @param options Priority and/or delivery confirmation options.
|
|
524
|
+
*
|
|
525
|
+
* @return true if the message was queued for sending, false if not (no
|
|
526
|
+
* route to destination, or — when an ackCallback is set —
|
|
527
|
+
* PAINLESSMESH_MAX_PENDING_ACKS messages already await
|
|
528
|
+
* acknowledgment; in both cases nothing is sent and the callback
|
|
529
|
+
* will not fire).
|
|
530
|
+
*/
|
|
531
|
+
bool sendSingle(uint32_t destId, TSTRING msg, const SendOptions& options) {
|
|
532
|
+
Log(logger::COMMUNICATION, "sendSingle(): dest=%u msg=%s priority=%u%s\n",
|
|
533
|
+
destId, msg.c_str(), options.priority,
|
|
534
|
+
options.ackCallback ? " with ack" : "");
|
|
399
535
|
auto single = painlessmesh::protocol::Single(this->nodeId, destId, msg);
|
|
400
|
-
|
|
401
|
-
if (!
|
|
402
|
-
|
|
536
|
+
single.priority = options.priority;
|
|
537
|
+
if (!options.ackCallback)
|
|
538
|
+
return painlessmesh::router::send<T>(single, (*this));
|
|
539
|
+
if (ackTracker.full()) {
|
|
540
|
+
Log(logger::ERROR, "sendSingle(): pending-ack limit reached\n");
|
|
541
|
+
return false;
|
|
542
|
+
}
|
|
543
|
+
single.msgId = ackTracker.nextMessageId();
|
|
544
|
+
if (!painlessmesh::router::send<T>(single, (*this))) return false;
|
|
545
|
+
ackTracker.track(single.msgId, {destId}, options.ackCallback,
|
|
546
|
+
options.ackTimeoutMs, static_cast<uint32_t>(millis()));
|
|
547
|
+
this->ensureAckScheduling();
|
|
548
|
+
return true;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/** Send message to a specific node with delivery confirmation
|
|
552
|
+
*
|
|
553
|
+
* The message is tagged with a unique id and the destination node
|
|
554
|
+
* automatically replies with an acknowledgment. The callback fires
|
|
555
|
+
* exactly once: with delivered = true and the measured round-trip
|
|
556
|
+
* latency when the ACK arrives, or with delivered = false when
|
|
557
|
+
* ackTimeoutMs elapses without an ACK. ACK processing happens inside
|
|
558
|
+
* mesh.update() — no blocking waits.
|
|
559
|
+
*
|
|
560
|
+
* \code
|
|
561
|
+
* mesh.sendSingle(dest, msg,
|
|
562
|
+
* [](uint32_t nodeId, bool delivered, uint32_t latencyMs) {
|
|
563
|
+
* if (delivered)
|
|
564
|
+
* Serial.printf("Node %u confirmed in %u ms\n", nodeId, latencyMs);
|
|
565
|
+
* else
|
|
566
|
+
* Serial.printf("Delivery to %u timed out\n", nodeId);
|
|
567
|
+
* });
|
|
568
|
+
* \endcode
|
|
569
|
+
*
|
|
570
|
+
* @param destId The nodeId of the node to send it to.
|
|
571
|
+
* @param msg The message to send
|
|
572
|
+
* @param ackCallback Delivery confirmation callback. Passing nullptr
|
|
573
|
+
* behaves exactly like the plain sendSingle().
|
|
574
|
+
* @param ackTimeoutMs How long to wait for the acknowledgment (default
|
|
575
|
+
* 5000 ms).
|
|
576
|
+
*
|
|
577
|
+
* @return true if the message was queued for sending, false if not (no
|
|
578
|
+
* route to destination, or PAINLESSMESH_MAX_PENDING_ACKS
|
|
579
|
+
* messages already await acknowledgment — in both cases nothing
|
|
580
|
+
* is sent and the callback will not fire).
|
|
581
|
+
*/
|
|
582
|
+
bool sendSingle(uint32_t destId, TSTRING msg,
|
|
583
|
+
ack::deliveryCallback_t ackCallback,
|
|
584
|
+
uint32_t ackTimeoutMs = 5000) {
|
|
585
|
+
SendOptions options;
|
|
586
|
+
options.ackCallback = ackCallback;
|
|
587
|
+
options.ackTimeoutMs = ackTimeoutMs;
|
|
588
|
+
return sendSingle(destId, msg, options);
|
|
403
589
|
}
|
|
404
590
|
|
|
405
591
|
/** Broadcast a message to every node on the mesh network.
|
|
@@ -409,18 +595,142 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
409
595
|
* @return true if everything works, false if not
|
|
410
596
|
*/
|
|
411
597
|
bool sendBroadcast(TSTRING msg, bool includeSelf = false) {
|
|
598
|
+
return sendBroadcast(msg, SendOptions(), includeSelf);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/** Broadcast a message to every node — unified path (issue #384)
|
|
602
|
+
*
|
|
603
|
+
* All sendBroadcast() overloads funnel into this one. Priority and
|
|
604
|
+
* delivery confirmation compose freely; see SendOptions for the semantics
|
|
605
|
+
* of each field. With an ackCallback set, the set of nodes expected to
|
|
606
|
+
* acknowledge is snapshotted from the current mesh layout (excluding this
|
|
607
|
+
* node) when the broadcast is sent, and the callback fires once per
|
|
608
|
+
* expected node. ackTimeoutMs values below 1000 ms are raised to 1000 ms
|
|
609
|
+
* so the 50 ms ACK jitter window still leaves time for routed round-trip
|
|
610
|
+
* delivery.
|
|
611
|
+
*
|
|
612
|
+
* @param msg The message to broadcast
|
|
613
|
+
* @param options Priority and/or delivery confirmation options.
|
|
614
|
+
* @param includeSelf Send message to myself as well. Default is false.
|
|
615
|
+
*
|
|
616
|
+
* @return Without an ackCallback: true if the message was queued to at
|
|
617
|
+
* least one peer. With an ackCallback: true if the message was
|
|
618
|
+
* queued and at least one node is expected to acknowledge, false
|
|
619
|
+
* otherwise — including when PAINLESSMESH_MAX_PENDING_ACKS
|
|
620
|
+
* messages already await acknowledgment (nothing is sent; the
|
|
621
|
+
* callback will not fire).
|
|
622
|
+
*/
|
|
623
|
+
bool sendBroadcast(TSTRING msg, const SendOptions& options,
|
|
624
|
+
bool includeSelf = false) {
|
|
412
625
|
using namespace logger;
|
|
413
|
-
Log(COMMUNICATION, "sendBroadcast(): msg=%s\n", msg.c_str()
|
|
626
|
+
Log(COMMUNICATION, "sendBroadcast(): msg=%s priority=%u%s\n", msg.c_str(),
|
|
627
|
+
options.priority, options.ackCallback ? " with ack" : "");
|
|
414
628
|
painlessmesh::protocol::Broadcast pkg(this->nodeId, 0, msg);
|
|
629
|
+
pkg.priority = options.priority;
|
|
630
|
+
|
|
631
|
+
if (!options.ackCallback) {
|
|
632
|
+
auto success = router::broadcast<protocol::Broadcast, T>(pkg, (*this), 0);
|
|
633
|
+
if (includeSelf) {
|
|
634
|
+
protocol::Variant var(pkg);
|
|
635
|
+
this->callbackList.execute(var.type(), var, NULL, 0);
|
|
636
|
+
}
|
|
637
|
+
return success > 0;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
if (ackTracker.full()) {
|
|
641
|
+
Log(ERROR, "sendBroadcast(): pending-ack limit reached\n");
|
|
642
|
+
return false;
|
|
643
|
+
}
|
|
644
|
+
// Broadcast recipients intentionally jitter ACK replies across this
|
|
645
|
+
// window. A shorter deadline would deterministically time out nodes
|
|
646
|
+
// whose slot falls after it, even when delivery is immediate.
|
|
647
|
+
auto ackTimeoutMs = options.ackTimeoutMs;
|
|
648
|
+
if (ackTimeoutMs < ACK_BROADCAST_MIN_TIMEOUT_MS)
|
|
649
|
+
ackTimeoutMs = ACK_BROADCAST_MIN_TIMEOUT_MS;
|
|
650
|
+
auto expected = this->getNodeList(false);
|
|
651
|
+
pkg.msgId = ackTracker.nextMessageId();
|
|
415
652
|
auto success = router::broadcast<protocol::Broadcast, T>(pkg, (*this), 0);
|
|
653
|
+
const bool trackingPeers = success > 0 && !expected.empty();
|
|
654
|
+
if (trackingPeers) {
|
|
655
|
+
ackTracker.track(pkg.msgId, expected, options.ackCallback, ackTimeoutMs,
|
|
656
|
+
static_cast<uint32_t>(millis()));
|
|
657
|
+
this->ensureAckScheduling();
|
|
658
|
+
}
|
|
416
659
|
if (includeSelf) {
|
|
417
660
|
protocol::Variant var(pkg);
|
|
418
661
|
this->callbackList.execute(var.type(), var, NULL, 0);
|
|
419
662
|
}
|
|
420
|
-
|
|
421
|
-
|
|
663
|
+
// Do not access mesh state after self-delivery: an onReceive callback
|
|
664
|
+
// may have called stop(). Local delivery still occurs without peers,
|
|
665
|
+
// while the return value continues to describe peer ACK tracking.
|
|
666
|
+
return trackingPeers;
|
|
422
667
|
}
|
|
423
668
|
|
|
669
|
+
/** Broadcast a message with per-node delivery confirmation
|
|
670
|
+
*
|
|
671
|
+
* The set of nodes expected to acknowledge is snapshotted from the
|
|
672
|
+
* current mesh layout (excluding this node) when the broadcast is sent.
|
|
673
|
+
* The callback fires once per expected node: with delivered = true and
|
|
674
|
+
* the round-trip latency when that node's ACK arrives, or with
|
|
675
|
+
* delivered = false when ackTimeoutMs elapses. The local node never
|
|
676
|
+
* acknowledges itself, even with includeSelf = true.
|
|
677
|
+
*
|
|
678
|
+
* Note: includeSelf and ackCallback are deliberately non-defaulted on
|
|
679
|
+
* this overload; C++ overload resolution against the priority overload
|
|
680
|
+
* would otherwise be ambiguous or, worse, silently convert a lambda to
|
|
681
|
+
* bool.
|
|
682
|
+
*
|
|
683
|
+
* @param msg The message to broadcast
|
|
684
|
+
* @param includeSelf Send message to myself as well.
|
|
685
|
+
* @param ackCallback Delivery confirmation callback, fired once per
|
|
686
|
+
* node. Passing nullptr behaves like the plain sendBroadcast().
|
|
687
|
+
* @param ackTimeoutMs How long to wait for acknowledgments (default
|
|
688
|
+
* 5000 ms). Values below 1000 ms are raised to 1000 ms so the
|
|
689
|
+
* 50 ms jitter window still leaves time for routed round-trip
|
|
690
|
+
* delivery.
|
|
691
|
+
*
|
|
692
|
+
* @return true if the message was queued and at least one node is
|
|
693
|
+
* expected to acknowledge, false otherwise — including when
|
|
694
|
+
* PAINLESSMESH_MAX_PENDING_ACKS messages already await
|
|
695
|
+
* acknowledgment (nothing is sent; the callback will not
|
|
696
|
+
* fire).
|
|
697
|
+
*/
|
|
698
|
+
bool sendBroadcast(TSTRING msg, bool includeSelf,
|
|
699
|
+
ack::deliveryCallback_t ackCallback,
|
|
700
|
+
uint32_t ackTimeoutMs = 5000) {
|
|
701
|
+
SendOptions options;
|
|
702
|
+
options.ackCallback = ackCallback;
|
|
703
|
+
options.ackTimeoutMs = ackTimeoutMs;
|
|
704
|
+
return sendBroadcast(msg, options, includeSelf);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/** Process pending delivery acknowledgments (non-blocking poll)
|
|
708
|
+
*
|
|
709
|
+
* Schedules timeout callbacks for messages whose acknowledgment window has
|
|
710
|
+
* elapsed. Callbacks run on a later scheduler pass, after the current mesh
|
|
711
|
+
* dispatch has unwound. This also happens automatically inside
|
|
712
|
+
* mesh.update(), so calling it manually is only needed in tight loops that
|
|
713
|
+
* want prompt timeout detection.
|
|
714
|
+
*
|
|
715
|
+
* @return Number of messages still awaiting acknowledgment
|
|
716
|
+
*/
|
|
717
|
+
size_t checkAcks() {
|
|
718
|
+
std::list<ack::DeliveryResult> results;
|
|
719
|
+
auto pending = ackTracker.collectExpired(static_cast<uint32_t>(millis()),
|
|
720
|
+
results);
|
|
721
|
+
if (!results.empty()) {
|
|
722
|
+
this->addTask([results]() {
|
|
723
|
+
for (const auto& result : results) {
|
|
724
|
+
result.callback(result.nodeId, result.delivered, result.latencyMs);
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
return pending;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/** Number of messages still awaiting delivery acknowledgment */
|
|
732
|
+
size_t pendingAcks() const { return ackTracker.pending(); }
|
|
733
|
+
|
|
424
734
|
/** Broadcast a message with priority to every node on the mesh network.
|
|
425
735
|
*
|
|
426
736
|
* @param msg The message to broadcast
|
|
@@ -429,29 +739,20 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
429
739
|
*
|
|
430
740
|
* @return true if everything works, false if not
|
|
431
741
|
*/
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
if (includeSelf) {
|
|
450
|
-
protocol::Variant var(pkg);
|
|
451
|
-
this->callbackList.execute(var.type(), var, NULL, 0);
|
|
452
|
-
}
|
|
453
|
-
if (success > 0) return true;
|
|
454
|
-
return false;
|
|
742
|
+
template <
|
|
743
|
+
typename Priority,
|
|
744
|
+
typename IncludeSelf = bool,
|
|
745
|
+
typename std::enable_if<
|
|
746
|
+
(std::is_integral<typename std::decay<Priority>::type>::value ||
|
|
747
|
+
std::is_enum<typename std::decay<Priority>::type>::value) &&
|
|
748
|
+
!std::is_same<typename std::decay<Priority>::type, bool>::value &&
|
|
749
|
+
std::is_same<typename std::decay<IncludeSelf>::type, bool>::value,
|
|
750
|
+
int>::type = 0>
|
|
751
|
+
bool sendBroadcast(TSTRING msg, Priority priorityLevel,
|
|
752
|
+
IncludeSelf includeSelf = false) {
|
|
753
|
+
SendOptions options;
|
|
754
|
+
options.priority = static_cast<uint8_t>(priorityLevel);
|
|
755
|
+
return sendBroadcast(msg, options, includeSelf);
|
|
455
756
|
}
|
|
456
757
|
|
|
457
758
|
/** Sends a node a packet to measure network trip delay to that node.
|
|
@@ -483,6 +784,15 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
483
784
|
* Serial.println(msg);
|
|
484
785
|
* });
|
|
485
786
|
* \endcode
|
|
787
|
+
*
|
|
788
|
+
* \note **Accumulates — does not replace.** Calling onReceive() a second
|
|
789
|
+
* time does not overwrite the previous handler; both handlers are invoked
|
|
790
|
+
* for every subsequent message, in the order they were registered. This
|
|
791
|
+
* mirrors the other on...() setters on this class (onNewConnection,
|
|
792
|
+
* onDroppedConnection, onChangedConnections, onNodeTimeAdjusted,
|
|
793
|
+
* onNodeDelayReceived). There is currently no public API to unregister
|
|
794
|
+
* a handler; if you need replace-semantics, keep a single dispatcher
|
|
795
|
+
* lambda and mutate the target it dispatches to.
|
|
486
796
|
*/
|
|
487
797
|
void onReceive(receivedCallback_t onReceive) {
|
|
488
798
|
using namespace painlessmesh;
|
|
@@ -785,6 +1095,61 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
785
1095
|
return this->root;
|
|
786
1096
|
}
|
|
787
1097
|
|
|
1098
|
+
// ==================== Capacity (ESP8266) ====================
|
|
1099
|
+
|
|
1100
|
+
/** Live connections for which this node is the access point.
|
|
1101
|
+
*
|
|
1102
|
+
* Every one of them holds send and receive buffers on this node's heap.
|
|
1103
|
+
* A leaf has none; a node the mesh routes through has as many as attach.
|
|
1104
|
+
*/
|
|
1105
|
+
size_t apChildren() {
|
|
1106
|
+
size_t n = 0;
|
|
1107
|
+
for (auto&& sub : this->subs) {
|
|
1108
|
+
if (sub && sub->connected() && !sub->station) ++n;
|
|
1109
|
+
}
|
|
1110
|
+
return n;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
/** Whether this node is being asked to do more than its part can carry.
|
|
1114
|
+
*
|
|
1115
|
+
* The ESP8266 is specified for small meshes, or as a **leaf** in larger
|
|
1116
|
+
* ones: measured as an interior node of a seven-node mesh it runs at
|
|
1117
|
+
* 10–13 KB free — a working set that tracks its live connections and the
|
|
1118
|
+
* traffic through them, not a leak — and a single 8 KB package or one
|
|
1119
|
+
* OTA part can then fail to allocate. This is the part's limit, not a
|
|
1120
|
+
* defect. It is set by capacityCheck(), which the Arduino layer runs
|
|
1121
|
+
* periodically on ESP8266 and which logs an ERROR when the condition is
|
|
1122
|
+
* first met and again every five minutes it persists. Configure the node
|
|
1123
|
+
* as a leaf with init(..., maxconn = 0) — or 1 to allow a single child.
|
|
1124
|
+
*/
|
|
1125
|
+
bool overCapacity() { return _overCapacity; }
|
|
1126
|
+
|
|
1127
|
+
/** Evaluate the capacity rule now. Returns the new state.
|
|
1128
|
+
*
|
|
1129
|
+
* \param freeHeap the part's current free heap in bytes
|
|
1130
|
+
* \param floor below this, with more than one child, is over capacity
|
|
1131
|
+
*/
|
|
1132
|
+
bool capacityCheck(uint32_t freeHeap, uint32_t floor) {
|
|
1133
|
+
using namespace logger;
|
|
1134
|
+
size_t children = apChildren();
|
|
1135
|
+
bool over = freeHeap < floor && children > 1;
|
|
1136
|
+
uint32_t now = millis();
|
|
1137
|
+
if (over && (!_overCapacity ||
|
|
1138
|
+
now - _capacityWarnedAt > CAPACITY_WARN_INTERVAL_MS)) {
|
|
1139
|
+
Log(ERROR,
|
|
1140
|
+
"capacity: %u B free with %u AP children on ESP8266. This part is "
|
|
1141
|
+
"specified as a leaf in meshes of this size; init(..., maxconn=0) "
|
|
1142
|
+
"or reduce the mesh.\n",
|
|
1143
|
+
freeHeap, children);
|
|
1144
|
+
_capacityWarnedAt = now;
|
|
1145
|
+
}
|
|
1146
|
+
_overCapacity = over;
|
|
1147
|
+
return over;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
static constexpr uint32_t ESP8266_CAPACITY_FLOOR = 12 * 1024;
|
|
1151
|
+
static constexpr uint32_t CAPACITY_WARN_INTERVAL_MS = 5 * 60 * 1000;
|
|
1152
|
+
|
|
788
1153
|
// ==================== Gateway Status API ====================
|
|
789
1154
|
|
|
790
1155
|
/**
|
|
@@ -954,6 +1319,22 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
954
1319
|
* mesh.enableBridgeCleanup();
|
|
955
1320
|
* \endcode
|
|
956
1321
|
*/
|
|
1322
|
+
/**
|
|
1323
|
+
* Forget one bridge now: it announced that it is stepping down.
|
|
1324
|
+
*
|
|
1325
|
+
* Returns whether it was known.
|
|
1326
|
+
*/
|
|
1327
|
+
bool forgetBridge(uint32_t bridgeNodeId) {
|
|
1328
|
+
size_t sizeBefore = knownBridges.size();
|
|
1329
|
+
knownBridges.erase(
|
|
1330
|
+
std::remove_if(knownBridges.begin(), knownBridges.end(),
|
|
1331
|
+
[bridgeNodeId](const BridgeInfo& bridge) {
|
|
1332
|
+
return bridge.nodeId == bridgeNodeId;
|
|
1333
|
+
}),
|
|
1334
|
+
knownBridges.end());
|
|
1335
|
+
return knownBridges.size() != sizeBefore;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
957
1338
|
void cleanupExpiredBridges() {
|
|
958
1339
|
using namespace logger;
|
|
959
1340
|
size_t sizeBefore = knownBridges.size();
|
|
@@ -1279,11 +1660,39 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
1279
1660
|
Log(COMMUNICATION, "sendToInternet(): msgId=%u dest=%s priority=%u\n",
|
|
1280
1661
|
messageId, destination.c_str(), priority);
|
|
1281
1662
|
|
|
1282
|
-
//
|
|
1283
|
-
//
|
|
1284
|
-
//
|
|
1663
|
+
// A shared gateway can execute its own gateway package locally. Keep the
|
|
1664
|
+
// same package handler, pending-request tracking and callback semantics,
|
|
1665
|
+
// but do not require a mesh peer merely to reach this node's own uplink.
|
|
1285
1666
|
if (hasLocalInternet()) {
|
|
1286
|
-
|
|
1667
|
+
PendingInternetRequest request;
|
|
1668
|
+
request.messageId = messageId;
|
|
1669
|
+
request.timestamp = millis();
|
|
1670
|
+
request.retryCount = 0;
|
|
1671
|
+
request.maxRetries = internetRetryCount;
|
|
1672
|
+
request.priority = priority;
|
|
1673
|
+
request.timeoutMs = internetRequestTimeout;
|
|
1674
|
+
request.retryDelayMs = internetRetryDelay;
|
|
1675
|
+
request.gatewayNodeId = this->nodeId;
|
|
1676
|
+
request.destination = destination;
|
|
1677
|
+
request.payload = payload;
|
|
1678
|
+
request.callback = callback;
|
|
1679
|
+
pendingInternetRequests[messageId] = request;
|
|
1680
|
+
|
|
1681
|
+
gateway::GatewayDataPackage pkg;
|
|
1682
|
+
pkg.from = this->nodeId;
|
|
1683
|
+
pkg.dest = this->nodeId;
|
|
1684
|
+
pkg.messageId = messageId;
|
|
1685
|
+
pkg.originNode = this->nodeId;
|
|
1686
|
+
pkg.timestamp = this->getNodeTime();
|
|
1687
|
+
pkg.priority = priority;
|
|
1688
|
+
pkg.destination = destination;
|
|
1689
|
+
pkg.payload = payload;
|
|
1690
|
+
pkg.contentType = "application/json";
|
|
1691
|
+
pkg.retryCount = 0;
|
|
1692
|
+
pkg.requiresAck = true;
|
|
1693
|
+
protocol::Variant variant(&pkg);
|
|
1694
|
+
this->callbackList.execute(protocol::GATEWAY_DATA, variant, nullptr, 0);
|
|
1695
|
+
return messageId;
|
|
1287
1696
|
}
|
|
1288
1697
|
|
|
1289
1698
|
// Validate mesh connectivity before attempting to send
|
|
@@ -1346,6 +1755,9 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
1346
1755
|
bool sent = false;
|
|
1347
1756
|
auto conn = painlessmesh::router::findRoute<T>((*this), gateway->nodeId);
|
|
1348
1757
|
if (conn) {
|
|
1758
|
+
// HTTP runs on the gateway's cooperative scheduler. Preserve this
|
|
1759
|
+
// requester's route until the corresponding acknowledgment can return.
|
|
1760
|
+
painlessmesh::gateway::reserveGatewayBlockingBudget(*conn);
|
|
1349
1761
|
sent = painlessmesh::router::sendWithPriority(pkg, conn, priority);
|
|
1350
1762
|
if (!sent) {
|
|
1351
1763
|
Log(ERROR, "sendToInternet(): sendWithPriority failed to gateway %u (send buffer full?)\n", gateway->nodeId);
|
|
@@ -1714,6 +2126,7 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
1714
2126
|
auto conn = painlessmesh::router::findRoute<T>((*this), gateway->nodeId);
|
|
1715
2127
|
bool sent = false;
|
|
1716
2128
|
if (conn) {
|
|
2129
|
+
painlessmesh::gateway::reserveGatewayBlockingBudget(*conn);
|
|
1717
2130
|
sent = painlessmesh::router::sendWithPriority(pkg, conn, request.priority);
|
|
1718
2131
|
}
|
|
1719
2132
|
|
|
@@ -3129,11 +3542,99 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
3129
3542
|
|
|
3130
3543
|
~Mesh() {
|
|
3131
3544
|
this->stop();
|
|
3545
|
+
plugin::PackageHandler<T>::reclaimQuarantinedTasks();
|
|
3546
|
+
reclaimRetiredSchedulers();
|
|
3132
3547
|
if (!isExternalScheduler) delete mScheduler;
|
|
3133
3548
|
if (messageQueue) delete messageQueue;
|
|
3134
3549
|
}
|
|
3135
3550
|
|
|
3136
3551
|
protected:
|
|
3552
|
+
void reclaimRetiredSchedulers() {
|
|
3553
|
+
for (auto it = retiredSchedulers.begin();
|
|
3554
|
+
it != retiredSchedulers.end();) {
|
|
3555
|
+
if ((*it)->getCurrentTask() != nullptr) {
|
|
3556
|
+
++it;
|
|
3557
|
+
continue;
|
|
3558
|
+
}
|
|
3559
|
+
delete *it;
|
|
3560
|
+
it = retiredSchedulers.erase(it);
|
|
3561
|
+
}
|
|
3562
|
+
}
|
|
3563
|
+
|
|
3564
|
+
std::list<Scheduler*> retiredSchedulers = {};
|
|
3565
|
+
|
|
3566
|
+
void queueBroadcastAck(uint32_t origin, uint32_t msgId) {
|
|
3567
|
+
const auto key = std::make_pair(origin, msgId);
|
|
3568
|
+
if (std::find(pendingBroadcastAcks.begin(), pendingBroadcastAcks.end(),
|
|
3569
|
+
key) != pendingBroadcastAcks.end())
|
|
3570
|
+
return;
|
|
3571
|
+
if (pendingBroadcastAcks.size() >= MAX_QUEUED_BROADCAST_ACKS) {
|
|
3572
|
+
Log(logger::ERROR, "Mesh: broadcast-ack queue limit (%u) reached\n",
|
|
3573
|
+
(unsigned)MAX_QUEUED_BROADCAST_ACKS);
|
|
3574
|
+
return;
|
|
3575
|
+
}
|
|
3576
|
+
pendingBroadcastAcks.push_back(key);
|
|
3577
|
+
if (!broadcastAckTask) {
|
|
3578
|
+
broadcastAckTask = this->addTask(
|
|
3579
|
+
[this]() {
|
|
3580
|
+
while (!this->pendingBroadcastAcks.empty()) {
|
|
3581
|
+
auto queued = std::move(this->pendingBroadcastAcks);
|
|
3582
|
+
this->pendingBroadcastAcks.clear();
|
|
3583
|
+
for (const auto& item : queued) {
|
|
3584
|
+
auto pkg = ack::MessageAckPackage(this->nodeId, item.first,
|
|
3585
|
+
item.second);
|
|
3586
|
+
this->sendPackage(&pkg);
|
|
3587
|
+
}
|
|
3588
|
+
}
|
|
3589
|
+
},
|
|
3590
|
+
this->nodeId % ACK_BROADCAST_JITTER_MS);
|
|
3591
|
+
} else if (!broadcastAckTask->isEnabled() ||
|
|
3592
|
+
broadcastAckTask->isLastIteration()) {
|
|
3593
|
+
broadcastAckTask->restartDelayed();
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
3596
|
+
|
|
3597
|
+
/**
|
|
3598
|
+
* Make sure the periodic ack-timeout task is running
|
|
3599
|
+
*
|
|
3600
|
+
* The task disables itself as soon as no acknowledgments are pending,
|
|
3601
|
+
* so there is zero scheduler overhead when the ack API is not in use.
|
|
3602
|
+
*/
|
|
3603
|
+
void ensureAckScheduling() {
|
|
3604
|
+
if (!ackCheckTask) {
|
|
3605
|
+
ackCheckTask = this->addTask(ACK_CHECK_INTERVAL_MS, TASK_FOREVER,
|
|
3606
|
+
[this]() {
|
|
3607
|
+
if (this->checkAcks() == 0 &&
|
|
3608
|
+
this->ackCheckTask)
|
|
3609
|
+
this->ackCheckTask->disable();
|
|
3610
|
+
});
|
|
3611
|
+
} else if (!ackCheckTask->isEnabled()) {
|
|
3612
|
+
ackCheckTask->enable();
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
3615
|
+
|
|
3616
|
+
// Poll interval for ack timeouts while acks are pending. Note for
|
|
3617
|
+
// battery/light-sleep nodes: while any ack is outstanding the
|
|
3618
|
+
// scheduler wakes at this cadence for up to ackTimeoutMs; override at
|
|
3619
|
+
// build time (coarser is cheaper, timeout resolution degrades
|
|
3620
|
+
// accordingly).
|
|
3621
|
+
#ifndef PAINLESSMESH_ACK_CHECK_INTERVAL_MS
|
|
3622
|
+
#define PAINLESSMESH_ACK_CHECK_INTERVAL_MS 100
|
|
3623
|
+
#endif
|
|
3624
|
+
static constexpr unsigned long ACK_CHECK_INTERVAL_MS =
|
|
3625
|
+
PAINLESSMESH_ACK_CHECK_INTERVAL_MS;
|
|
3626
|
+
// Broadcast ACK replies are staggered by nodeId within this window so
|
|
3627
|
+
// N nodes do not converge N simultaneous unicasts on the sender
|
|
3628
|
+
static constexpr uint32_t ACK_BROADCAST_JITTER_MS = 50;
|
|
3629
|
+
// Allow the jittered ACK to traverse a multi-hop route after its slot.
|
|
3630
|
+
static constexpr uint32_t ACK_BROADCAST_MIN_TIMEOUT_MS = 1000;
|
|
3631
|
+
static constexpr size_t MAX_QUEUED_BROADCAST_ACKS =
|
|
3632
|
+
PAINLESSMESH_MAX_QUEUED_BROADCAST_ACKS;
|
|
3633
|
+
ack::AckTracker ackTracker;
|
|
3634
|
+
std::shared_ptr<Task> ackCheckTask;
|
|
3635
|
+
std::vector<std::pair<uint32_t, uint32_t> > pendingBroadcastAcks;
|
|
3636
|
+
std::shared_ptr<Task> broadcastAckTask;
|
|
3637
|
+
|
|
3137
3638
|
void setScheduler(Scheduler *baseScheduler) {
|
|
3138
3639
|
this->mScheduler = baseScheduler;
|
|
3139
3640
|
isExternalScheduler = true;
|
|
@@ -3275,6 +3776,9 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
3275
3776
|
uint32_t bridgeStatusIntervalMs = 30000; // Default 30 seconds
|
|
3276
3777
|
uint32_t bridgeTimeoutMs = 60000; // Default 60 seconds
|
|
3277
3778
|
bool bridgeStatusBroadcastEnabled = true;
|
|
3779
|
+
// See capacityCheck(). Only ever set on ESP8266.
|
|
3780
|
+
bool _overCapacity = false;
|
|
3781
|
+
uint32_t _capacityWarnedAt = 0;
|
|
3278
3782
|
|
|
3279
3783
|
// Bridge cleanup configuration
|
|
3280
3784
|
static const size_t MAX_KNOWN_BRIDGES = 20; // Memory efficient limit for ESP8266
|