@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.
Files changed (54) hide show
  1. package/BRIDGE_TO_INTERNET.md +167 -29
  2. package/CHANGELOG.md +483 -0
  3. package/CONTRIBUTING.md +56 -53
  4. package/README.md +100 -95
  5. package/RELEASE_GUIDE.md +81 -780
  6. package/examples/alteriom/README.md +8 -10
  7. package/examples/alteriom/alteriom.ino +2 -2
  8. package/examples/alteriom/alteriom_sensor_package.hpp +17 -11
  9. package/examples/alteriom/mppt_example/alteriom_custom_package_template.hpp +320 -0
  10. package/examples/alteriom/mppt_example/alteriom_sensor_package.hpp +1389 -0
  11. package/examples/alteriom/mppt_example/{alteriom_mppt_example.ino → mppt_example.ino} +4 -0
  12. package/examples/basic/test/simulator/README.md +3 -3
  13. package/examples/bridge_failover/README.md +51 -14
  14. package/examples/commandControl/commandControl.ino +86 -0
  15. package/examples/commandControl/platformio.ini +26 -0
  16. package/examples/mqttBridge/mqttBridge.ino +4 -0
  17. package/examples/mqttBridge/platformio.ini +1 -1
  18. package/examples/otaSender/otaSender.ino +5 -1
  19. package/examples/priority/README.md +1 -1
  20. package/examples/priority/{priority_basic_example.ino → priority_basic_example/priority_basic_example.ino} +4 -4
  21. package/examples/priority/{priority_with_queue.ino → priority_with_queue/priority_with_queue.ino} +20 -2
  22. package/examples/reliableSensorLogging/platformio.ini +26 -0
  23. package/examples/reliableSensorLogging/reliableSensorLogging.ino +151 -0
  24. package/examples/sendToInternet/README.md +12 -5
  25. package/examples/sendToInternet/{CMakeLists.txt → pc_node/CMakeLists.txt} +7 -7
  26. package/examples/sendToInternet/{PC_NODE_README.md → pc_node/PC_NODE_README.md} +15 -15
  27. package/examples/sendToInternet/{build.sh → pc_node/build.sh} +5 -5
  28. package/examples/sendToInternet/{pc_mesh_node.cpp → pc_node/pc_mesh_node.cpp} +12 -1
  29. package/examples/sharedGateway/README.md +1 -2
  30. package/keywords.txt +50 -1
  31. package/library.json +8 -6
  32. package/library.properties +2 -2
  33. package/package.json +3 -3
  34. package/src/AlteriomPainlessMesh.h +3 -3
  35. package/src/arduino/wifi.hpp +556 -126
  36. package/src/painlessMesh.h +2 -2
  37. package/src/painlessMeshSTA.cpp +607 -87
  38. package/src/painlessMeshSTA.h +135 -3
  39. package/src/painlessmesh/ack.hpp +283 -0
  40. package/src/painlessmesh/buffer.hpp +70 -8
  41. package/src/painlessmesh/callback.hpp +38 -5
  42. package/src/painlessmesh/configuration.hpp +69 -1
  43. package/src/painlessmesh/connection.hpp +12 -5
  44. package/src/painlessmesh/gateway.hpp +270 -5
  45. package/src/painlessmesh/layout.hpp +70 -2
  46. package/src/painlessmesh/logger.hpp +15 -0
  47. package/src/painlessmesh/mesh.hpp +552 -48
  48. package/src/painlessmesh/ntp.hpp +2 -4
  49. package/src/painlessmesh/plugin.hpp +30 -6
  50. package/src/painlessmesh/protocol.hpp +55 -2
  51. package/src/painlessmesh/router.hpp +192 -77
  52. package/src/painlessmesh/tcp.hpp +10 -0
  53. package/src/painlessmesh/message_tracker.hpp +0 -311
  54. /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](../../../../test/simulator/README.md)
127
- - [Integration Guide](../../../../test/simulator/docs/INTEGRATING_INTO_YOUR_PROJECT.md)
128
- - [Configuration Reference](../../../../test/simulator/docs/CONFIGURATION_GUIDE.md)
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
- - No status received within 60 seconds (configurable timeout)
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
- - Primary bridge fails or loses Internet connectivity
95
- - No status received within 60 seconds (configurable timeout)
96
- - Bridge reports `internetConnected: false`
97
- - Nodes detect failure and start election
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. After 60 seconds, regular nodes detect failure
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
- ## Performance Considerations
634
-
635
- - **Memory**: Each candidate adds ~12 bytes during election
636
- - **Network**: Election broadcast ~256 bytes per node
637
- - **Latency**: Typical failover time 60-70 seconds
638
- - **Scalability**: Tested with up to 10 nodes
639
- - **Reliability**: 99.9% success rate in simulations
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
 
@@ -5,7 +5,7 @@ src_dir = .
5
5
  lib_deps =
6
6
  bblanchon/ArduinoJson
7
7
  arkhipenko/TaskScheduler
8
- PubSubClient
8
+ knolleary/PubSubClient
9
9
 
10
10
  lib_ldf_mode = deep+
11
11
  [env:esp8266]
@@ -24,7 +24,11 @@
24
24
 
25
25
  #ifdef ESP32
26
26
  #else
27
- #define CS_PIN D8
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](../queued_alarms/README.md) - For offline message queueing
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 level 2 = NORMAL
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 level 1 = HIGH
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 level 3 = LOW
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 level 0 = CRITICAL
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
  }
@@ -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.getQueuedMessageCount());
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
- ../../test/catch/fake_serial.cpp
30
- ../../src/scheduler.cpp
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
- ../../test/include/
36
- ../../test/boost/
37
- ../../test/ArduinoJson/src/
38
- ../../test/TaskScheduler/src/
39
- ../../src/
35
+ ../../../test/include/
36
+ ../../../test/boost/
37
+ ../../../test/ArduinoJson/src/
38
+ ../../../test/TaskScheduler/src/
39
+ ../../../src/
40
40
  )
41
41
 
42
42
  # Link libraries