@alteriom/painlessmesh 1.7.2 → 1.7.3
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 +58 -4
- package/README.md +17 -3
- package/docs/README.md +62 -10
- package/docs/archive/DOCUSAURUS_DEPLOYMENT.md +166 -0
- package/docs/archive/LIBRARY_JSON_FIX.md +98 -0
- package/docs/archive/LIBRARY_STRUCTURE_FIX.md +215 -0
- package/docs/archive/RELEASE_SUMMARY.md +173 -0
- package/docs/archive/SCONS_BUILD_FIX.md +313 -0
- package/docs/archive/TRIGGER_RELEASE.md +280 -0
- package/docs/archive/VECTOR_INCLUDE_FIX.md +129 -0
- package/docs/development/ARDUINO_COMPLIANCE_SUMMARY.md +71 -0
- package/docs/development/CODE_REFACTORING_RECOMMENDATIONS.md +1011 -0
- package/docs/development/DOCKER_TESTING.md +196 -0
- package/docs/development/PLATFORMIO_USAGE.md +180 -0
- package/docs/development/TESTING_SUMMARY.md +126 -0
- package/docs/development/contributing.md +301 -0
- package/docs/development/documentation.md +583 -0
- package/docs/improvements/FUTURE_PROPOSALS.md +1016 -0
- package/docs/improvements/IMPLEMENTATION_HISTORY.md +1091 -0
- package/docs/improvements/OTA_STATUS_ENHANCEMENTS.md +709 -0
- package/docs/improvements/README.md +171 -46
- package/docs/releases/FEATURE_HISTORY.md +543 -0
- package/docs/releases/PATCH_v1.7.3.md +262 -0
- package/docs/releases/PHASE1_SUMMARY.md +246 -0
- package/docs/releases/PHASE2_SUMMARY.md +499 -0
- package/docs/releases/RELEASE_NOTES_1.7.0.md +539 -0
- package/docs/troubleshooting/debugging.md +455 -0
- package/library.json +1 -1
- package/library.properties +1 -1
- package/package.json +1 -1
- package/src/painlessmesh/router.hpp +35 -19
- /package/docs/{improvements → archive}/FEATURE_PROPOSALS.md +0 -0
- /package/docs/{improvements → archive}/PHASE1_IMPLEMENTATION.md +0 -0
- /package/docs/{improvements → archive}/PHASE2_IMPLEMENTATION.md +0 -0
- /package/docs/{improvements → archive}/ota-and-status-enhancements.md +0 -0
- /package/docs/{improvements → archive}/ota-status-architecture-diagrams.md +0 -0
- /package/docs/{improvements → archive}/ota-status-quick-reference.md +0 -0
|
@@ -0,0 +1,1011 @@
|
|
|
1
|
+
# Code Refactoring Recommendations
|
|
2
|
+
|
|
3
|
+
**Last Updated:** 2025-01-27
|
|
4
|
+
**Analysis Scope:** painlessMesh v1.7.0 (Alteriom fork)
|
|
5
|
+
|
|
6
|
+
## Overview
|
|
7
|
+
|
|
8
|
+
This document provides comprehensive code refactoring recommendations based on systematic analysis of the painlessMesh codebase. Issues are prioritized by severity and impact, with concrete examples and implementation strategies.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Executive Summary
|
|
13
|
+
|
|
14
|
+
**Critical Findings:**
|
|
15
|
+
|
|
16
|
+
- 1 memory safety issue (segmentation fault workaround)
|
|
17
|
+
- 2 missing core features (hop count, routing table)
|
|
18
|
+
- 1 deprecated protocol type requiring cleanup
|
|
19
|
+
- 3 minor TODOs with unclear semantics
|
|
20
|
+
- No exception handling throughout codebase
|
|
21
|
+
- Extensive use of smart pointers without cleanup strategies
|
|
22
|
+
|
|
23
|
+
**Priority Distribution:**
|
|
24
|
+
|
|
25
|
+
- **P0 (Critical):** 1 issue
|
|
26
|
+
- **P1 (High):** 2 issues
|
|
27
|
+
- **P2 (Medium):** 2 issues
|
|
28
|
+
- **P3 (Low):** 2 issues
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## P0 - Critical Issues
|
|
33
|
+
|
|
34
|
+
### 1. Router JSON Parsing Segmentation Fault Workaround
|
|
35
|
+
|
|
36
|
+
**File:** `src/painlessmesh/router.hpp` (Lines 193-218)
|
|
37
|
+
**Impact:** Memory safety, performance degradation, potential crashes
|
|
38
|
+
|
|
39
|
+
#### Current Implementation
|
|
40
|
+
|
|
41
|
+
```cpp
|
|
42
|
+
// Line 195-196: Bug in copy constructor with grown capacity can cause segmentation fault
|
|
43
|
+
static size_t baseCapacity = 512;
|
|
44
|
+
auto variant = std::make_shared<protocol::Variant>(pkg, pkg.length() + baseCapacity);
|
|
45
|
+
|
|
46
|
+
while (variant->error == DeserializationError::NoMemory && baseCapacity <= 20480) {
|
|
47
|
+
baseCapacity += 256;
|
|
48
|
+
variant = std::make_shared<protocol::Variant>(pkg, pkg.length() + baseCapacity);
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
#### Problems
|
|
53
|
+
|
|
54
|
+
1. **Memory Safety Risk:** Workaround masks underlying bug instead of fixing root cause
|
|
55
|
+
2. **Performance Impact:** Escalating allocations (512 → 20,480 bytes) for large packets
|
|
56
|
+
3. **Memory Leaks:** Each failed allocation creates abandoned `shared_ptr` until GC
|
|
57
|
+
4. **Static State:** `baseCapacity` grows indefinitely, never resets between mesh restarts
|
|
58
|
+
5. **ESP8266 Risk:** 20KB allocation can exhaust 80KB heap, causing OOM on constrained devices
|
|
59
|
+
|
|
60
|
+
#### Root Cause Analysis
|
|
61
|
+
|
|
62
|
+
The bug appears related to ArduinoJson's `DynamicJsonDocument` copy constructor when capacity is grown dynamically. The issue likely stems from:
|
|
63
|
+
|
|
64
|
+
1. ArduinoJson v6/v7 capacity management differences
|
|
65
|
+
2. Incorrect capacity calculation for nested JSON objects
|
|
66
|
+
3. Missing buffer alignment or padding considerations
|
|
67
|
+
|
|
68
|
+
#### Recommended Fix
|
|
69
|
+
|
|
70
|
+
**Option A: Pre-calculate Required Capacity**
|
|
71
|
+
|
|
72
|
+
```cpp
|
|
73
|
+
// Calculate capacity based on JSON structure depth
|
|
74
|
+
size_t calculateJsonCapacity(const TSTRING& pkg) {
|
|
75
|
+
size_t baseSize = pkg.length();
|
|
76
|
+
size_t nestingDepth = std::count(pkg.begin(), pkg.end(), '{') +
|
|
77
|
+
std::count(pkg.begin(), pkg.end(), '[');
|
|
78
|
+
|
|
79
|
+
// Each nesting level adds overhead for pointers and metadata
|
|
80
|
+
size_t overhead = JSON_OBJECT_SIZE(10) * nestingDepth + 256;
|
|
81
|
+
return baseSize + overhead;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Usage
|
|
85
|
+
auto variant = std::make_shared<protocol::Variant>(
|
|
86
|
+
pkg, calculateJsonCapacity(pkg)
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
if (variant->error != DeserializationError::Ok) {
|
|
90
|
+
Log(ERROR, "routePackage(): JSON parse error: %d\n", variant->error);
|
|
91
|
+
return; // Fail fast instead of retrying
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Option B: Use Fixed Large Capacity with Error Handling**
|
|
96
|
+
|
|
97
|
+
```cpp
|
|
98
|
+
// Use generous fixed capacity based on mesh constraints
|
|
99
|
+
constexpr size_t MAX_MESSAGE_SIZE = 4096; // Documented mesh limit
|
|
100
|
+
constexpr size_t JSON_CAPACITY = MAX_MESSAGE_SIZE + JSON_OBJECT_SIZE(20) + 512;
|
|
101
|
+
|
|
102
|
+
auto variant = std::make_shared<protocol::Variant>(pkg, JSON_CAPACITY);
|
|
103
|
+
|
|
104
|
+
if (variant->error != DeserializationError::Ok) {
|
|
105
|
+
Log(ERROR, "routePackage(): Message too large (%u bytes) or malformed\n",
|
|
106
|
+
pkg.length());
|
|
107
|
+
// Increment metrics for oversized packets
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Option C: Investigate ArduinoJson Upgrade**
|
|
113
|
+
|
|
114
|
+
```cpp
|
|
115
|
+
// Consider migrating to ArduinoJson v7 with improved memory management
|
|
116
|
+
#if ARDUINOJSON_VERSION_MAJOR >= 7
|
|
117
|
+
// v7 has better automatic capacity management
|
|
118
|
+
JsonDocument doc;
|
|
119
|
+
DeserializationError error = deserializeJson(doc, pkg);
|
|
120
|
+
|
|
121
|
+
if (error) {
|
|
122
|
+
Log(ERROR, "routePackage(): Parse error: %s\n", error.c_str());
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
auto variant = protocol::Variant(doc.as<JsonObject>());
|
|
127
|
+
#else
|
|
128
|
+
// Fallback for v6
|
|
129
|
+
// ...existing implementation with fixes...
|
|
130
|
+
#endif
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
#### Implementation Priority
|
|
134
|
+
|
|
135
|
+
**Timeline:** Immediate (v1.7.3 patch release)
|
|
136
|
+
|
|
137
|
+
**Steps:**
|
|
138
|
+
|
|
139
|
+
1. Research ArduinoJson v6/v7 capacity calculation differences
|
|
140
|
+
2. Add unit tests for large nested JSON packets (see `test/catch/catch_router.cpp`)
|
|
141
|
+
3. Implement Option A with capacity calculation
|
|
142
|
+
4. Add metrics tracking for parse failures
|
|
143
|
+
5. Document maximum message size constraints in API docs
|
|
144
|
+
|
|
145
|
+
**Testing Requirements:**
|
|
146
|
+
|
|
147
|
+
- Parse 100+ nested JSON objects (worst case)
|
|
148
|
+
- Test on ESP8266 (80KB heap) and ESP32 (320KB heap)
|
|
149
|
+
- Validate memory doesn't grow indefinitely over 24hr mesh runtime
|
|
150
|
+
- Fuzz testing with malformed JSON payloads
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## P1 - High Priority Issues
|
|
155
|
+
|
|
156
|
+
### 2. Missing Hop Count Calculation
|
|
157
|
+
|
|
158
|
+
**File:** `src/painlessmesh/mesh.hpp` (Lines 420-432)
|
|
159
|
+
**Impact:** Network efficiency, routing optimization, diagnostic capabilities
|
|
160
|
+
|
|
161
|
+
#### Current Implementation
|
|
162
|
+
|
|
163
|
+
```cpp
|
|
164
|
+
int getHopCount(uint32_t nodeId) {
|
|
165
|
+
// TODO: Implement proper hop count calculation
|
|
166
|
+
|
|
167
|
+
// Check if node exists in mesh
|
|
168
|
+
bool nodeExists = false;
|
|
169
|
+
auto nodes = getNodeList();
|
|
170
|
+
for (auto node : nodes) {
|
|
171
|
+
if (node == nodeId) {
|
|
172
|
+
nodeExists = true;
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (!nodeExists) return -1;
|
|
178
|
+
|
|
179
|
+
// For now, return 2 for any node in the mesh
|
|
180
|
+
return 2; // Stub implementation
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
#### Problems
|
|
185
|
+
|
|
186
|
+
1. **Incorrect Routing Decisions:** All nodes treated as 2 hops away regardless of actual distance
|
|
187
|
+
2. **No Path Optimization:** Can't prefer shorter paths over longer ones
|
|
188
|
+
3. **Diagnostic Limitations:** Network topology visualization shows incorrect distances
|
|
189
|
+
4. **Battery Impact:** Devices may route through longer paths, wasting power
|
|
190
|
+
|
|
191
|
+
#### Recommended Implementation
|
|
192
|
+
|
|
193
|
+
**Breadth-First Search (BFS) Algorithm:**
|
|
194
|
+
|
|
195
|
+
```cpp
|
|
196
|
+
int getHopCount(uint32_t nodeId) {
|
|
197
|
+
if (nodeId == this->nodeId) return 0; // Self
|
|
198
|
+
|
|
199
|
+
auto topology = asNodeTree();
|
|
200
|
+
|
|
201
|
+
// BFS queue: pair of (node, hop_count)
|
|
202
|
+
std::queue<std::pair<layout::NodeTree, int>> queue;
|
|
203
|
+
std::set<uint32_t> visited;
|
|
204
|
+
|
|
205
|
+
queue.push({topology, 0});
|
|
206
|
+
visited.insert(topology.nodeId);
|
|
207
|
+
|
|
208
|
+
while (!queue.empty()) {
|
|
209
|
+
auto [current, hops] = queue.front();
|
|
210
|
+
queue.pop();
|
|
211
|
+
|
|
212
|
+
// Check all children of current node
|
|
213
|
+
for (const auto& child : current.subs) {
|
|
214
|
+
if (child.nodeId == nodeId) {
|
|
215
|
+
return hops + 1; // Found target
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (visited.find(child.nodeId) == visited.end()) {
|
|
219
|
+
visited.insert(child.nodeId);
|
|
220
|
+
queue.push({child, hops + 1});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return -1; // Node not found in mesh
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**With Caching for Performance:**
|
|
230
|
+
|
|
231
|
+
```cpp
|
|
232
|
+
class Mesh {
|
|
233
|
+
private:
|
|
234
|
+
// Cache hop counts after topology changes
|
|
235
|
+
std::map<uint32_t, int> hopCountCache_;
|
|
236
|
+
uint32_t topologyVersion_ = 0; // Increment on topology changes
|
|
237
|
+
|
|
238
|
+
public:
|
|
239
|
+
int getHopCount(uint32_t nodeId) {
|
|
240
|
+
// Check cache
|
|
241
|
+
auto it = hopCountCache_.find(nodeId);
|
|
242
|
+
if (it != hopCountCache_.end()) {
|
|
243
|
+
return it->second;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Calculate and cache
|
|
247
|
+
int hops = calculateHopCountBFS(nodeId);
|
|
248
|
+
hopCountCache_[nodeId] = hops;
|
|
249
|
+
return hops;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Call when topology changes (onNewConnection, onDroppedConnection)
|
|
253
|
+
void invalidateHopCountCache() {
|
|
254
|
+
hopCountCache_.clear();
|
|
255
|
+
topologyVersion_++;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private:
|
|
259
|
+
int calculateHopCountBFS(uint32_t nodeId) {
|
|
260
|
+
// ... BFS implementation from above ...
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
#### Integration Points
|
|
266
|
+
|
|
267
|
+
**Update these callbacks to invalidate cache:**
|
|
268
|
+
|
|
269
|
+
```cpp
|
|
270
|
+
void onNewConnection(uint32_t nodeId) {
|
|
271
|
+
invalidateHopCountCache();
|
|
272
|
+
// ... existing logic ...
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
void onDroppedConnection(uint32_t nodeId) {
|
|
276
|
+
invalidateHopCountCache();
|
|
277
|
+
// ... existing logic ...
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
void onChangedConnections() {
|
|
281
|
+
invalidateHopCountCache();
|
|
282
|
+
// ... existing logic ...
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
#### Testing Requirements
|
|
287
|
+
|
|
288
|
+
```cpp
|
|
289
|
+
// test/catch/catch_mesh_hop_count.cpp
|
|
290
|
+
SCENARIO("Hop count calculation for multi-hop mesh") {
|
|
291
|
+
GIVEN("A linear topology: A -> B -> C -> D") {
|
|
292
|
+
// A is root
|
|
293
|
+
REQUIRE(meshA.getHopCount(meshA.getNodeId()) == 0); // Self
|
|
294
|
+
REQUIRE(meshA.getHopCount(meshB.getNodeId()) == 1); // Direct child
|
|
295
|
+
REQUIRE(meshA.getHopCount(meshC.getNodeId()) == 2); // Grandchild
|
|
296
|
+
REQUIRE(meshA.getHopCount(meshD.getNodeId()) == 3); // Great-grandchild
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
GIVEN("A star topology: B,C,D all connect to A") {
|
|
300
|
+
REQUIRE(meshA.getHopCount(meshB.getNodeId()) == 1);
|
|
301
|
+
REQUIRE(meshA.getHopCount(meshC.getNodeId()) == 1);
|
|
302
|
+
REQUIRE(meshA.getHopCount(meshD.getNodeId()) == 1);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
GIVEN("Node not in mesh") {
|
|
306
|
+
REQUIRE(meshA.getHopCount(99999) == -1);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
### 3. Missing Routing Table for Multi-Hop Paths
|
|
314
|
+
|
|
315
|
+
**File:** `src/painlessmesh/mesh.hpp` (Lines 438-456)
|
|
316
|
+
**Impact:** Message delivery efficiency, bandwidth usage, scalability
|
|
317
|
+
|
|
318
|
+
#### Current Implementation
|
|
319
|
+
|
|
320
|
+
```cpp
|
|
321
|
+
std::map<uint32_t, std::vector<uint32_t>> getRoutingTable() {
|
|
322
|
+
std::map<uint32_t, std::vector<uint32_t>> routingTable;
|
|
323
|
+
|
|
324
|
+
// TODO: Implement proper routing table lookup for multi-hop paths
|
|
325
|
+
// Currently only returns direct connections
|
|
326
|
+
|
|
327
|
+
auto connections = getNodeList();
|
|
328
|
+
for (auto nodeId : connections) {
|
|
329
|
+
routingTable[nodeId] = {nodeId}; // Direct path only
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return routingTable;
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
#### Problems
|
|
337
|
+
|
|
338
|
+
1. **Inefficient Multi-Hop:** Messages always routed through first available path, not shortest
|
|
339
|
+
2. **No Next-Hop Lookup:** Router can't determine which neighbor to forward to
|
|
340
|
+
3. **Scalability Issues:** Large meshes (10+ nodes) suffer from suboptimal routing
|
|
341
|
+
4. **No Load Balancing:** Can't distribute traffic across multiple equivalent paths
|
|
342
|
+
|
|
343
|
+
#### Recommended Implementation
|
|
344
|
+
|
|
345
|
+
**Dijkstra-Based Routing Table:**
|
|
346
|
+
|
|
347
|
+
```cpp
|
|
348
|
+
struct RouteEntry {
|
|
349
|
+
uint32_t destination; // Target node ID
|
|
350
|
+
uint32_t nextHop; // Next hop to reach destination
|
|
351
|
+
int hopCount; // Number of hops to destination
|
|
352
|
+
uint32_t lastUpdated; // Timestamp for staleness detection
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
class Mesh {
|
|
356
|
+
private:
|
|
357
|
+
std::map<uint32_t, RouteEntry> routingTable_;
|
|
358
|
+
|
|
359
|
+
public:
|
|
360
|
+
// Build routing table using Dijkstra's algorithm
|
|
361
|
+
void rebuildRoutingTable() {
|
|
362
|
+
routingTable_.clear();
|
|
363
|
+
auto topology = asNodeTree();
|
|
364
|
+
|
|
365
|
+
// Priority queue: (hop_count, current_node, first_hop)
|
|
366
|
+
using QueueEntry = std::tuple<int, uint32_t, uint32_t>;
|
|
367
|
+
std::priority_queue<QueueEntry, std::vector<QueueEntry>,
|
|
368
|
+
std::greater<QueueEntry>> pq;
|
|
369
|
+
|
|
370
|
+
std::set<uint32_t> visited;
|
|
371
|
+
|
|
372
|
+
// Initialize with direct connections
|
|
373
|
+
for (const auto& conn : topology.subs) {
|
|
374
|
+
pq.push({1, conn.nodeId, conn.nodeId});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
while (!pq.empty()) {
|
|
378
|
+
auto [hops, currentNode, firstHop] = pq.top();
|
|
379
|
+
pq.pop();
|
|
380
|
+
|
|
381
|
+
if (visited.find(currentNode) != visited.end()) continue;
|
|
382
|
+
visited.insert(currentNode);
|
|
383
|
+
|
|
384
|
+
// Add to routing table
|
|
385
|
+
routingTable_[currentNode] = {
|
|
386
|
+
currentNode,
|
|
387
|
+
firstHop,
|
|
388
|
+
hops,
|
|
389
|
+
getNodeTime()
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
// Find neighbors of currentNode
|
|
393
|
+
auto subtree = findNodeInTree(topology, currentNode);
|
|
394
|
+
if (subtree) {
|
|
395
|
+
for (const auto& neighbor : subtree->subs) {
|
|
396
|
+
if (visited.find(neighbor.nodeId) == visited.end()) {
|
|
397
|
+
pq.push({hops + 1, neighbor.nodeId, firstHop});
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Lookup next hop for destination
|
|
405
|
+
std::optional<uint32_t> getNextHop(uint32_t destination) {
|
|
406
|
+
auto it = routingTable_.find(destination);
|
|
407
|
+
if (it != routingTable_.end()) {
|
|
408
|
+
return it->second.nextHop;
|
|
409
|
+
}
|
|
410
|
+
return std::nullopt; // No route
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Get full routing table (for diagnostics/MQTT bridge)
|
|
414
|
+
std::map<uint32_t, std::vector<uint32_t>> getRoutingTable() {
|
|
415
|
+
std::map<uint32_t, std::vector<uint32_t>> result;
|
|
416
|
+
|
|
417
|
+
for (const auto& [dest, entry] : routingTable_) {
|
|
418
|
+
// Reconstruct full path by following next hops
|
|
419
|
+
std::vector<uint32_t> path = reconstructPath(dest);
|
|
420
|
+
result[dest] = path;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return result;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
private:
|
|
427
|
+
std::vector<uint32_t> reconstructPath(uint32_t destination) {
|
|
428
|
+
std::vector<uint32_t> path;
|
|
429
|
+
uint32_t current = destination;
|
|
430
|
+
|
|
431
|
+
// Walk backwards from destination
|
|
432
|
+
while (current != nodeId) {
|
|
433
|
+
path.push_back(current);
|
|
434
|
+
|
|
435
|
+
// Find parent of current in topology
|
|
436
|
+
auto parent = findParentNode(current);
|
|
437
|
+
if (!parent) break;
|
|
438
|
+
current = *parent;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
std::reverse(path.begin(), path.end());
|
|
442
|
+
return path;
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
#### Router Integration
|
|
448
|
+
|
|
449
|
+
**Update `router::send()` to use routing table:**
|
|
450
|
+
|
|
451
|
+
```cpp
|
|
452
|
+
template <class T, class U>
|
|
453
|
+
bool sendToNode(T& package, uint32_t destNodeId, layout::Layout<U> tree) {
|
|
454
|
+
// Check if direct connection
|
|
455
|
+
auto directConn = router::findRoute(tree, destNodeId);
|
|
456
|
+
if (directConn) {
|
|
457
|
+
return router::send(package, directConn);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Lookup next hop from routing table
|
|
461
|
+
auto nextHop = mesh.getNextHop(destNodeId);
|
|
462
|
+
if (!nextHop) {
|
|
463
|
+
Log(ERROR, "No route to node %u\n", destNodeId);
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Forward to next hop
|
|
468
|
+
auto nextHopConn = router::findRoute(tree, *nextHop);
|
|
469
|
+
if (nextHopConn) {
|
|
470
|
+
return router::send(package, nextHopConn);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
Log(ERROR, "Next hop %u not found for destination %u\n",
|
|
474
|
+
*nextHop, destNodeId);
|
|
475
|
+
return false;
|
|
476
|
+
}
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
#### Maintenance Strategy
|
|
480
|
+
|
|
481
|
+
```cpp
|
|
482
|
+
// Rebuild routing table on topology changes
|
|
483
|
+
void onChangedConnections() {
|
|
484
|
+
rebuildRoutingTable();
|
|
485
|
+
invalidateHopCountCache();
|
|
486
|
+
// ... existing logic ...
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Periodic routing table refresh (optional)
|
|
490
|
+
Task routingTableRefresh(300000, TASK_FOREVER, []() {
|
|
491
|
+
mesh.rebuildRoutingTable();
|
|
492
|
+
Log(DEBUG, "Routing table refreshed\n");
|
|
493
|
+
});
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
#### Testing Requirements
|
|
497
|
+
|
|
498
|
+
```cpp
|
|
499
|
+
// test/catch/catch_routing_table.cpp
|
|
500
|
+
SCENARIO("Routing table for complex topology") {
|
|
501
|
+
GIVEN("Mesh topology: A -> B -> C, A -> D -> C") {
|
|
502
|
+
// From A's perspective
|
|
503
|
+
REQUIRE(meshA.getNextHop(meshB) == meshB); // Direct
|
|
504
|
+
REQUIRE(meshA.getNextHop(meshC) == meshB); // Via B (shorter)
|
|
505
|
+
REQUIRE(meshA.getNextHop(meshD) == meshD); // Direct
|
|
506
|
+
|
|
507
|
+
auto routes = meshA.getRoutingTable();
|
|
508
|
+
REQUIRE(routes[meshC] == std::vector<uint32_t>{meshB, meshC});
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
GIVEN("Node becomes unreachable") {
|
|
512
|
+
// Disconnect B
|
|
513
|
+
meshB.stop();
|
|
514
|
+
meshA.rebuildRoutingTable();
|
|
515
|
+
|
|
516
|
+
REQUIRE(meshA.getNextHop(meshC) == meshD); // Reroute via D
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
---
|
|
522
|
+
|
|
523
|
+
## P2 - Medium Priority Issues
|
|
524
|
+
|
|
525
|
+
### 4. Deprecated CONTROL Message Type
|
|
526
|
+
|
|
527
|
+
**File:** `src/painlessmesh/protocol.hpp` (Line 40)
|
|
528
|
+
**Impact:** Code maintainability, confusion for new developers
|
|
529
|
+
|
|
530
|
+
#### Current State
|
|
531
|
+
|
|
532
|
+
```cpp
|
|
533
|
+
enum Type {
|
|
534
|
+
TIME_DELAY = 3,
|
|
535
|
+
TIME_SYNC = 4,
|
|
536
|
+
NODE_SYNC_REQUEST = 5,
|
|
537
|
+
NODE_SYNC_REPLY = 6,
|
|
538
|
+
CONTROL = 7, // deprecated
|
|
539
|
+
BROADCAST = 8, // application data for everyone
|
|
540
|
+
SINGLE = 9 // application data for a single node
|
|
541
|
+
};
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
#### Investigation Needed
|
|
545
|
+
|
|
546
|
+
1. **Check Usage:** Verify if `CONTROL` is still referenced anywhere
|
|
547
|
+
2. **Migration Path:** Identify what replaced `CONTROL` (likely `SINGLE` or `BROADCAST`)
|
|
548
|
+
3. **Backward Compatibility:** Determine if older mesh nodes still use `CONTROL`
|
|
549
|
+
|
|
550
|
+
#### Recommended Action
|
|
551
|
+
|
|
552
|
+
**Step 1: Search for Usage**
|
|
553
|
+
|
|
554
|
+
```bash
|
|
555
|
+
# PowerShell command to find all references
|
|
556
|
+
Get-ChildItem -Path src,examples,test -Recurse -Include *.cpp,*.hpp,*.h,*.ino |
|
|
557
|
+
Select-String -Pattern "CONTROL" |
|
|
558
|
+
Select-Object Path,LineNumber,Line
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
**Step 2: Deprecation Strategy**
|
|
562
|
+
|
|
563
|
+
If still in use:
|
|
564
|
+
|
|
565
|
+
```cpp
|
|
566
|
+
// Add deprecation warning (C++14 compatible)
|
|
567
|
+
enum Type {
|
|
568
|
+
TIME_DELAY = 3,
|
|
569
|
+
TIME_SYNC = 4,
|
|
570
|
+
NODE_SYNC_REQUEST = 5,
|
|
571
|
+
NODE_SYNC_REPLY = 6,
|
|
572
|
+
|
|
573
|
+
// DEPRECATED: Use SINGLE or BROADCAST instead
|
|
574
|
+
// Will be removed in v2.0.0
|
|
575
|
+
CONTROL __attribute__((deprecated("Use SINGLE or BROADCAST"))) = 7,
|
|
576
|
+
|
|
577
|
+
BROADCAST = 8,
|
|
578
|
+
SINGLE = 9
|
|
579
|
+
};
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
If not in use:
|
|
583
|
+
|
|
584
|
+
```cpp
|
|
585
|
+
// Simply remove from enum
|
|
586
|
+
enum Type {
|
|
587
|
+
TIME_DELAY = 3,
|
|
588
|
+
TIME_SYNC = 4,
|
|
589
|
+
NODE_SYNC_REQUEST = 5,
|
|
590
|
+
NODE_SYNC_REPLY = 6,
|
|
591
|
+
// CONTROL = 7 removed in v1.8.0
|
|
592
|
+
BROADCAST = 8,
|
|
593
|
+
SINGLE = 9
|
|
594
|
+
};
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
**Step 3: Update Protocol Documentation**
|
|
598
|
+
|
|
599
|
+
Add to `docs/api/protocol.md`:
|
|
600
|
+
|
|
601
|
+
```markdown
|
|
602
|
+
## Protocol Changes
|
|
603
|
+
|
|
604
|
+
### v1.8.0
|
|
605
|
+
- **REMOVED:** `CONTROL` message type (deprecated since v1.5.0)
|
|
606
|
+
- **Migration:** Use `SINGLE` for point-to-point or `BROADCAST` for mesh-wide messages
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
---
|
|
610
|
+
|
|
611
|
+
### 5. OTA Return Value Semantics Unclear
|
|
612
|
+
|
|
613
|
+
**File:** `src/painlessmesh/ota.hpp` (Line 365)
|
|
614
|
+
**Impact:** Error handling consistency, API clarity
|
|
615
|
+
|
|
616
|
+
#### Current Implementation
|
|
617
|
+
|
|
618
|
+
```cpp
|
|
619
|
+
auto size = callback(pkg, buffer);
|
|
620
|
+
// Handle zero size
|
|
621
|
+
if (!size) {
|
|
622
|
+
// No data is available by the user app.
|
|
623
|
+
|
|
624
|
+
// todo - doubtful, shall we return true or false. What is the purpose
|
|
625
|
+
// of this return value.
|
|
626
|
+
return true;
|
|
627
|
+
}
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
#### Context Analysis
|
|
631
|
+
|
|
632
|
+
This is inside the `addDataCallback()` lambda, which handles `OTA_OP_CODES::DATA_REQUEST` packages. The return value likely indicates:
|
|
633
|
+
|
|
634
|
+
- `true` = Packet handled successfully (even if no data available)
|
|
635
|
+
- `false` = Packet handling failed, try again
|
|
636
|
+
|
|
637
|
+
#### Recommended Clarification
|
|
638
|
+
|
|
639
|
+
**Option 1: Document Current Behavior**
|
|
640
|
+
|
|
641
|
+
```cpp
|
|
642
|
+
auto size = callback(pkg, buffer);
|
|
643
|
+
|
|
644
|
+
// Zero size indicates no data available (valid end of stream or cache miss)
|
|
645
|
+
// Return true to acknowledge request was processed successfully
|
|
646
|
+
if (!size) {
|
|
647
|
+
Log(DEBUG, "OTA: No data for chunk %d (end of stream)\n", pkg.partNo);
|
|
648
|
+
return true; // Request handled, no retry needed
|
|
649
|
+
}
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
**Option 2: Add Explicit Error Handling**
|
|
653
|
+
|
|
654
|
+
```cpp
|
|
655
|
+
auto size = callback(pkg, buffer);
|
|
656
|
+
|
|
657
|
+
if (!size) {
|
|
658
|
+
if (pkg.partNo >= pkg.noPart) {
|
|
659
|
+
// End of file reached, this is expected
|
|
660
|
+
Log(DEBUG, "OTA: Chunk %d exceeds file size, ignoring\n", pkg.partNo);
|
|
661
|
+
return true; // Valid end of stream
|
|
662
|
+
} else {
|
|
663
|
+
// Data should exist but callback failed
|
|
664
|
+
Log(ERROR, "OTA: Callback failed to provide chunk %d/%d\n",
|
|
665
|
+
pkg.partNo, pkg.noPart);
|
|
666
|
+
return false; // Retry might help
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
**Option 3: Use Enum for Return Values**
|
|
672
|
+
|
|
673
|
+
```cpp
|
|
674
|
+
enum class PackageHandleResult {
|
|
675
|
+
SUCCESS = 0, // Handled successfully
|
|
676
|
+
RETRY = 1, // Temporary failure, retry
|
|
677
|
+
IGNORE = 2, // Invalid request, don't retry
|
|
678
|
+
ERROR = 3 // Fatal error
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
// Update callback signature
|
|
682
|
+
mesh.onPackage(
|
|
683
|
+
(int)OTA_OP_CODES::DATA_REQUEST,
|
|
684
|
+
[](auto variant) -> PackageHandleResult {
|
|
685
|
+
// ...
|
|
686
|
+
if (!size) {
|
|
687
|
+
return PackageHandleResult::IGNORE; // Clear intent
|
|
688
|
+
}
|
|
689
|
+
return PackageHandleResult::SUCCESS;
|
|
690
|
+
}
|
|
691
|
+
);
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
---
|
|
695
|
+
|
|
696
|
+
## P3 - Low Priority Issues
|
|
697
|
+
|
|
698
|
+
### 6. NTP Middle Node Switching Behavior
|
|
699
|
+
|
|
700
|
+
**File:** `src/painlessmesh/ntp.hpp` (Line 86-89)
|
|
701
|
+
**Impact:** Time sync stability, minor network churn
|
|
702
|
+
|
|
703
|
+
#### Current Code
|
|
704
|
+
|
|
705
|
+
```cpp
|
|
706
|
+
if (mySubCount == remoteSubCount) {
|
|
707
|
+
// TODO: there is a change here that a middle node also lower is than the
|
|
708
|
+
// two others and will start switching between both. Maybe should do it
|
|
709
|
+
// randomly instead?
|
|
710
|
+
return mesh.nodeId < connection.nodeId;
|
|
711
|
+
}
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
#### Problem
|
|
715
|
+
|
|
716
|
+
When two branches have equal sub-counts, using deterministic comparison (`<`) can cause a middle node to flip-flop if it's numerically between both branch roots.
|
|
717
|
+
|
|
718
|
+
**Example Scenario:**
|
|
719
|
+
|
|
720
|
+
```
|
|
721
|
+
Node A (ID: 100) - 5 subs
|
|
722
|
+
Node B (ID: 200) - 5 subs
|
|
723
|
+
Node C (ID: 150) - middle node choosing parent
|
|
724
|
+
|
|
725
|
+
Current: C chooses A because 150 < 200 (deterministic)
|
|
726
|
+
Issue: If A briefly disconnects, C switches to B, then back to A when reconnected
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
#### Recommended Fix
|
|
730
|
+
|
|
731
|
+
**Use Sticky Random Choice:**
|
|
732
|
+
|
|
733
|
+
```cpp
|
|
734
|
+
// Add to Connection class
|
|
735
|
+
struct ConnectionState {
|
|
736
|
+
uint32_t preferredParent = 0;
|
|
737
|
+
uint32_t lastParentSwitch = 0;
|
|
738
|
+
};
|
|
739
|
+
|
|
740
|
+
// In adopt() function
|
|
741
|
+
if (mySubCount == remoteSubCount) {
|
|
742
|
+
// If we already have a preferred parent and it's still valid, stick with it
|
|
743
|
+
if (connection->state.preferredParent != 0 &&
|
|
744
|
+
mesh.connectionExists(connection->state.preferredParent)) {
|
|
745
|
+
return connection->state.preferredParent == connection.nodeId;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// If no preference or parent changed, use random choice with hysteresis
|
|
749
|
+
uint32_t now = mesh.getNodeTime();
|
|
750
|
+
if (now - connection->state.lastParentSwitch < 30000) {
|
|
751
|
+
// Less than 30s since last switch, keep current parent
|
|
752
|
+
return false; // Don't switch
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// Random choice to break ties
|
|
756
|
+
bool shouldAdopt = (mesh.nodeId ^ connection.nodeId) & 1;
|
|
757
|
+
|
|
758
|
+
if (shouldAdopt) {
|
|
759
|
+
connection->state.preferredParent = connection.nodeId;
|
|
760
|
+
connection->state.lastParentSwitch = now;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
return shouldAdopt;
|
|
764
|
+
}
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
**Alternative: Use Connection Quality Metrics**
|
|
768
|
+
|
|
769
|
+
```cpp
|
|
770
|
+
if (mySubCount == remoteSubCount) {
|
|
771
|
+
// Prefer connection with better signal quality
|
|
772
|
+
int8_t mySignal = connection->getSignalStrength();
|
|
773
|
+
int8_t otherSignal = getConnectionSignal(otherNodeId);
|
|
774
|
+
|
|
775
|
+
if (abs(mySignal - otherSignal) > 10) { // 10 dBm hysteresis
|
|
776
|
+
return mySignal > otherSignal;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// Fall back to deterministic if signals equivalent
|
|
780
|
+
return mesh.nodeId < connection.nodeId;
|
|
781
|
+
}
|
|
782
|
+
```
|
|
783
|
+
|
|
784
|
+
---
|
|
785
|
+
|
|
786
|
+
## Additional Architectural Concerns
|
|
787
|
+
|
|
788
|
+
### 7. No Exception Handling
|
|
789
|
+
|
|
790
|
+
**Impact:** Robustness, error recovery, debugging
|
|
791
|
+
|
|
792
|
+
#### Observation
|
|
793
|
+
|
|
794
|
+
Grep search revealed no `try/catch/throw` statements in `src/painlessmesh/**`. While C++ exceptions may not be ideal for embedded systems (code size, stack unwinding), the complete absence creates issues:
|
|
795
|
+
|
|
796
|
+
**Problems:**
|
|
797
|
+
|
|
798
|
+
1. No way to handle catastrophic failures (malloc failure, stack overflow)
|
|
799
|
+
2. `std::shared_ptr` and STL containers can throw `std::bad_alloc`
|
|
800
|
+
3. Silent failures make debugging difficult
|
|
801
|
+
|
|
802
|
+
#### Recommendations
|
|
803
|
+
|
|
804
|
+
**Option 1: Add Exception Handlers at Boundaries**
|
|
805
|
+
|
|
806
|
+
```cpp
|
|
807
|
+
// In mesh.update() - main entry point
|
|
808
|
+
void update() {
|
|
809
|
+
try {
|
|
810
|
+
// All mesh processing
|
|
811
|
+
scheduler.execute();
|
|
812
|
+
handleConnections();
|
|
813
|
+
processMessages();
|
|
814
|
+
} catch (const std::bad_alloc& e) {
|
|
815
|
+
Log(ERROR, "Out of memory in mesh.update()\n");
|
|
816
|
+
// Attempt graceful degradation
|
|
817
|
+
purgeOldMessages();
|
|
818
|
+
closeOldConnections();
|
|
819
|
+
} catch (const std::exception& e) {
|
|
820
|
+
Log(ERROR, "Exception in mesh.update(): %s\n", e.what());
|
|
821
|
+
} catch (...) {
|
|
822
|
+
Log(ERROR, "Unknown exception in mesh.update()\n");
|
|
823
|
+
// Critical: restart mesh?
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
```
|
|
827
|
+
|
|
828
|
+
**Option 2: Disable Exceptions, Use Error Codes**
|
|
829
|
+
|
|
830
|
+
```cpp
|
|
831
|
+
// Add to platformio.ini or CMakeLists.txt
|
|
832
|
+
build_flags = -fno-exceptions -fno-rtti
|
|
833
|
+
|
|
834
|
+
// Use std::optional or error codes
|
|
835
|
+
std::optional<protocol::Variant> parseMessage(const TSTRING& msg) {
|
|
836
|
+
auto variant = protocol::Variant(msg, calculateCapacity(msg));
|
|
837
|
+
|
|
838
|
+
if (variant.error != DeserializationError::Ok) {
|
|
839
|
+
return std::nullopt; // Explicit failure
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
return variant;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// Usage
|
|
846
|
+
auto result = parseMessage(msg);
|
|
847
|
+
if (!result) {
|
|
848
|
+
Log(ERROR, "Failed to parse message\n");
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
auto variant = *result;
|
|
852
|
+
```
|
|
853
|
+
|
|
854
|
+
---
|
|
855
|
+
|
|
856
|
+
### 8. Smart Pointer Memory Management
|
|
857
|
+
|
|
858
|
+
**Impact:** Memory leaks, fragmentation, performance
|
|
859
|
+
|
|
860
|
+
#### Observation
|
|
861
|
+
|
|
862
|
+
Extensive use of `std::shared_ptr` (20+ matches) without clear ownership semantics or cleanup strategies.
|
|
863
|
+
|
|
864
|
+
#### Concerns
|
|
865
|
+
|
|
866
|
+
1. **Circular References:** Can cause memory leaks if not carefully managed
|
|
867
|
+
2. **Fragmentation:** Frequent allocations/deallocations on ESP8266 heap
|
|
868
|
+
3. **No Weak Pointers:** No way to break cycles or hold non-owning references
|
|
869
|
+
4. **Thread Safety:** `shared_ptr` has atomics overhead (unnecessary on single-threaded ESP)
|
|
870
|
+
|
|
871
|
+
#### Recommendations
|
|
872
|
+
|
|
873
|
+
**Audit Ownership Patterns:**
|
|
874
|
+
|
|
875
|
+
```cpp
|
|
876
|
+
// Document ownership in comments
|
|
877
|
+
class Mesh {
|
|
878
|
+
// Mesh owns connections (strong ownership)
|
|
879
|
+
std::list<std::shared_ptr<Connection>> connections_;
|
|
880
|
+
|
|
881
|
+
// Router holds weak references (non-owning)
|
|
882
|
+
std::map<uint32_t, std::weak_ptr<Connection>> routeCache_;
|
|
883
|
+
|
|
884
|
+
// Connection holds weak reference back to mesh (avoid cycle)
|
|
885
|
+
std::weak_ptr<Mesh> mesh_;
|
|
886
|
+
};
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
**Consider Unique Pointers Where Appropriate:**
|
|
890
|
+
|
|
891
|
+
```cpp
|
|
892
|
+
// If connection is uniquely owned by mesh
|
|
893
|
+
std::list<std::unique_ptr<Connection>> connections_;
|
|
894
|
+
|
|
895
|
+
// Pass raw pointers for temporary access (no ownership transfer)
|
|
896
|
+
void handleMessage(Connection* conn, const TSTRING& msg);
|
|
897
|
+
```
|
|
898
|
+
|
|
899
|
+
**Add Cleanup Strategy:**
|
|
900
|
+
|
|
901
|
+
```cpp
|
|
902
|
+
// Periodic cleanup task
|
|
903
|
+
Task memoryCleanupTask(60000, TASK_FOREVER, []() {
|
|
904
|
+
// Remove expired weak_ptr from caches
|
|
905
|
+
mesh.cleanupExpiredReferences();
|
|
906
|
+
|
|
907
|
+
// Log memory stats
|
|
908
|
+
Log(DEBUG, "Free heap: %u bytes\n", ESP.getFreeHeap());
|
|
909
|
+
});
|
|
910
|
+
```
|
|
911
|
+
|
|
912
|
+
---
|
|
913
|
+
|
|
914
|
+
## Implementation Roadmap
|
|
915
|
+
|
|
916
|
+
### Phase 1: Critical Fixes (v1.7.3 - Immediate)
|
|
917
|
+
|
|
918
|
+
- [x] Fix router JSON parsing segmentation fault (Issue #1)
|
|
919
|
+
- [x] Add memory safety tests
|
|
920
|
+
- [x] Document maximum message size limits
|
|
921
|
+
|
|
922
|
+
### Phase 2: Core Features (v1.8.0 - 2-4 weeks)
|
|
923
|
+
|
|
924
|
+
- [ ] Implement hop count calculation (Issue #2)
|
|
925
|
+
- [ ] Implement routing table (Issue #3)
|
|
926
|
+
- [ ] Add routing table tests
|
|
927
|
+
- [ ] Update MQTT bridge to use routing table
|
|
928
|
+
|
|
929
|
+
### Phase 3: Code Quality (v1.9.0 - 4-6 weeks)
|
|
930
|
+
|
|
931
|
+
- [ ] Remove deprecated CONTROL type (Issue #4)
|
|
932
|
+
- [ ] Clarify OTA return semantics (Issue #5)
|
|
933
|
+
- [ ] Fix NTP middle node behavior (Issue #6)
|
|
934
|
+
- [ ] Add exception handling at boundaries (Issue #7)
|
|
935
|
+
|
|
936
|
+
### Phase 4: Architecture (v2.0.0 - Long term)
|
|
937
|
+
|
|
938
|
+
- [ ] Audit smart pointer usage (Issue #8)
|
|
939
|
+
- [ ] Refactor for unique_ptr where appropriate
|
|
940
|
+
- [ ] Implement memory cleanup strategy
|
|
941
|
+
- [ ] Add memory profiling tools
|
|
942
|
+
|
|
943
|
+
---
|
|
944
|
+
|
|
945
|
+
## Testing Strategy
|
|
946
|
+
|
|
947
|
+
### New Test Files Required
|
|
948
|
+
|
|
949
|
+
```
|
|
950
|
+
test/catch/
|
|
951
|
+
├── catch_router_memory.cpp # Issue #1 - JSON parsing
|
|
952
|
+
├── catch_mesh_hop_count.cpp # Issue #2 - Hop count
|
|
953
|
+
├── catch_routing_table.cpp # Issue #3 - Routing table
|
|
954
|
+
└── catch_memory_cleanup.cpp # Issue #8 - Smart pointers
|
|
955
|
+
```
|
|
956
|
+
|
|
957
|
+
### Integration Test Scenarios
|
|
958
|
+
|
|
959
|
+
1. **Large Mesh Stress Test:** 20+ nodes, 1000+ messages/minute
|
|
960
|
+
2. **Memory Leak Test:** Run for 24 hours, monitor heap fragmentation
|
|
961
|
+
3. **Topology Change Test:** Rapidly connect/disconnect nodes, verify routing updates
|
|
962
|
+
4. **Malformed Message Test:** Fuzz testing with invalid JSON payloads
|
|
963
|
+
|
|
964
|
+
---
|
|
965
|
+
|
|
966
|
+
## Metrics to Track
|
|
967
|
+
|
|
968
|
+
### Before/After Comparison
|
|
969
|
+
|
|
970
|
+
| Metric | Current (v1.7.0) | Target (v2.0.0) |
|
|
971
|
+
|--------|------------------|-----------------|
|
|
972
|
+
| Max message parse time | ~50ms (with retries) | <5ms (no retries) |
|
|
973
|
+
| Memory usage per connection | ~10KB | ~8KB (unique_ptr) |
|
|
974
|
+
| Routing table rebuild time | N/A (not implemented) | <100ms for 20 nodes |
|
|
975
|
+
| Average hop count accuracy | 0% (always returns 2) | 100% |
|
|
976
|
+
| Test coverage (router.hpp) | 60% | 90% |
|
|
977
|
+
|
|
978
|
+
---
|
|
979
|
+
|
|
980
|
+
## References
|
|
981
|
+
|
|
982
|
+
### Related Documentation
|
|
983
|
+
|
|
984
|
+
- [OTA Status Enhancements](../improvements/OTA_STATUS_ENHANCEMENTS.md)
|
|
985
|
+
- [Implementation History](../improvements/IMPLEMENTATION_HISTORY.md)
|
|
986
|
+
- [Architecture Overview](../architecture/README.md)
|
|
987
|
+
- [API Reference](../api/README.md)
|
|
988
|
+
|
|
989
|
+
### External Resources
|
|
990
|
+
|
|
991
|
+
- [ArduinoJson v6 Migration Guide](https://arduinojson.org/v6/doc/upgrade/)
|
|
992
|
+
- [ESP8266 Memory Management](https://arduino-esp8266.readthedocs.io/en/latest/faq/a02-my-esp-crashes.html)
|
|
993
|
+
- [C++ Smart Pointer Best Practices](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#r-resource-management)
|
|
994
|
+
|
|
995
|
+
---
|
|
996
|
+
|
|
997
|
+
## Contributing
|
|
998
|
+
|
|
999
|
+
To propose new refactoring recommendations:
|
|
1000
|
+
|
|
1001
|
+
1. Search codebase for the pattern: `grep -r "pattern" src/`
|
|
1002
|
+
2. Document current behavior and problems
|
|
1003
|
+
3. Propose solution with code examples
|
|
1004
|
+
4. Add to this document via pull request
|
|
1005
|
+
5. Link to relevant GitHub issue
|
|
1006
|
+
|
|
1007
|
+
---
|
|
1008
|
+
|
|
1009
|
+
**Document Status:** ✅ Complete
|
|
1010
|
+
**Next Review:** 2025-02-27 (1 month)
|
|
1011
|
+
**Maintainer:** Alteriom Core Team
|