@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
package/src/painlessmesh/ntp.hpp
CHANGED
|
@@ -249,8 +249,8 @@ void handleTimeDelay(T& mesh, painlessmesh::protocol::TimeDelay timeDelay,
|
|
|
249
249
|
}
|
|
250
250
|
|
|
251
251
|
template <class T, typename U>
|
|
252
|
-
callback::MeshPackageCallbackList<U
|
|
253
|
-
|
|
252
|
+
void addPackageCallback(callback::MeshPackageCallbackList<U>& callbackList,
|
|
253
|
+
T& mesh) {
|
|
254
254
|
// TimeSync
|
|
255
255
|
callbackList.onPackage(
|
|
256
256
|
protocol::TIME_SYNC,
|
|
@@ -270,8 +270,6 @@ callback::MeshPackageCallbackList<U> addPackageCallback(
|
|
|
270
270
|
handleTimeDelay<T, U>(mesh, timeDelay, connection, receivedAt);
|
|
271
271
|
return false;
|
|
272
272
|
});
|
|
273
|
-
|
|
274
|
-
return callbackList;
|
|
275
273
|
}
|
|
276
274
|
|
|
277
275
|
} // namespace ntp
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
#include "painlessmesh/configuration.hpp"
|
|
6
6
|
|
|
7
7
|
#include "painlessmesh/router.hpp"
|
|
8
|
+
#include <utility>
|
|
8
9
|
#include <vector>
|
|
9
10
|
|
|
10
11
|
namespace painlessmesh {
|
|
@@ -187,14 +188,19 @@ class PackageHandler : public layout::Layout<T> {
|
|
|
187
188
|
// in the middle of its own execution - causing a use-after-free crash
|
|
188
189
|
// (same bug family as upstream issue #373, but triggered by the
|
|
189
190
|
// shared_ptr refcount instead of a raw delete in onDisable).
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
//
|
|
191
|
+
// Move it out of the reusable list while retaining ownership for the
|
|
192
|
+
// handler's lifetime. This also protects a stop()/init() sequence inside
|
|
193
|
+
// the callback: a new scheduler must receive a new task rather than
|
|
194
|
+
// mutating the task that is still executing on the old scheduler.
|
|
194
195
|
Task* current = scheduler ? scheduler->getCurrentTask() : nullptr;
|
|
195
196
|
for (auto it = taskList.begin(); it != taskList.end();) {
|
|
196
197
|
if (current != nullptr && it->get() == current) {
|
|
197
|
-
|
|
198
|
+
// Keep the executing task alive, but remove it from the reusable
|
|
199
|
+
// pool. A callback may stop() and immediately init() with a new
|
|
200
|
+
// scheduler; reusing this still-running task would rewrite its
|
|
201
|
+
// closure and would not attach it to that new scheduler.
|
|
202
|
+
quarantinedTasks.push_back({scheduler, *it});
|
|
203
|
+
it = taskList.erase(it);
|
|
198
204
|
continue;
|
|
199
205
|
}
|
|
200
206
|
(*it)->disable();
|
|
@@ -247,6 +253,7 @@ class PackageHandler : public layout::Layout<T> {
|
|
|
247
253
|
long aIterations,
|
|
248
254
|
std::function<void()> aCallback) {
|
|
249
255
|
using namespace painlessmesh::logger;
|
|
256
|
+
reclaimQuarantinedTasks();
|
|
250
257
|
for (auto&& task : taskList) {
|
|
251
258
|
if (task.use_count() == 1 && !task->isEnabled()) {
|
|
252
259
|
task->set(aInterval, aIterations, aCallback, NULL, NULL);
|
|
@@ -281,11 +288,28 @@ class PackageHandler : public layout::Layout<T> {
|
|
|
281
288
|
}
|
|
282
289
|
|
|
283
290
|
protected:
|
|
291
|
+
void reclaimQuarantinedTasks() {
|
|
292
|
+
for (auto it = quarantinedTasks.begin(); it != quarantinedTasks.end();) {
|
|
293
|
+
if (it->first != nullptr &&
|
|
294
|
+
it->first->getCurrentTask() == it->second.get()) {
|
|
295
|
+
++it;
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
it->second->disable();
|
|
299
|
+
it->second->setCallback(NULL);
|
|
300
|
+
it = quarantinedTasks.erase(it);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
284
304
|
callback::MeshPackageCallbackList<T> callbackList;
|
|
285
305
|
std::list<std::shared_ptr<Task> > taskList = {};
|
|
306
|
+
// Tasks detached while their own callback is executing, paired with the
|
|
307
|
+
// scheduler whose stack still references them. They become reclaimable
|
|
308
|
+
// after that scheduler no longer reports the task as current.
|
|
309
|
+
std::list<std::pair<Scheduler*, std::shared_ptr<Task> > > quarantinedTasks =
|
|
310
|
+
{};
|
|
286
311
|
};
|
|
287
312
|
|
|
288
313
|
} // namespace plugin
|
|
289
314
|
} // namespace painlessmesh
|
|
290
315
|
#endif
|
|
291
|
-
|
|
@@ -62,6 +62,17 @@ constexpr int GATEWAY_DATA = 620; // Gateway data package for Internet rou
|
|
|
62
62
|
constexpr int GATEWAY_ACK = 621; // Gateway acknowledgment package
|
|
63
63
|
constexpr int GATEWAY_HEARTBEAT = 622; // Gateway heartbeat for health monitoring
|
|
64
64
|
|
|
65
|
+
// Message delivery confirmation types
|
|
66
|
+
constexpr int MESSAGE_ACK = 630; // Per-message delivery acknowledgment (issue #379)
|
|
67
|
+
|
|
68
|
+
// Outbound queue priority levels, in SentBuffer scheduling order (lower value
|
|
69
|
+
// drains first). PRIORITY_NORMAL is the wire default: a package whose priority
|
|
70
|
+
// equals it omits the "prio" key entirely.
|
|
71
|
+
constexpr uint8_t PRIORITY_CRITICAL = 0;
|
|
72
|
+
constexpr uint8_t PRIORITY_HIGH = 1;
|
|
73
|
+
constexpr uint8_t PRIORITY_NORMAL = 2;
|
|
74
|
+
constexpr uint8_t PRIORITY_LOW = 3;
|
|
75
|
+
|
|
65
76
|
class PackageInterface {
|
|
66
77
|
public:
|
|
67
78
|
virtual JsonObject addTo(JsonObject&& jsonObj) const = 0;
|
|
@@ -81,6 +92,15 @@ class Single : public PackageInterface {
|
|
|
81
92
|
uint32_t from;
|
|
82
93
|
uint32_t dest;
|
|
83
94
|
TSTRING msg = "";
|
|
95
|
+
// Non-zero when the sender requested a delivery acknowledgment. Only
|
|
96
|
+
// serialized when set, so plain sends carry zero overhead.
|
|
97
|
+
uint32_t msgId = 0;
|
|
98
|
+
// Outbound queue priority: 0=CRITICAL, 1=HIGH, 2=NORMAL, 3=LOW. Carried on
|
|
99
|
+
// the wire (as "prio") so intermediate hops re-enqueue a forwarded package
|
|
100
|
+
// at the sender's priority instead of silently dropping it to NORMAL after
|
|
101
|
+
// the first hop (issue #384). Only serialized when it deviates from NORMAL,
|
|
102
|
+
// so default sends carry zero overhead. Pre-2.0 receivers ignore the key.
|
|
103
|
+
uint8_t priority = PRIORITY_NORMAL;
|
|
84
104
|
|
|
85
105
|
Single() {}
|
|
86
106
|
Single(uint32_t fromID, uint32_t destID, TSTRING& message) {
|
|
@@ -93,6 +113,8 @@ class Single : public PackageInterface {
|
|
|
93
113
|
dest = jsonObj["dest"].as<uint32_t>();
|
|
94
114
|
from = jsonObj["from"].as<uint32_t>();
|
|
95
115
|
msg = jsonObj["msg"].as<TSTRING>();
|
|
116
|
+
msgId = jsonObj["msgId"] | (uint32_t)0;
|
|
117
|
+
priority = jsonObj["prio"] | PRIORITY_NORMAL;
|
|
96
118
|
}
|
|
97
119
|
|
|
98
120
|
JsonObject addTo(JsonObject&& jsonObj) const {
|
|
@@ -100,12 +122,16 @@ class Single : public PackageInterface {
|
|
|
100
122
|
jsonObj["dest"] = dest;
|
|
101
123
|
jsonObj["from"] = from;
|
|
102
124
|
jsonObj["msg"] = msg;
|
|
125
|
+
if (msgId != 0) jsonObj["msgId"] = msgId;
|
|
126
|
+
if (priority != PRIORITY_NORMAL) jsonObj["prio"] = priority;
|
|
103
127
|
return jsonObj;
|
|
104
128
|
}
|
|
105
129
|
|
|
106
130
|
#if ARDUINOJSON_VERSION_MAJOR < 7
|
|
107
131
|
size_t jsonObjectSize() const {
|
|
108
|
-
return JSON_OBJECT_SIZE(
|
|
132
|
+
return JSON_OBJECT_SIZE((msgId != 0 ? 5 : 4) +
|
|
133
|
+
(priority != PRIORITY_NORMAL ? 1 : 0)) +
|
|
134
|
+
ceil(1.1 * msg.length());
|
|
109
135
|
}
|
|
110
136
|
#endif
|
|
111
137
|
};
|
|
@@ -127,7 +153,9 @@ class Broadcast : public Single {
|
|
|
127
153
|
|
|
128
154
|
#if ARDUINOJSON_VERSION_MAJOR < 7
|
|
129
155
|
size_t jsonObjectSize() const {
|
|
130
|
-
return JSON_OBJECT_SIZE(
|
|
156
|
+
return JSON_OBJECT_SIZE((msgId != 0 ? 5 : 4) +
|
|
157
|
+
(priority != PRIORITY_NORMAL ? 1 : 0)) +
|
|
158
|
+
ceil(1.1 * msg.length());
|
|
131
159
|
}
|
|
132
160
|
#endif
|
|
133
161
|
};
|
|
@@ -717,6 +745,31 @@ class Variant {
|
|
|
717
745
|
*/
|
|
718
746
|
int type() { return jsonObj["type"].as<int>(); }
|
|
719
747
|
|
|
748
|
+
/**
|
|
749
|
+
* Origin node of the package (0 if not present)
|
|
750
|
+
*
|
|
751
|
+
* Lightweight field peek — unlike to<T>() this does not materialize a
|
|
752
|
+
* package object or copy the message payload.
|
|
753
|
+
*/
|
|
754
|
+
uint32_t from() { return jsonObj["from"] | (uint32_t)0; }
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* Delivery-confirmation id of the package (0 = no ack requested)
|
|
758
|
+
*
|
|
759
|
+
* Lightweight field peek — unlike to<T>() this does not materialize a
|
|
760
|
+
* package object or copy the message payload.
|
|
761
|
+
*/
|
|
762
|
+
uint32_t msgId() { return jsonObj["msgId"] | (uint32_t)0; }
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* Outbound queue priority of the package (PRIORITY_NORMAL if not present)
|
|
766
|
+
*
|
|
767
|
+
* Lightweight field peek — unlike to<T>() this does not materialize a
|
|
768
|
+
* package object or copy the message payload. Used by forwarding hops to
|
|
769
|
+
* re-enqueue a routed package at the sender's priority (issue #384).
|
|
770
|
+
*/
|
|
771
|
+
uint8_t priority() { return jsonObj["prio"] | PRIORITY_NORMAL; }
|
|
772
|
+
|
|
720
773
|
/**
|
|
721
774
|
* Package routing method
|
|
722
775
|
*/
|
|
@@ -17,18 +17,47 @@ namespace painlessmesh {
|
|
|
17
17
|
* Helper functions to route messages
|
|
18
18
|
*/
|
|
19
19
|
namespace router {
|
|
20
|
+
// Layouts are taken by const reference throughout this header: Layout<T>::subs
|
|
21
|
+
// is a std::list of shared_ptrs, so passing by value used to copy the whole
|
|
22
|
+
// connection list (one heap allocation per connection) on every packet sent,
|
|
23
|
+
// broadcast or forwarded (issue #387).
|
|
20
24
|
template <class T>
|
|
21
|
-
std::shared_ptr<T> findRoute(layout::Layout<T
|
|
25
|
+
std::shared_ptr<T> findRoute(const layout::Layout<T>& tree,
|
|
22
26
|
std::function<bool(std::shared_ptr<T>)> func) {
|
|
23
27
|
auto route = std::find_if(tree.subs.begin(), tree.subs.end(), func);
|
|
24
28
|
if (route == tree.subs.end()) return NULL;
|
|
25
29
|
return (*route);
|
|
26
30
|
}
|
|
27
31
|
|
|
32
|
+
/** The live connection through which nodeId is reachable, or NULL.
|
|
33
|
+
*
|
|
34
|
+
* A closed connection stays in subs until eraseClosedConnections() next
|
|
35
|
+
* runs. Routing a packet to it is a silent loss: the write is queued into
|
|
36
|
+
* a buffer nothing will ever drain, and the sender is told it succeeded.
|
|
37
|
+
* On the Alteriom HIL rig, correlating every unacknowledged delivery with
|
|
38
|
+
* the receiver's log showed the message had usually never arrived at all —
|
|
39
|
+
* 27 of 33 across three suites — which is this. A dead link is not a
|
|
40
|
+
* route, for any purpose; the liveness test is the one
|
|
41
|
+
* layout::syncLayout() already applies.
|
|
42
|
+
*/
|
|
28
43
|
template <class T>
|
|
29
|
-
std::shared_ptr<T> findRoute(layout::Layout<T
|
|
44
|
+
std::shared_ptr<T> findRoute(const layout::Layout<T>& tree, uint32_t nodeId) {
|
|
30
45
|
return findRoute<T>(tree, [nodeId](std::shared_ptr<T> s) {
|
|
31
|
-
return layout::contains((*s), nodeId);
|
|
46
|
+
return s->connected() && layout::contains((*s), nodeId);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** findRoute() for the duplicate-connection check in handleNodeSync().
|
|
51
|
+
*
|
|
52
|
+
* `exclude` drops the connection being judged, which cannot duplicate
|
|
53
|
+
* itself. Refusing a live direct connection on the authority of a dead
|
|
54
|
+
* route left a node with neither once the dead one was erased.
|
|
55
|
+
*/
|
|
56
|
+
template <class T>
|
|
57
|
+
std::shared_ptr<T> findLiveRoute(const layout::Layout<T>& tree, uint32_t nodeId,
|
|
58
|
+
std::shared_ptr<T> exclude = nullptr) {
|
|
59
|
+
return findRoute<T>(tree, [nodeId, exclude](std::shared_ptr<T> s) {
|
|
60
|
+
return s != exclude && s->connected() && layout::contains((*s), nodeId);
|
|
32
61
|
});
|
|
33
62
|
}
|
|
34
63
|
|
|
@@ -95,53 +124,49 @@ bool sendWithPriority(protocol::Variant&& variant, std::shared_ptr<U> conn, uint
|
|
|
95
124
|
return conn->addMessageWithPriority(msg, priorityLevel);
|
|
96
125
|
}
|
|
97
126
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if (conn) return conn->addMessage(msg);
|
|
105
|
-
return false;
|
|
106
|
-
}
|
|
127
|
+
// The layout-level send and broadcast functions below all funnel into the
|
|
128
|
+
// protocol::Variant& core overloads, which enqueue at the priority carried in
|
|
129
|
+
// the package's "prio" field (PRIORITY_NORMAL when absent). This is what keeps
|
|
130
|
+
// a sender's priority attached to a package across intermediate hops instead
|
|
131
|
+
// of silently dropping it to NORMAL after the first hop (issue #384): the
|
|
132
|
+
// forwarding path in routePackage() re-reads the field from the wire.
|
|
107
133
|
|
|
108
134
|
template <class U>
|
|
109
|
-
bool send(protocol::Variant& variant, layout::Layout<U
|
|
135
|
+
bool send(protocol::Variant& variant, const layout::Layout<U>& layout) {
|
|
110
136
|
TSTRING msg;
|
|
111
137
|
variant.printTo(msg);
|
|
112
138
|
auto conn = findRoute<U>(layout, variant.dest());
|
|
113
|
-
if (conn) return conn->
|
|
139
|
+
if (conn) return conn->addMessageWithPriority(msg, variant.priority());
|
|
114
140
|
return false;
|
|
115
141
|
}
|
|
116
142
|
|
|
117
143
|
template <class T, class U>
|
|
118
|
-
bool send(T
|
|
144
|
+
bool send(T& package, const layout::Layout<U>& layout) {
|
|
119
145
|
painlessmesh::protocol::Variant variant(package);
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
146
|
+
return send<U>(variant, layout);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
template <class T, class U>
|
|
150
|
+
bool send(T&& package, const layout::Layout<U>& layout) {
|
|
151
|
+
painlessmesh::protocol::Variant variant(package);
|
|
152
|
+
return send<U>(variant, layout);
|
|
125
153
|
}
|
|
126
154
|
|
|
127
155
|
template <class U>
|
|
128
|
-
bool send(protocol::Variant&& variant, layout::Layout<U
|
|
129
|
-
|
|
130
|
-
variant.printTo(msg);
|
|
131
|
-
auto conn = findRoute<U>(layout, variant.dest());
|
|
132
|
-
if (conn) return conn->addMessage(msg);
|
|
133
|
-
return false;
|
|
156
|
+
bool send(protocol::Variant&& variant, const layout::Layout<U>& layout) {
|
|
157
|
+
return send<U>(variant, layout);
|
|
134
158
|
}
|
|
135
159
|
|
|
136
|
-
template <class T
|
|
137
|
-
size_t broadcast(
|
|
138
|
-
|
|
160
|
+
template <class T>
|
|
161
|
+
size_t broadcast(protocol::Variant& variant, const layout::Layout<T>& layout,
|
|
162
|
+
uint32_t exclude) {
|
|
139
163
|
TSTRING msg;
|
|
140
164
|
variant.printTo(msg);
|
|
165
|
+
const auto priority = variant.priority();
|
|
141
166
|
size_t i = 0;
|
|
142
167
|
for (auto&& conn : layout.subs) {
|
|
143
168
|
if (conn->nodeId != 0 && conn->nodeId != exclude) {
|
|
144
|
-
auto sent = conn->
|
|
169
|
+
auto sent = conn->addMessageWithPriority(msg, priority);
|
|
145
170
|
if (sent) ++i;
|
|
146
171
|
}
|
|
147
172
|
}
|
|
@@ -149,53 +174,29 @@ size_t broadcast(T& package, layout::Layout<U> layout, uint32_t exclude) {
|
|
|
149
174
|
}
|
|
150
175
|
|
|
151
176
|
template <class T, class U>
|
|
152
|
-
size_t broadcast(T
|
|
177
|
+
size_t broadcast(T& package, const layout::Layout<U>& layout,
|
|
178
|
+
uint32_t exclude) {
|
|
153
179
|
painlessmesh::protocol::Variant variant(package);
|
|
154
|
-
|
|
155
|
-
variant.printTo(msg);
|
|
156
|
-
size_t i = 0;
|
|
157
|
-
for (auto&& conn : layout.subs) {
|
|
158
|
-
if (conn->nodeId != 0 && conn->nodeId != exclude) {
|
|
159
|
-
auto sent = conn->addMessage(msg);
|
|
160
|
-
if (sent) ++i;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
return i;
|
|
180
|
+
return broadcast<U>(variant, layout, exclude);
|
|
164
181
|
}
|
|
165
182
|
|
|
166
|
-
template <class T>
|
|
167
|
-
size_t broadcast(
|
|
183
|
+
template <class T, class U>
|
|
184
|
+
size_t broadcast(T&& package, const layout::Layout<U>& layout,
|
|
168
185
|
uint32_t exclude) {
|
|
169
|
-
|
|
170
|
-
variant
|
|
171
|
-
size_t i = 0;
|
|
172
|
-
for (auto&& conn : layout.subs) {
|
|
173
|
-
if (conn->nodeId != 0 && conn->nodeId != exclude) {
|
|
174
|
-
auto sent = conn->addMessage(msg);
|
|
175
|
-
if (sent) ++i;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
return i;
|
|
186
|
+
painlessmesh::protocol::Variant variant(package);
|
|
187
|
+
return broadcast<U>(variant, layout, exclude);
|
|
179
188
|
}
|
|
180
189
|
|
|
181
190
|
template <class T>
|
|
182
|
-
size_t broadcast(protocol::Variant&& variant, layout::Layout<T
|
|
191
|
+
size_t broadcast(protocol::Variant&& variant, const layout::Layout<T>& layout,
|
|
183
192
|
uint32_t exclude) {
|
|
184
|
-
|
|
185
|
-
variant.printTo(msg);
|
|
186
|
-
size_t i = 0;
|
|
187
|
-
for (auto&& conn : layout.subs) {
|
|
188
|
-
if (conn->nodeId != 0 && conn->nodeId != exclude) {
|
|
189
|
-
auto sent = conn->addMessage(msg);
|
|
190
|
-
if (sent) ++i;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
return i;
|
|
193
|
+
return broadcast<T>(variant, layout, exclude);
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
template <class T>
|
|
197
|
-
void routePackage(layout::Layout<T
|
|
198
|
-
const TSTRING& pkg,
|
|
197
|
+
void routePackage(const layout::Layout<T>& layout,
|
|
198
|
+
std::shared_ptr<T> connection, const TSTRING& pkg,
|
|
199
|
+
callback::MeshPackageCallbackList<T>& cbl,
|
|
199
200
|
uint32_t receivedAt) {
|
|
200
201
|
using namespace logger;
|
|
201
202
|
Log(COMMUNICATION, "routePackage(): Recvd from %u: %s\n", connection->nodeId,
|
|
@@ -287,15 +288,63 @@ void handleNodeSync(T& mesh, protocol::NodeTree newTree,
|
|
|
287
288
|
}
|
|
288
289
|
|
|
289
290
|
if (conn->newConnection) {
|
|
290
|
-
|
|
291
|
-
|
|
291
|
+
// The loop check is the tree the new node presents: if this node is
|
|
292
|
+
// anywhere in it, the new connection would close a cycle. That is the
|
|
293
|
+
// only thing a second route to the same node can legitimately mean —
|
|
294
|
+
// a station has exactly one uplink, so a node that arrives on a fresh
|
|
295
|
+
// direct connection with a tree that does not contain us has left
|
|
296
|
+
// wherever else we remember it. Refusing it as "already connected" on
|
|
297
|
+
// the authority of that memory used to hold a rebooted node out of
|
|
298
|
+
// the mesh until the neighbour whose tree still carried it timed the
|
|
299
|
+
// old link out: every AP in turn dropped it a second after the
|
|
300
|
+
// association, for 30 to 100 s per reboot, measured on the Alteriom
|
|
301
|
+
// HIL rig on every restart a suite performs.
|
|
302
|
+
if (layout::contains(newTree, mesh.getNodeId())) {
|
|
303
|
+
// This node in the presented tree is a cycle only if the presenter is
|
|
304
|
+
// also reachable from here through some other live connection — the
|
|
305
|
+
// two ends of the loop. Without that route it is the presenter's
|
|
306
|
+
// memory of where this node used to be, held in a branch its owner
|
|
307
|
+
// has not timed out yet: a newly promoted bridge listed the node that
|
|
308
|
+
// came to join it at the place it held before the promotion, and was
|
|
309
|
+
// refused as a loop on every attempt for the whole promotion window.
|
|
310
|
+
// Stale, the mention is dropped before the tree is taken.
|
|
311
|
+
auto otherRoute = router::findLiveRoute<U>(mesh, newTree.nodeId, conn);
|
|
312
|
+
if (otherRoute) {
|
|
313
|
+
Log(logger::SYNC,
|
|
314
|
+
"handleNodeSync(): %u's tree contains this node and %u is already "
|
|
315
|
+
"reachable through %u: a loop. Closing the new connection\n",
|
|
316
|
+
newTree.nodeId, newTree.nodeId, otherRoute->nodeId);
|
|
317
|
+
Log.remote("Loop through %u\n", newTree.nodeId);
|
|
318
|
+
conn->close();
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
292
321
|
Log(logger::SYNC,
|
|
293
|
-
"handleNodeSync():
|
|
294
|
-
"
|
|
322
|
+
"handleNodeSync(): %u's tree lists this node where it used to be; "
|
|
323
|
+
"stale, not a loop\n",
|
|
295
324
|
newTree.nodeId);
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
325
|
+
layout::forget(newTree, mesh.getNodeId());
|
|
326
|
+
}
|
|
327
|
+
// Whatever else still routes to this node is stale. A direct link to
|
|
328
|
+
// it is the one it had before it went away — TCP has not noticed yet —
|
|
329
|
+
// and closes now instead of at its timeout. A route through a
|
|
330
|
+
// neighbour is that neighbour's memory of the node's old place; the
|
|
331
|
+
// node is taken out of it here so packets go down the live link, and
|
|
332
|
+
// the neighbour's next sync brings its own tree up to date.
|
|
333
|
+
auto oldConnection = router::findLiveRoute<U>(mesh, newTree.nodeId, conn);
|
|
334
|
+
if (oldConnection) {
|
|
335
|
+
if (oldConnection->nodeId == newTree.nodeId) {
|
|
336
|
+
Log(logger::SYNC,
|
|
337
|
+
"handleNodeSync(): %u connected again while its old link is "
|
|
338
|
+
"still open; closing the old one\n",
|
|
339
|
+
newTree.nodeId);
|
|
340
|
+
oldConnection->close();
|
|
341
|
+
} else {
|
|
342
|
+
Log(logger::SYNC,
|
|
343
|
+
"handleNodeSync(): %u was reachable through %u; that place is "
|
|
344
|
+
"stale, the direct connection wins\n",
|
|
345
|
+
newTree.nodeId, oldConnection->nodeId);
|
|
346
|
+
layout::forget(*oldConnection, newTree.nodeId);
|
|
347
|
+
}
|
|
299
348
|
}
|
|
300
349
|
auto remoteNodeId = newTree.nodeId;
|
|
301
350
|
mesh.addTask([&mesh, remoteNodeId]() {
|
|
@@ -324,6 +373,74 @@ void handleNodeSync(T& mesh, protocol::NodeTree newTree,
|
|
|
324
373
|
conn->newConnection = false;
|
|
325
374
|
}
|
|
326
375
|
|
|
376
|
+
// What a neighbour presents is news only when it differs from what it
|
|
377
|
+
// presented last time. The cached tree can differ from a restated claim
|
|
378
|
+
// because a fresher neighbour has since taken a node out of it (below),
|
|
379
|
+
// and taking the restatement as news put the node back, marked the
|
|
380
|
+
// connection changed, forced the fresher neighbour's sync, which took it
|
|
381
|
+
// out again: a sync every 30 to 80 ms between the two for the 10 s it
|
|
382
|
+
// took the restating neighbour to time out the dead link behind its
|
|
383
|
+
// claim, on every board that heard both.
|
|
384
|
+
//
|
|
385
|
+
// (A neighbour listing a node that is on a direct link of ours is not
|
|
386
|
+
// pruned here, tempting as that is: it is also the shape a loop takes
|
|
387
|
+
// while the trees grow round it, and the loop checks above need to see
|
|
388
|
+
// it. The ring of five in the desktop integration suite never broke up
|
|
389
|
+
// with that pruning in place.)
|
|
390
|
+
auto fingerprint = layout::fingerprint(newTree);
|
|
391
|
+
bool restated = conn->presented == fingerprint;
|
|
392
|
+
conn->presented = fingerprint;
|
|
393
|
+
|
|
394
|
+
if (restated) {
|
|
395
|
+
conn->nodeSyncTask.delay();
|
|
396
|
+
mesh.stability += (std::min)(1000 - mesh.stability, (size_t)25);
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// A node is in one place, and a changed sync is the freshest word on
|
|
401
|
+
// every node below conn. Any other neighbour whose cached tree still
|
|
402
|
+
// lists one of them lists it where it used to be: on the rig every board
|
|
403
|
+
// carried a node twice — under the neighbour it had moved to and under
|
|
404
|
+
// the one it had left — and a message routed by the older copy never
|
|
405
|
+
// arrived, nor did the Internet request that went the same way. The
|
|
406
|
+
// older copies go now; their owners' next changed syncs agree.
|
|
407
|
+
for (auto&& other : mesh.subs) {
|
|
408
|
+
if (other == conn || other->nodeId == 0) continue;
|
|
409
|
+
size_t removed = layout::forgetAll(*other, newTree);
|
|
410
|
+
if (removed) {
|
|
411
|
+
Log(logger::SYNC,
|
|
412
|
+
"handleNodeSync(): %u nodes now under %u were still listed under "
|
|
413
|
+
"%u; forgotten there\n",
|
|
414
|
+
(unsigned)removed, conn->nodeId, other->nodeId);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// A node taken out of another neighbour's cache above can only come back
|
|
419
|
+
// through that neighbour's own sync, and a restatement is skipped. So
|
|
420
|
+
// when this neighbour stops presenting a node it used to — the node has
|
|
421
|
+
// moved on, or the claim was the stale one — the others' restatements
|
|
422
|
+
// are taken in full again: the change marks the connection changed,
|
|
423
|
+
// which forces their syncs, and whichever of them still lists the node
|
|
424
|
+
// has it back. Only a removal does this; an addition re-adopted this way
|
|
425
|
+
// would prune the presenter here, mark a change, force a sync there, and
|
|
426
|
+
// start the ping-pong over. Without the repair, the desktop time-sync
|
|
427
|
+
// scenario failed two runs in three: ntp::adopt() weighs the cached
|
|
428
|
+
// subtrees to choose which side keeps its clock, and a node missing from
|
|
429
|
+
// both sides' trees had the two ends disagree.
|
|
430
|
+
if (conn->nodeId != 0) {
|
|
431
|
+
bool dropped = false;
|
|
432
|
+
for (auto&& id : layout::asList(*conn, false)) {
|
|
433
|
+
if (!layout::contains(newTree, id)) {
|
|
434
|
+
dropped = true;
|
|
435
|
+
break;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
if (dropped) {
|
|
439
|
+
for (auto&& other : mesh.subs) {
|
|
440
|
+
if (other != conn) other->presented = 0;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
327
444
|
if (conn->updateSubs(newTree)) {
|
|
328
445
|
auto nodeId = newTree.nodeId;
|
|
329
446
|
mesh.addTask(
|
|
@@ -335,8 +452,8 @@ void handleNodeSync(T& mesh, protocol::NodeTree newTree,
|
|
|
335
452
|
}
|
|
336
453
|
|
|
337
454
|
template <class T, typename U>
|
|
338
|
-
callback::MeshPackageCallbackList<U
|
|
339
|
-
|
|
455
|
+
void addPackageCallback(callback::MeshPackageCallbackList<U>& callbackList,
|
|
456
|
+
T& mesh) {
|
|
340
457
|
// REQUEST type,
|
|
341
458
|
callbackList.onPackage(
|
|
342
459
|
protocol::NODE_SYNC_REQUEST,
|
|
@@ -359,8 +476,6 @@ callback::MeshPackageCallbackList<U> addPackageCallback(
|
|
|
359
476
|
connection->timeOutTask.disable();
|
|
360
477
|
return false;
|
|
361
478
|
});
|
|
362
|
-
|
|
363
|
-
return callbackList;
|
|
364
479
|
}
|
|
365
480
|
|
|
366
481
|
} // namespace router
|
package/src/painlessmesh/tcp.hpp
CHANGED
|
@@ -160,7 +160,17 @@ void initServer(AsyncServer &server, M &mesh) {
|
|
|
160
160
|
conn->initTasks();
|
|
161
161
|
mesh.subs.push_back(conn);
|
|
162
162
|
mesh.semaphoreGive();
|
|
163
|
+
return;
|
|
163
164
|
}
|
|
165
|
+
// The mesh was busy for the whole second the semaphore allows. The
|
|
166
|
+
// stack has accepted the client already; left like this it is
|
|
167
|
+
// neither the mesh's nor closed, and the peer waits on a link that
|
|
168
|
+
// will never carry a node sync. Say so, and close it, so the peer
|
|
169
|
+
// tries again at once rather than after its own timeout.
|
|
170
|
+
Log(ERROR,
|
|
171
|
+
"New AP connection refused: mesh busy, closing it so the peer "
|
|
172
|
+
"retries\n");
|
|
173
|
+
client->close(true);
|
|
164
174
|
},
|
|
165
175
|
NULL);
|
|
166
176
|
server.begin();
|