@alteriom/painlessmesh 1.10.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BRIDGE_TO_INTERNET.md +167 -29
- package/CHANGELOG.md +542 -0
- package/CONTRIBUTING.md +56 -53
- package/README.md +102 -95
- package/RELEASE_GUIDE.md +94 -788
- 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/sendToInternet/platformio.ini +2 -2
- 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 +4 -4
- 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
|
@@ -18,6 +18,10 @@
|
|
|
18
18
|
// 3. A CommandPackage handler is included so a bridge node can
|
|
19
19
|
// request an immediate reading (command code 10).
|
|
20
20
|
//
|
|
21
|
+
// The two package headers beside this sketch are copies of the ones in
|
|
22
|
+
// examples/alteriom/: an Arduino sketch can only include files from its own
|
|
23
|
+
// directory or from a library, and a nested example is a sketch of its own.
|
|
24
|
+
//
|
|
21
25
|
// See alteriom_custom_package_template.hpp for a step-by-step
|
|
22
26
|
// guide to creating your own custom packages.
|
|
23
27
|
//************************************************************
|
|
@@ -123,9 +123,9 @@ print(df.groupby('node_id')['messages_received'].sum())
|
|
|
123
123
|
|
|
124
124
|
## Documentation
|
|
125
125
|
|
|
126
|
-
- [Simulator Documentation](
|
|
127
|
-
- [Integration Guide](
|
|
128
|
-
- [Configuration Reference](
|
|
126
|
+
- [Simulator Documentation](https://github.com/Alteriom/painlessMesh-simulator#readme)
|
|
127
|
+
- [Integration Guide](https://github.com/Alteriom/painlessMesh-simulator/blob/main/docs/INTEGRATING_INTO_YOUR_PROJECT.md)
|
|
128
|
+
- [Configuration Reference](https://github.com/Alteriom/painlessMesh-simulator/blob/main/docs/CONFIGURATION_GUIDE.md)
|
|
129
129
|
|
|
130
130
|
## Troubleshooting
|
|
131
131
|
|
|
@@ -77,7 +77,11 @@ Broadcasts occur:
|
|
|
77
77
|
- Periodically (default: every 30 seconds)
|
|
78
78
|
|
|
79
79
|
Regular nodes track these broadcasts and detect failures when:
|
|
80
|
-
-
|
|
80
|
+
- The bridge says it is leaving: `mesh.stop()` on a bridge (and a sketch that
|
|
81
|
+
stops the mesh before rebooting) broadcasts a last status with
|
|
82
|
+
`leaving: true`, and every node forgets that bridge at once
|
|
83
|
+
- No status received within 60 seconds (configurable timeout) — the case of a
|
|
84
|
+
bridge that lost power or its radio
|
|
81
85
|
- Bridge reports `internetConnected: false`
|
|
82
86
|
|
|
83
87
|
### 2. Election Trigger
|
|
@@ -91,10 +95,12 @@ An election is triggered when:
|
|
|
91
95
|
- Nodes without credentials remain passive
|
|
92
96
|
|
|
93
97
|
**Scenario 2: Bridge Failure**
|
|
94
|
-
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
-
|
|
98
|
+
- The bridge announces it is leaving: candidates check for another bridge
|
|
99
|
+
about a second later (or as soon as the startup period allows) and start
|
|
100
|
+
an election
|
|
101
|
+
- The bridge disappears without a word: no status received within 60 seconds
|
|
102
|
+
(configurable timeout), noticed at the next 30-second monitor tick
|
|
103
|
+
- The bridge reports `internetConnected: false`
|
|
98
104
|
|
|
99
105
|
### 3. Election Protocol
|
|
100
106
|
|
|
@@ -123,7 +129,8 @@ All nodes independently evaluate candidates using identical rules:
|
|
|
123
129
|
### 4. Failover Prevention
|
|
124
130
|
|
|
125
131
|
To prevent oscillation:
|
|
126
|
-
- Minimum 60 seconds between role changes
|
|
132
|
+
- Minimum 60 seconds between role changes; a check that arrives during the
|
|
133
|
+
hold is retried when the hold ends rather than dropped
|
|
127
134
|
- Split-brain prevention via state machine
|
|
128
135
|
- Deterministic winner selection ensures consensus
|
|
129
136
|
|
|
@@ -242,11 +249,20 @@ You can choose between two deployment modes:
|
|
|
242
249
|
|
|
243
250
|
**Scenario 2: Bridge Goes Offline**
|
|
244
251
|
1. Power off the current bridge node (initial or elected)
|
|
245
|
-
2.
|
|
252
|
+
2. Its last status ages out after 60 seconds, and the next 30-second
|
|
253
|
+
monitor tick notices: up to 90 seconds after the bridge went
|
|
246
254
|
3. Election starts automatically
|
|
247
255
|
4. Node with best router signal becomes new bridge
|
|
248
256
|
5. Monitor serial output to see election process
|
|
249
257
|
|
|
258
|
+
**Scenario 2b: Bridge Stops Cleanly**
|
|
259
|
+
1. Reboot the bridge through a sketch that calls `mesh.stop()` first (the
|
|
260
|
+
`bridge` example's regular-mode restart does), or call `mesh.stop()`
|
|
261
|
+
2. The bridge broadcasts `leaving: true`; every node forgets it at once
|
|
262
|
+
3. Candidates that are past their startup period hold the election
|
|
263
|
+
within seconds; the rig's failover test sees the backup promoted well
|
|
264
|
+
inside a 120-second window
|
|
265
|
+
|
|
250
266
|
**Scenario 3: Bridge Loses Internet**
|
|
251
267
|
1. Disconnect router from Internet (or block bridge node's Internet)
|
|
252
268
|
2. Bridge reports `internetConnected: false`
|
|
@@ -370,6 +386,11 @@ bool amBridge = mesh.isBridge();
|
|
|
370
386
|
}
|
|
371
387
|
```
|
|
372
388
|
|
|
389
|
+
A bridge that stops cleanly sends one last status with `"leaving": true`
|
|
390
|
+
and `"internetConnected": false` (2.0). Peers also read `routerChannel` from
|
|
391
|
+
every status as the channel the mesh is rooted on — their *home* — and go
|
|
392
|
+
back to it whenever they find themselves elsewhere.
|
|
393
|
+
|
|
373
394
|
### Type 611: BRIDGE_ELECTION
|
|
374
395
|
```json
|
|
375
396
|
{
|
|
@@ -393,10 +414,16 @@ bool amBridge = mesh.isBridge();
|
|
|
393
414
|
"previousBridge": 1234567890,
|
|
394
415
|
"reason": "Election winner - best router signal",
|
|
395
416
|
"routerRSSI": -35,
|
|
417
|
+
"routerChannel": 6,
|
|
396
418
|
"timestamp": 1609459400
|
|
397
419
|
}
|
|
398
420
|
```
|
|
399
421
|
|
|
422
|
+
`routerChannel` (2.0) is the channel the new bridge serves the mesh on;
|
|
423
|
+
peers move their AP and station to it a second after the takeover
|
|
424
|
+
propagates. A node that misses the message still finds the mesh by
|
|
425
|
+
scanning: after two empty scans (about 30 s) it looks at every channel.
|
|
426
|
+
|
|
400
427
|
## Use Cases
|
|
401
428
|
|
|
402
429
|
### 1. Fish Farm Monitoring System
|
|
@@ -630,13 +657,23 @@ mesh.setBridgeTimeout(30000); // 30 seconds
|
|
|
630
657
|
mesh.setBridgeStatusInterval(60000); // 60 seconds
|
|
631
658
|
```
|
|
632
659
|
|
|
633
|
-
##
|
|
634
|
-
|
|
635
|
-
- **
|
|
636
|
-
|
|
637
|
-
- **
|
|
638
|
-
|
|
639
|
-
|
|
660
|
+
## What to expect
|
|
661
|
+
|
|
662
|
+
- **A bridge that stops cleanly**: candidates elect within seconds of the
|
|
663
|
+
`leaving` status (after their startup period).
|
|
664
|
+
- **A bridge that loses power**: noticed after the bridge timeout (60 s)
|
|
665
|
+
plus up to one monitor tick (30 s), then a 1–3 s random delay and a 5 s
|
|
666
|
+
collection window before the winner promotes itself.
|
|
667
|
+
- **The mesh follows**: the takeover carries the new bridge's channel;
|
|
668
|
+
peers that hear it move at once, peers that miss it re-detect the channel
|
|
669
|
+
after about 30 s.
|
|
670
|
+
- **Validated on hardware**: the failover scenario (backup started beside a
|
|
671
|
+
live primary, primary rebooted into a regular node, backup promoted and
|
|
672
|
+
carrying a real HTTP request within 120 s) runs on the Alteriom rig with
|
|
673
|
+
an ESP32, ESP32-C3, ESP32-C5, ESP32-C6, ESP32-S3 and ESP8266 and a real
|
|
674
|
+
router, and is part of the release gate.
|
|
675
|
+
- **Memory**: each candidate adds about 12 bytes during an election; an
|
|
676
|
+
election broadcast is about 256 bytes per node.
|
|
640
677
|
|
|
641
678
|
## Dependencies
|
|
642
679
|
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
//************************************************************
|
|
2
|
+
// Command/control with delivery confirmation (issue #379)
|
|
3
|
+
//
|
|
4
|
+
// A controller node periodically broadcasts a command to all nodes and
|
|
5
|
+
// tracks, per node, whether the command was delivered. Nodes execute
|
|
6
|
+
// the command in their onReceive callback; the acknowledgment is sent
|
|
7
|
+
// automatically by the library.
|
|
8
|
+
//
|
|
9
|
+
// The broadcast ack overload requires includeSelf to be passed
|
|
10
|
+
// explicitly:
|
|
11
|
+
// mesh.sendBroadcast(msg, false, callback, timeoutMs);
|
|
12
|
+
// The callback fires once per mesh node (excluding this node).
|
|
13
|
+
//************************************************************
|
|
14
|
+
#include <painlessMesh.h>
|
|
15
|
+
|
|
16
|
+
#define MESH_SSID "whateverYouLike"
|
|
17
|
+
#define MESH_PASSWORD "somethingSneaky"
|
|
18
|
+
#define MESH_PORT 5555
|
|
19
|
+
|
|
20
|
+
// Define CONTROLLER on exactly one node to make it send the commands
|
|
21
|
+
// #define CONTROLLER
|
|
22
|
+
|
|
23
|
+
Scheduler userScheduler;
|
|
24
|
+
painlessMesh mesh;
|
|
25
|
+
|
|
26
|
+
uint32_t commandsSent = 0;
|
|
27
|
+
uint32_t nodesConfirmed = 0;
|
|
28
|
+
uint32_t nodesMissed = 0;
|
|
29
|
+
uint32_t resultsReceived = 0;
|
|
30
|
+
uint32_t expectedResults = 0;
|
|
31
|
+
|
|
32
|
+
void sendCommand();
|
|
33
|
+
Task taskSendCommand(TASK_SECOND * 15, TASK_FOREVER, &sendCommand);
|
|
34
|
+
|
|
35
|
+
void sendCommand() {
|
|
36
|
+
#ifdef CONTROLLER
|
|
37
|
+
String cmd = String("{\"cmd\":\"setInterval\",\"seconds\":") +
|
|
38
|
+
String(random(5, 60)) + String("}");
|
|
39
|
+
++commandsSent;
|
|
40
|
+
nodesConfirmed = 0;
|
|
41
|
+
nodesMissed = 0;
|
|
42
|
+
resultsReceived = 0;
|
|
43
|
+
expectedResults = mesh.getNodeList(false).size();
|
|
44
|
+
|
|
45
|
+
bool queued = mesh.sendBroadcast(
|
|
46
|
+
cmd, false,
|
|
47
|
+
[](uint32_t nodeId, bool delivered, uint32_t latencyMs) {
|
|
48
|
+
if (delivered) {
|
|
49
|
+
++nodesConfirmed;
|
|
50
|
+
Serial.printf("Node %u confirmed command in %u ms\n", nodeId,
|
|
51
|
+
latencyMs);
|
|
52
|
+
} else {
|
|
53
|
+
++nodesMissed;
|
|
54
|
+
Serial.printf("Node %u did NOT confirm — resend or alert\n",
|
|
55
|
+
nodeId);
|
|
56
|
+
// A real controller would retry with sendSingle(nodeId, ...)
|
|
57
|
+
}
|
|
58
|
+
++resultsReceived;
|
|
59
|
+
if (resultsReceived == expectedResults)
|
|
60
|
+
Serial.printf("Command %u done: %u confirmed, %u missed\n",
|
|
61
|
+
commandsSent, nodesConfirmed, nodesMissed);
|
|
62
|
+
},
|
|
63
|
+
5000);
|
|
64
|
+
|
|
65
|
+
if (!queued)
|
|
66
|
+
Serial.println("No nodes connected, command not sent");
|
|
67
|
+
#endif
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
void receivedCallback(uint32_t from, String &msg) {
|
|
71
|
+
// Executing the command counts as delivery; the ack is automatic
|
|
72
|
+
Serial.printf("Executing command from %u: %s\n", from, msg.c_str());
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
void setup() {
|
|
76
|
+
Serial.begin(115200);
|
|
77
|
+
|
|
78
|
+
mesh.setDebugMsgTypes(ERROR | STARTUP);
|
|
79
|
+
mesh.init(MESH_SSID, MESH_PASSWORD, &userScheduler, MESH_PORT);
|
|
80
|
+
mesh.onReceive(&receivedCallback);
|
|
81
|
+
|
|
82
|
+
userScheduler.addTask(taskSendCommand);
|
|
83
|
+
taskSendCommand.enable();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
void loop() { mesh.update(); }
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[platformio]
|
|
2
|
+
src_dir = .
|
|
3
|
+
|
|
4
|
+
[env]
|
|
5
|
+
lib_deps =
|
|
6
|
+
bblanchon/ArduinoJson
|
|
7
|
+
arkhipenko/TaskScheduler
|
|
8
|
+
|
|
9
|
+
lib_ldf_mode = deep+
|
|
10
|
+
[env:esp8266]
|
|
11
|
+
platform = espressif8266
|
|
12
|
+
board = nodemcuv2
|
|
13
|
+
framework = arduino
|
|
14
|
+
lib_extra_dirs = ../../ ; Load the local copy of painlessmesh. For your own example add painlessmesh to the lib_deps
|
|
15
|
+
lib_deps =
|
|
16
|
+
${env.lib_deps} ; Inherit common dependencies
|
|
17
|
+
esp32async/ESPAsyncTCP@^2.0.0 ; Only for ESP8266
|
|
18
|
+
|
|
19
|
+
[env:esp32]
|
|
20
|
+
platform = espressif32
|
|
21
|
+
board = esp32dev
|
|
22
|
+
framework = arduino
|
|
23
|
+
lib_extra_dirs = ../../ ; Load the local copy of painlessmesh. For your own example add painlessmesh to the lib_deps
|
|
24
|
+
lib_deps =
|
|
25
|
+
${env.lib_deps} ; Inherit common dependencies
|
|
26
|
+
esp32async/AsyncTCP
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
|
|
11
11
|
#include <Arduino.h>
|
|
12
12
|
#include <painlessMesh.h>
|
|
13
|
+
// Requires the "PubSubClient" library by Nick O'Leary. Arduino IDE users:
|
|
14
|
+
// install it via Library Manager (Tools > Manage Libraries... > search
|
|
15
|
+
// "PubSubClient"); PlatformIO users already get it automatically from this
|
|
16
|
+
// example's platformio.ini.
|
|
13
17
|
#include <PubSubClient.h>
|
|
14
18
|
#include <WiFiClient.h>
|
|
15
19
|
|
|
@@ -24,7 +24,11 @@
|
|
|
24
24
|
|
|
25
25
|
#ifdef ESP32
|
|
26
26
|
#else
|
|
27
|
-
|
|
27
|
+
// SD card chip-select. The D0..D8 aliases only exist on the NodeMCU/Wemos
|
|
28
|
+
// ESP8266 variants; the "generic" ESP8266 variant does not define them, so
|
|
29
|
+
// D8 fails to compile there. SS is GPIO15 on every ESP8266 variant, which is
|
|
30
|
+
// the exact pin D8 maps to on NodeMCU, so this is portable and equivalent.
|
|
31
|
+
#define CS_PIN SS
|
|
28
32
|
#endif
|
|
29
33
|
|
|
30
34
|
#include "painlessMesh.h"
|
|
@@ -269,6 +269,6 @@ This is by design - it ensures critical messages get through when you need them
|
|
|
269
269
|
|
|
270
270
|
## See Also
|
|
271
271
|
|
|
272
|
-
- [Message Queue](
|
|
272
|
+
- [Message Queue](../../BRIDGE_TO_INTERNET.md) - For offline message queueing, see the bridge guide and `mesh.enableMessageQueue()`
|
|
273
273
|
- [Basic Example](../basic/basic.ino) - Getting started with painlessMesh
|
|
274
274
|
- [Bridge Examples](../bridge/) - Internet connectivity patterns
|
|
@@ -26,7 +26,7 @@ Task taskSensorData(5000, TASK_FOREVER, []() {
|
|
|
26
26
|
String sensorData = "{\"type\":\"sensor\",\"temp\":23.5,\"humidity\":45}";
|
|
27
27
|
|
|
28
28
|
// Send with NORMAL priority (default)
|
|
29
|
-
mesh.sendBroadcast(sensorData, 2); // Priority
|
|
29
|
+
mesh.sendBroadcast(sensorData, (uint8_t)2, false); // Priority 2 = NORMAL
|
|
30
30
|
|
|
31
31
|
Serial.println("Sent sensor data (NORMAL priority)");
|
|
32
32
|
});
|
|
@@ -36,7 +36,7 @@ Task taskStatusUpdate(10000, TASK_FOREVER, []() {
|
|
|
36
36
|
String status = "{\"type\":\"status\",\"online\":true,\"uptime\":" + String(millis()) + "}";
|
|
37
37
|
|
|
38
38
|
// Send with HIGH priority
|
|
39
|
-
mesh.sendBroadcast(status, 1); // Priority
|
|
39
|
+
mesh.sendBroadcast(status, (uint8_t)1, false); // Priority 1 = HIGH
|
|
40
40
|
|
|
41
41
|
Serial.println("Sent status update (HIGH priority)");
|
|
42
42
|
});
|
|
@@ -46,7 +46,7 @@ Task taskDebugLog(15000, TASK_FOREVER, []() {
|
|
|
46
46
|
String debug = "{\"type\":\"debug\",\"freeHeap\":" + String(ESP.getFreeHeap()) + "}";
|
|
47
47
|
|
|
48
48
|
// Send with LOW priority
|
|
49
|
-
mesh.sendBroadcast(debug, 3); // Priority
|
|
49
|
+
mesh.sendBroadcast(debug, (uint8_t)3, false); // Priority 3 = LOW
|
|
50
50
|
|
|
51
51
|
Serial.println("Sent debug log (LOW priority)");
|
|
52
52
|
});
|
|
@@ -99,7 +99,7 @@ void sendCriticalAlarm(String alarmType, String message) {
|
|
|
99
99
|
String criticalMsg = "{\"type\":\"alarm\",\"alarm\":\"" + alarmType + "\",\"msg\":\"" + message + "\"}";
|
|
100
100
|
|
|
101
101
|
// Send with CRITICAL priority - will be sent immediately, bypassing queue
|
|
102
|
-
mesh.sendBroadcast(criticalMsg, 0); // Priority
|
|
102
|
+
mesh.sendBroadcast(criticalMsg, (uint8_t)0, false); // Priority 0 = CRITICAL
|
|
103
103
|
|
|
104
104
|
Serial.printf("CRITICAL ALARM SENT: %s - %s\n", alarmType.c_str(), message.c_str());
|
|
105
105
|
}
|
package/examples/priority/{priority_with_queue.ino → priority_with_queue/priority_with_queue.ino}
RENAMED
|
@@ -22,6 +22,24 @@
|
|
|
22
22
|
Scheduler userScheduler;
|
|
23
23
|
painlessMesh mesh;
|
|
24
24
|
|
|
25
|
+
// The queue types live in the painlessmesh namespace
|
|
26
|
+
using painlessmesh::MessagePriority;
|
|
27
|
+
using painlessmesh::QueueState;
|
|
28
|
+
using painlessmesh::PRIORITY_CRITICAL;
|
|
29
|
+
using painlessmesh::PRIORITY_HIGH;
|
|
30
|
+
using painlessmesh::PRIORITY_NORMAL;
|
|
31
|
+
using painlessmesh::PRIORITY_LOW;
|
|
32
|
+
using painlessmesh::QUEUE_EMPTY;
|
|
33
|
+
using painlessmesh::QUEUE_NORMAL;
|
|
34
|
+
using painlessmesh::QUEUE_75_PERCENT;
|
|
35
|
+
using painlessmesh::QUEUE_FULL;
|
|
36
|
+
|
|
37
|
+
// Forward declarations: Arduino's auto-prototype generation does not see
|
|
38
|
+
// past the global lambdas below
|
|
39
|
+
void sendMessage(String message, uint8_t priority);
|
|
40
|
+
void flushQueuedMessages();
|
|
41
|
+
void queueStateChanged(QueueState state, uint32_t count);
|
|
42
|
+
|
|
25
43
|
bool internetAvailable = false;
|
|
26
44
|
uint32_t bridgeNodeId = 0;
|
|
27
45
|
|
|
@@ -182,8 +200,8 @@ void flushQueuedMessages() {
|
|
|
182
200
|
delay(10);
|
|
183
201
|
}
|
|
184
202
|
|
|
185
|
-
Serial.printf("Queue flush complete. Remaining: %u messages\n",
|
|
186
|
-
mesh.
|
|
203
|
+
Serial.printf("Queue flush complete. Remaining: %u messages\n",
|
|
204
|
+
mesh.getQueueStats().currentSize);
|
|
187
205
|
}
|
|
188
206
|
|
|
189
207
|
void receivedCallback(uint32_t from, String& msg) {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[platformio]
|
|
2
|
+
src_dir = .
|
|
3
|
+
|
|
4
|
+
[env]
|
|
5
|
+
lib_deps =
|
|
6
|
+
bblanchon/ArduinoJson
|
|
7
|
+
arkhipenko/TaskScheduler
|
|
8
|
+
|
|
9
|
+
lib_ldf_mode = deep+
|
|
10
|
+
[env:esp8266]
|
|
11
|
+
platform = espressif8266
|
|
12
|
+
board = nodemcuv2
|
|
13
|
+
framework = arduino
|
|
14
|
+
lib_extra_dirs = ../../ ; Load the local copy of painlessmesh. For your own example add painlessmesh to the lib_deps
|
|
15
|
+
lib_deps =
|
|
16
|
+
${env.lib_deps} ; Inherit common dependencies
|
|
17
|
+
esp32async/ESPAsyncTCP@^2.0.0 ; Only for ESP8266
|
|
18
|
+
|
|
19
|
+
[env:esp32]
|
|
20
|
+
platform = espressif32
|
|
21
|
+
board = esp32dev
|
|
22
|
+
framework = arduino
|
|
23
|
+
lib_extra_dirs = ../../ ; Load the local copy of painlessmesh. For your own example add painlessmesh to the lib_deps
|
|
24
|
+
lib_deps =
|
|
25
|
+
${env.lib_deps} ; Inherit common dependencies
|
|
26
|
+
esp32async/AsyncTCP
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
//************************************************************
|
|
2
|
+
// Reliable sensor logging with delivery confirmation (issue #379)
|
|
3
|
+
//
|
|
4
|
+
// Demonstrates the per-message acknowledgment API:
|
|
5
|
+
// 1. every 10 seconds a sensor reading is sent to a gateway node
|
|
6
|
+
// 2. the reading stays in a small retry buffer until the gateway
|
|
7
|
+
// confirms delivery (delivered == true)
|
|
8
|
+
// 3. unconfirmed readings are retried up to MAX_ATTEMPTS times
|
|
9
|
+
//
|
|
10
|
+
// Run one node with GATEWAY defined and one or more sensor nodes. The
|
|
11
|
+
// gateway announces its node id; only sensor nodes generate readings.
|
|
12
|
+
//************************************************************
|
|
13
|
+
#include <painlessMesh.h>
|
|
14
|
+
|
|
15
|
+
#define MESH_SSID "whateverYouLike"
|
|
16
|
+
#define MESH_PASSWORD "somethingSneaky"
|
|
17
|
+
#define MESH_PORT 5555
|
|
18
|
+
|
|
19
|
+
// Define GATEWAY on exactly one node.
|
|
20
|
+
// #define GATEWAY
|
|
21
|
+
|
|
22
|
+
// How long to wait for a delivery confirmation
|
|
23
|
+
#define ACK_TIMEOUT_MS 3000
|
|
24
|
+
// How often a reading is retried before it is dropped
|
|
25
|
+
#define MAX_ATTEMPTS 3
|
|
26
|
+
|
|
27
|
+
Scheduler userScheduler;
|
|
28
|
+
painlessMesh mesh;
|
|
29
|
+
|
|
30
|
+
// The node we log readings to. In a real deployment you would discover
|
|
31
|
+
// this via a broadcast announcement or hardcode your gateway's node id.
|
|
32
|
+
uint32_t gatewayId = 0;
|
|
33
|
+
|
|
34
|
+
// A single buffered reading awaiting confirmation
|
|
35
|
+
struct PendingReading {
|
|
36
|
+
String payload;
|
|
37
|
+
uint8_t attempts = 0;
|
|
38
|
+
bool inFlight = false;
|
|
39
|
+
};
|
|
40
|
+
PendingReading pending;
|
|
41
|
+
|
|
42
|
+
void sendReading();
|
|
43
|
+
Task taskSendReading(TASK_SECOND * 10, TASK_FOREVER, &sendReading);
|
|
44
|
+
#ifdef GATEWAY
|
|
45
|
+
void announceGateway();
|
|
46
|
+
Task taskAnnounceGateway(TASK_SECOND * 5, TASK_FOREVER, &announceGateway);
|
|
47
|
+
#endif
|
|
48
|
+
|
|
49
|
+
void transmitPending() {
|
|
50
|
+
if (gatewayId == 0 || pending.payload.length() == 0 || pending.inFlight)
|
|
51
|
+
return;
|
|
52
|
+
|
|
53
|
+
pending.attempts++;
|
|
54
|
+
pending.inFlight = true;
|
|
55
|
+
bool queued = mesh.sendSingle(
|
|
56
|
+
gatewayId, pending.payload,
|
|
57
|
+
[](uint32_t nodeId, bool delivered, uint32_t latencyMs) {
|
|
58
|
+
pending.inFlight = false;
|
|
59
|
+
if (delivered) {
|
|
60
|
+
Serial.printf("Reading confirmed by %u in %u ms\n", nodeId,
|
|
61
|
+
latencyMs);
|
|
62
|
+
pending.payload = "";
|
|
63
|
+
pending.attempts = 0;
|
|
64
|
+
} else if (pending.attempts < MAX_ATTEMPTS) {
|
|
65
|
+
Serial.printf("No ack from %u, retrying (attempt %u)\n", nodeId,
|
|
66
|
+
pending.attempts + 1);
|
|
67
|
+
transmitPending();
|
|
68
|
+
} else {
|
|
69
|
+
Serial.printf("Dropping reading after %u attempts\n",
|
|
70
|
+
pending.attempts);
|
|
71
|
+
pending.payload = "";
|
|
72
|
+
pending.attempts = 0;
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
ACK_TIMEOUT_MS);
|
|
76
|
+
|
|
77
|
+
if (!queued) {
|
|
78
|
+
// No route to the gateway right now; try again on the next reading
|
|
79
|
+
pending.inFlight = false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
void sendReading() {
|
|
84
|
+
if (pending.inFlight) {
|
|
85
|
+
Serial.println("Previous reading still awaiting acknowledgment, skipping");
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (pending.payload.length() > 0) {
|
|
89
|
+
// A previous attempt had no route. Replace that unsent reading; a real
|
|
90
|
+
// application might queue multiple readings instead.
|
|
91
|
+
Serial.println("Previous unsent reading still pending, overwriting");
|
|
92
|
+
}
|
|
93
|
+
pending.payload =
|
|
94
|
+
String("{\"sensor\":\"temp\",\"value\":") + String(random(15, 30)) +
|
|
95
|
+
String(",\"node\":") + String(mesh.getNodeId()) + String("}");
|
|
96
|
+
pending.attempts = 0;
|
|
97
|
+
transmitPending();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
#ifdef GATEWAY
|
|
101
|
+
void announceGateway() {
|
|
102
|
+
mesh.sendBroadcast("GATEWAY_ANNOUNCE");
|
|
103
|
+
Serial.printf("Gateway announcement sent from %u\n", mesh.getNodeId());
|
|
104
|
+
}
|
|
105
|
+
#endif
|
|
106
|
+
|
|
107
|
+
void receivedCallback(uint32_t from, String &msg) {
|
|
108
|
+
#ifdef GATEWAY
|
|
109
|
+
// The gateway simply prints what it receives. The acknowledgment is
|
|
110
|
+
// sent automatically by the library — no application code needed.
|
|
111
|
+
Serial.printf("Gateway received from %u: %s\n", from, msg.c_str());
|
|
112
|
+
#else
|
|
113
|
+
if (msg == "GATEWAY_ANNOUNCE") {
|
|
114
|
+
gatewayId = from;
|
|
115
|
+
Serial.printf("Discovered gateway node %u\n", gatewayId);
|
|
116
|
+
}
|
|
117
|
+
#endif
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
void newConnectionCallback(uint32_t nodeId) {
|
|
121
|
+
Serial.printf("New connection: %u\n", nodeId);
|
|
122
|
+
#ifdef GATEWAY
|
|
123
|
+
// Announce immediately as well as periodically so new sensors do not
|
|
124
|
+
// wait for the next scheduled announcement.
|
|
125
|
+
announceGateway();
|
|
126
|
+
#endif
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
void setup() {
|
|
130
|
+
Serial.begin(115200);
|
|
131
|
+
|
|
132
|
+
mesh.setDebugMsgTypes(ERROR | STARTUP);
|
|
133
|
+
mesh.init(MESH_SSID, MESH_PASSWORD, &userScheduler, MESH_PORT);
|
|
134
|
+
mesh.onReceive(&receivedCallback);
|
|
135
|
+
mesh.onNewConnection(&newConnectionCallback);
|
|
136
|
+
|
|
137
|
+
#ifdef GATEWAY
|
|
138
|
+
userScheduler.addTask(taskAnnounceGateway);
|
|
139
|
+
taskAnnounceGateway.enable();
|
|
140
|
+
announceGateway();
|
|
141
|
+
#else
|
|
142
|
+
userScheduler.addTask(taskSendReading);
|
|
143
|
+
taskSendReading.enable();
|
|
144
|
+
#endif
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
void loop() {
|
|
148
|
+
// Ack timeouts are processed inside update() — no extra calls needed.
|
|
149
|
+
// mesh.pendingAcks() tells you how many messages are still unconfirmed.
|
|
150
|
+
mesh.update();
|
|
151
|
+
}
|
|
@@ -195,10 +195,17 @@ The callback provides `httpStatus` to indicate the result:
|
|
|
195
195
|
## Files
|
|
196
196
|
|
|
197
197
|
- `sendToInternet.ino` - Main example sketch for ESP32/ESP8266
|
|
198
|
-
- `mock_server_test.ino` - Bridge testing with mock HTTP server
|
|
199
|
-
- `pc_mesh_node.cpp` - **NEW:** PC-based mesh node for testing regular node → bridge flow
|
|
198
|
+
- `mock_server_test/mock_server_test.ino` - Bridge testing with mock HTTP server
|
|
199
|
+
- `pc_node/pc_mesh_node.cpp` - **NEW:** PC-based mesh node for testing regular node → bridge flow
|
|
200
200
|
- `README.md` - This documentation
|
|
201
|
-
- `PC_NODE_README.md` - **NEW:** Documentation for PC mesh node testing
|
|
201
|
+
- `pc_node/PC_NODE_README.md` - **NEW:** Documentation for PC mesh node testing
|
|
202
|
+
|
|
203
|
+
### Related examples
|
|
204
|
+
|
|
205
|
+
- [`mock_server_test/`](mock_server_test/) - Bridge testing against the mock
|
|
206
|
+
HTTP server. It lives in its own sketch folder because the Arduino toolchain
|
|
207
|
+
merges every `.ino` in a folder into one translation unit, so two sketches
|
|
208
|
+
side by side collide on `setup()`/`loop()`.
|
|
202
209
|
|
|
203
210
|
## Testing from Regular Nodes
|
|
204
211
|
|
|
@@ -214,7 +221,7 @@ The **PC Mesh Node** allows you to:
|
|
|
214
221
|
|
|
215
222
|
**Quick Start:**
|
|
216
223
|
```bash
|
|
217
|
-
cd examples/sendToInternet
|
|
224
|
+
cd examples/sendToInternet/pc_node
|
|
218
225
|
|
|
219
226
|
# Build
|
|
220
227
|
cmake . && make
|
|
@@ -223,7 +230,7 @@ cmake . && make
|
|
|
223
230
|
./pc_mesh_node 192.168.1.100 5555
|
|
224
231
|
```
|
|
225
232
|
|
|
226
|
-
**Full Documentation:** [PC_NODE_README.md](PC_NODE_README.md)
|
|
233
|
+
**Full Documentation:** [pc_node/PC_NODE_README.md](pc_node/PC_NODE_README.md)
|
|
227
234
|
|
|
228
235
|
**What It Tests:**
|
|
229
236
|
- Regular mesh node sending HTTP requests through bridge
|
|
@@ -26,17 +26,17 @@ SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
|
|
|
26
26
|
# Add executable
|
|
27
27
|
add_executable(pc_mesh_node
|
|
28
28
|
pc_mesh_node.cpp
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
../../../test/catch/fake_serial.cpp
|
|
30
|
+
../../../src/scheduler.cpp
|
|
31
31
|
)
|
|
32
32
|
|
|
33
33
|
# Include directories
|
|
34
34
|
target_include_directories(pc_mesh_node PUBLIC
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
../../../test/include/
|
|
36
|
+
../../../test/boost/
|
|
37
|
+
../../../test/ArduinoJson/src/
|
|
38
|
+
../../../test/TaskScheduler/src/
|
|
39
|
+
../../../src/
|
|
40
40
|
)
|
|
41
41
|
|
|
42
42
|
# Link libraries
|