@alteriom/painlessmesh 1.8.13 → 1.8.15
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 +43 -0
- package/README.md +2 -0
- package/RELEASE_NOTES_1.8.15.md +160 -0
- package/RELEASE_READINESS_PLAN.md +323 -0
- package/TESTING_WITH_SIMULATOR.md +259 -0
- package/docs/BRIDGE_INITIALIZATION_FALLBACK.md +357 -0
- package/docs/SIMULATOR_TESTING.md +408 -0
- package/docs/troubleshooting/common-architecture-mistakes.md +438 -0
- package/docs/troubleshooting/common-issues.md +28 -0
- package/docs/troubleshooting/faq.md +113 -12
- package/docs/troubleshooting/internet-access-faq.md +299 -0
- package/examples/basic/test/simulator/CMakeLists.txt +40 -0
- package/examples/basic/test/simulator/README.md +149 -0
- package/examples/basic/test/simulator/firmware/basic_firmware.hpp +117 -0
- package/examples/basic/test/simulator/scenarios/basic_mesh_test.yaml +81 -0
- package/examples/bridge/bridge.ino +17 -4
- package/examples/bridge_failover/bridge_failover.ino +17 -3
- package/examples/multi_bridge/primary_bridge.ino +15 -3
- package/examples/multi_bridge/secondary_bridge.ino +15 -3
- package/library.json +1 -1
- package/library.properties +1 -1
- package/package.json +5 -2
- package/src/arduino/wifi.hpp +60 -9
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
# Internet Access in painlessMesh - Quick Reference
|
|
2
|
+
|
|
3
|
+
## Quick Answer
|
|
4
|
+
|
|
5
|
+
**Q: Why can't my mesh nodes access the internet?**
|
|
6
|
+
|
|
7
|
+
**A: Only the bridge node has internet access. Regular mesh nodes must forward data through the bridge.**
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
Internet
|
|
11
|
+
|
|
|
12
|
+
Router ← Your WiFi
|
|
13
|
+
|
|
|
14
|
+
Bridge Node ← Only this node can access internet
|
|
15
|
+
|
|
|
16
|
+
Mesh Network ← These nodes cannot access internet directly
|
|
17
|
+
/ | \
|
|
18
|
+
Node1 Node2 Node3
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Common Error Messages
|
|
22
|
+
|
|
23
|
+
If you see these errors on regular mesh nodes, you're trying to access the internet directly:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
[HTTPS] GET... failed, error: connection refused
|
|
27
|
+
WiFi.status() != WL_CONNECTED
|
|
28
|
+
HTTP request timeout
|
|
29
|
+
Connection failed
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Quick Fix
|
|
33
|
+
|
|
34
|
+
### ❌ Wrong Approach (Doesn't Work)
|
|
35
|
+
|
|
36
|
+
```cpp
|
|
37
|
+
// Regular mesh node trying to access internet
|
|
38
|
+
HTTPClient http;
|
|
39
|
+
http.begin("http://api.example.com/data");
|
|
40
|
+
http.POST(sensorData); // FAILS!
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### ✅ Correct Approach (Works)
|
|
44
|
+
|
|
45
|
+
```cpp
|
|
46
|
+
// Regular mesh node sends to bridge
|
|
47
|
+
mesh.sendSingle(bridgeNodeId, sensorData);
|
|
48
|
+
|
|
49
|
+
// Bridge node forwards to internet
|
|
50
|
+
void receivedCallback(uint32_t from, String& msg) {
|
|
51
|
+
if (WiFi.status() == WL_CONNECTED) {
|
|
52
|
+
HTTPClient http;
|
|
53
|
+
http.begin("http://api.example.com/data");
|
|
54
|
+
http.POST(msg); // WORKS!
|
|
55
|
+
http.end();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Architecture Patterns
|
|
61
|
+
|
|
62
|
+
### Pattern 1: Single Bridge (Basic)
|
|
63
|
+
|
|
64
|
+
One designated node is the bridge:
|
|
65
|
+
|
|
66
|
+
```cpp
|
|
67
|
+
// ==== BRIDGE NODE ====
|
|
68
|
+
mesh.initAsBridge(MESH_PREFIX, MESH_PASSWORD,
|
|
69
|
+
ROUTER_SSID, ROUTER_PASSWORD,
|
|
70
|
+
&userScheduler, 5555);
|
|
71
|
+
|
|
72
|
+
// ==== SENSOR NODES ====
|
|
73
|
+
mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, 5555);
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Pattern 2: Bridge Failover (High Availability)
|
|
77
|
+
|
|
78
|
+
Multiple nodes can become bridge automatically:
|
|
79
|
+
|
|
80
|
+
```cpp
|
|
81
|
+
// All nodes have router credentials
|
|
82
|
+
mesh.setRouterCredentials(ROUTER_SSID, ROUTER_PASSWORD);
|
|
83
|
+
mesh.enableBridgeFailover(true);
|
|
84
|
+
|
|
85
|
+
mesh.onBridgeRoleChanged(&bridgeRoleCallback);
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Pattern 3: Multi-Bridge (Load Balancing)
|
|
89
|
+
|
|
90
|
+
Multiple simultaneous bridges:
|
|
91
|
+
|
|
92
|
+
```cpp
|
|
93
|
+
// Primary bridge (priority 10)
|
|
94
|
+
mesh.initAsBridge(..., 10);
|
|
95
|
+
|
|
96
|
+
// Secondary bridge (priority 5)
|
|
97
|
+
mesh.initAsBridge(..., 5);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## By Use Case
|
|
101
|
+
|
|
102
|
+
### HTTP/HTTPS Requests
|
|
103
|
+
|
|
104
|
+
```cpp
|
|
105
|
+
// Bridge forwards HTTP requests
|
|
106
|
+
void receivedCallback(uint32_t from, String& msg) {
|
|
107
|
+
HTTPClient http;
|
|
108
|
+
http.begin("https://api.example.com/endpoint");
|
|
109
|
+
http.addHeader("Content-Type", "application/json");
|
|
110
|
+
http.POST(msg);
|
|
111
|
+
http.end();
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### MQTT Publishing
|
|
116
|
+
|
|
117
|
+
```cpp
|
|
118
|
+
// Bridge publishes to MQTT
|
|
119
|
+
#include "PubSubClient.h"
|
|
120
|
+
PubSubClient mqtt(wifiClient);
|
|
121
|
+
|
|
122
|
+
void receivedCallback(uint32_t from, String& msg) {
|
|
123
|
+
String topic = "mesh/sensor/" + String(from);
|
|
124
|
+
mqtt.publish(topic.c_str(), msg.c_str());
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### WhatsApp Notifications
|
|
129
|
+
|
|
130
|
+
```cpp
|
|
131
|
+
// Bridge sends WhatsApp messages
|
|
132
|
+
#include "Callmebot_ESP32.h"
|
|
133
|
+
Callmebot_ESP32 whatsapp;
|
|
134
|
+
|
|
135
|
+
void receivedCallback(uint32_t from, String& msg) {
|
|
136
|
+
DynamicJsonDocument doc(1024);
|
|
137
|
+
deserializeJson(doc, msg);
|
|
138
|
+
|
|
139
|
+
if (doc["alarm"] == true) {
|
|
140
|
+
String alert = "Alarm from sensor " + String(from);
|
|
141
|
+
whatsapp.sendMessage(alert);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Cloud Services (AWS, Azure, GCP)
|
|
147
|
+
|
|
148
|
+
```cpp
|
|
149
|
+
// Bridge forwards to cloud
|
|
150
|
+
void receivedCallback(uint32_t from, String& msg) {
|
|
151
|
+
// AWS IoT Core
|
|
152
|
+
awsClient.publish("iot/sensor/data", msg);
|
|
153
|
+
|
|
154
|
+
// Azure IoT Hub
|
|
155
|
+
azureClient.sendEvent(msg);
|
|
156
|
+
|
|
157
|
+
// Google Cloud IoT
|
|
158
|
+
googleClient.publishTelemetry(msg);
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Email Notifications
|
|
163
|
+
|
|
164
|
+
```cpp
|
|
165
|
+
// Bridge sends email
|
|
166
|
+
#include "ESP_Mail_Client.h"
|
|
167
|
+
|
|
168
|
+
void receivedCallback(uint32_t from, String& msg) {
|
|
169
|
+
SMTPData smtpData;
|
|
170
|
+
smtpData.setLogin(SMTP_HOST, SMTP_PORT, EMAIL, PASSWORD);
|
|
171
|
+
smtpData.setMessage("Sensor Alert", msg);
|
|
172
|
+
MailClient.sendMail(smtpData);
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Technical Explanation
|
|
177
|
+
|
|
178
|
+
### Why Only Bridge Has Internet?
|
|
179
|
+
|
|
180
|
+
ESP8266/ESP32 WiFi hardware operates on a single channel:
|
|
181
|
+
|
|
182
|
+
**Bridge Node (WIFI_AP_STA):**
|
|
183
|
+
- Creates mesh Access Point (AP) on channel X
|
|
184
|
+
- Connects to router Station (STA) on channel X
|
|
185
|
+
- Both on same channel = Internet access ✅
|
|
186
|
+
|
|
187
|
+
**Regular Node (WIFI_AP):**
|
|
188
|
+
- Creates mesh Access Point (AP) on channel X
|
|
189
|
+
- No router connection
|
|
190
|
+
- No internet access ❌
|
|
191
|
+
|
|
192
|
+
### Can I Make All Nodes Bridges?
|
|
193
|
+
|
|
194
|
+
Technically yes, but **not recommended** because:
|
|
195
|
+
|
|
196
|
+
1. **Memory overhead**: +5-10KB RAM per node
|
|
197
|
+
2. **Performance degradation**: All compete for router
|
|
198
|
+
3. **Router limits**: Max clients (typically 10-32)
|
|
199
|
+
4. **Power consumption**: Extra WiFi connection
|
|
200
|
+
5. **Loses mesh benefits**: Defeats purpose of mesh
|
|
201
|
+
|
|
202
|
+
## Complete Examples
|
|
203
|
+
|
|
204
|
+
### Sensor Network with Cloud Upload
|
|
205
|
+
|
|
206
|
+
**Bridge:**
|
|
207
|
+
```cpp
|
|
208
|
+
#include "painlessMesh.h"
|
|
209
|
+
#include "HTTPClient.h"
|
|
210
|
+
|
|
211
|
+
painlessMesh mesh;
|
|
212
|
+
|
|
213
|
+
void setup() {
|
|
214
|
+
mesh.initAsBridge(MESH_PREFIX, MESH_PASSWORD,
|
|
215
|
+
ROUTER_SSID, ROUTER_PASSWORD,
|
|
216
|
+
&userScheduler, 5555);
|
|
217
|
+
mesh.onReceive(&receivedCallback);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
void receivedCallback(uint32_t from, String& msg) {
|
|
221
|
+
if (WiFi.status() == WL_CONNECTED) {
|
|
222
|
+
HTTPClient http;
|
|
223
|
+
http.begin("https://cloud.example.com/api/sensor");
|
|
224
|
+
http.addHeader("Authorization", "Bearer " + API_KEY);
|
|
225
|
+
http.addHeader("Content-Type", "application/json");
|
|
226
|
+
|
|
227
|
+
int httpCode = http.POST(msg);
|
|
228
|
+
if (httpCode == HTTP_CODE_OK) {
|
|
229
|
+
Serial.println("Data uploaded to cloud");
|
|
230
|
+
}
|
|
231
|
+
http.end();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**Sensor Node:**
|
|
237
|
+
```cpp
|
|
238
|
+
#include "painlessMesh.h"
|
|
239
|
+
|
|
240
|
+
painlessMesh mesh;
|
|
241
|
+
#define BRIDGE_NODE_ID 1234567890
|
|
242
|
+
|
|
243
|
+
void setup() {
|
|
244
|
+
mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, 5555);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
void loop() {
|
|
248
|
+
mesh.update();
|
|
249
|
+
sendSensorData();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
void sendSensorData() {
|
|
253
|
+
static unsigned long lastSend = 0;
|
|
254
|
+
if (millis() - lastSend < 60000) return;
|
|
255
|
+
lastSend = millis();
|
|
256
|
+
|
|
257
|
+
String data = "{\"sensor\":\"temp\",\"value\":" +
|
|
258
|
+
String(readTemperature()) + ",\"nodeId\":" +
|
|
259
|
+
String(mesh.getNodeId()) + "}";
|
|
260
|
+
|
|
261
|
+
mesh.sendSingle(BRIDGE_NODE_ID, data);
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## Troubleshooting Checklist
|
|
266
|
+
|
|
267
|
+
- [ ] Verify which node is the bridge
|
|
268
|
+
- [ ] Check bridge has `initAsBridge()` or router credentials
|
|
269
|
+
- [ ] Confirm bridge shows `WiFi.status() == WL_CONNECTED`
|
|
270
|
+
- [ ] Verify regular nodes use `mesh.init()` without router
|
|
271
|
+
- [ ] Check sensor nodes send to bridge, not directly to internet
|
|
272
|
+
- [ ] Verify bridge forwards received messages to internet
|
|
273
|
+
- [ ] Test bridge internet connection with simple HTTP request
|
|
274
|
+
- [ ] Check serial output for connection errors
|
|
275
|
+
- [ ] Monitor memory usage on bridge node
|
|
276
|
+
- [ ] Verify firewall/router allows bridge's outbound connections
|
|
277
|
+
|
|
278
|
+
## More Information
|
|
279
|
+
|
|
280
|
+
- **[Common Architecture Mistakes](common-architecture-mistakes.md)** - Detailed guide
|
|
281
|
+
- **[BRIDGE_TO_INTERNET.md](../../BRIDGE_TO_INTERNET.md)** - Complete setup guide
|
|
282
|
+
- **[Bridge Failover](../BRIDGE_FAILOVER.md)** - High availability
|
|
283
|
+
- **[FAQ](faq.md)** - Common questions
|
|
284
|
+
- **[examples/mqttBridge/](../../examples/mqttBridge/)** - Working example
|
|
285
|
+
|
|
286
|
+
## Still Having Issues?
|
|
287
|
+
|
|
288
|
+
When asking for help, provide:
|
|
289
|
+
|
|
290
|
+
1. Which node is the bridge? (show setup code)
|
|
291
|
+
2. Which nodes are sensors? (show setup code)
|
|
292
|
+
3. Serial output from bridge (with debug enabled)
|
|
293
|
+
4. Serial output from sensor node (with debug enabled)
|
|
294
|
+
5. Complete error message
|
|
295
|
+
6. What you're trying to access (HTTP API, MQTT, etc.)
|
|
296
|
+
|
|
297
|
+
Post in:
|
|
298
|
+
- [GitHub Issues](https://github.com/Alteriom/painlessMesh/issues)
|
|
299
|
+
- [Community Forum](https://groups.google.com/forum/#!forum/painlessmesh-user)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
cmake_minimum_required(VERSION 3.10)
|
|
2
|
+
project(BasicExampleSimulatorTests)
|
|
3
|
+
|
|
4
|
+
set(CMAKE_CXX_STANDARD 14)
|
|
5
|
+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
6
|
+
|
|
7
|
+
# Path to the simulator (submodule)
|
|
8
|
+
set(SIMULATOR_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../../../../test/simulator")
|
|
9
|
+
|
|
10
|
+
# Include simulator's CMake configuration
|
|
11
|
+
if(EXISTS "${SIMULATOR_ROOT}/CMakeLists.txt")
|
|
12
|
+
# Add simulator as subdirectory
|
|
13
|
+
add_subdirectory(${SIMULATOR_ROOT} ${CMAKE_CURRENT_BINARY_DIR}/simulator)
|
|
14
|
+
else()
|
|
15
|
+
message(FATAL_ERROR "Simulator not found at ${SIMULATOR_ROOT}. Did you initialize the submodule?")
|
|
16
|
+
endif()
|
|
17
|
+
|
|
18
|
+
# Include painlessMesh library
|
|
19
|
+
set(PAINLESS_MESH_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../../../..")
|
|
20
|
+
include_directories(
|
|
21
|
+
${PAINLESS_MESH_ROOT}/src
|
|
22
|
+
${CMAKE_CURRENT_SOURCE_DIR}/firmware
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# Register our custom firmware with the simulator
|
|
26
|
+
# This allows the YAML scenarios to reference "basic_example" template
|
|
27
|
+
add_library(basic_example_firmware INTERFACE)
|
|
28
|
+
target_include_directories(basic_example_firmware INTERFACE
|
|
29
|
+
${CMAKE_CURRENT_SOURCE_DIR}/firmware
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Link with simulator
|
|
33
|
+
target_link_libraries(basic_example_firmware INTERFACE
|
|
34
|
+
painlessmesh_simulator
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# The simulator executable will be built by the simulator's CMakeLists.txt
|
|
38
|
+
# We just need to ensure our firmware headers are available
|
|
39
|
+
message(STATUS "Basic example firmware tests configured")
|
|
40
|
+
message(STATUS "Run tests with: ./painlessmesh-simulator --config scenarios/basic_mesh_test.yaml")
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Basic Example - Simulator Tests
|
|
2
|
+
|
|
3
|
+
This directory contains simulator-based tests for the `basic.ino` example using the [painlessMesh-simulator](https://github.com/Alteriom/painlessMesh-simulator).
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The simulator allows testing the basic example with 10+ virtual nodes without physical hardware, validating:
|
|
8
|
+
- Mesh formation with multiple nodes
|
|
9
|
+
- Message broadcasting to all nodes
|
|
10
|
+
- Callback functionality
|
|
11
|
+
- Dynamic node joining
|
|
12
|
+
- Time synchronization
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
### Prerequisites
|
|
17
|
+
|
|
18
|
+
Install dependencies (Ubuntu/Debian):
|
|
19
|
+
```bash
|
|
20
|
+
sudo apt-get install cmake ninja-build libboost-dev libboost-program-options-dev libyaml-cpp-dev
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Initialize Submodule
|
|
24
|
+
|
|
25
|
+
If not already done:
|
|
26
|
+
```bash
|
|
27
|
+
cd ../../../../test
|
|
28
|
+
git submodule update --init simulator
|
|
29
|
+
cd -
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Build and Run
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Build simulator
|
|
36
|
+
mkdir build && cd build
|
|
37
|
+
cmake -G Ninja ..
|
|
38
|
+
ninja
|
|
39
|
+
|
|
40
|
+
# Run test scenario
|
|
41
|
+
bin/painlessmesh-simulator --config ../scenarios/basic_mesh_test.yaml
|
|
42
|
+
|
|
43
|
+
# Or using the simulator from test/simulator
|
|
44
|
+
cd ../../../../test/simulator
|
|
45
|
+
mkdir -p build && cd build
|
|
46
|
+
cmake -G Ninja ..
|
|
47
|
+
ninja
|
|
48
|
+
bin/painlessmesh-simulator --config ../../../examples/basic/test/simulator/scenarios/basic_mesh_test.yaml
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Test Scenarios
|
|
52
|
+
|
|
53
|
+
### basic_mesh_test.yaml
|
|
54
|
+
|
|
55
|
+
Tests basic functionality with 10 nodes:
|
|
56
|
+
- ✓ Mesh formation
|
|
57
|
+
- ✓ Message broadcasting
|
|
58
|
+
- ✓ Callback triggering
|
|
59
|
+
- ✓ Dynamic node joining (node added at 30s)
|
|
60
|
+
- ✓ Time synchronization
|
|
61
|
+
|
|
62
|
+
**Expected Results:**
|
|
63
|
+
- All nodes connect within 30 seconds
|
|
64
|
+
- Each node receives 5+ messages within 60 seconds
|
|
65
|
+
- Time differences < 10ms after 45 seconds
|
|
66
|
+
|
|
67
|
+
## Firmware Adapter
|
|
68
|
+
|
|
69
|
+
The `firmware/basic_firmware.hpp` file wraps the `basic.ino` logic for simulation:
|
|
70
|
+
|
|
71
|
+
```cpp
|
|
72
|
+
#include "simulator/firmware/firmware_base.hpp"
|
|
73
|
+
|
|
74
|
+
class BasicFirmware : public FirmwareBase {
|
|
75
|
+
// Implements same logic as basic.ino
|
|
76
|
+
// - setup() initializes mesh and callbacks
|
|
77
|
+
// - loop() calls mesh.update()
|
|
78
|
+
// - sendMessage() broadcasts periodically
|
|
79
|
+
};
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
This allows running the **exact same code** that runs on hardware.
|
|
83
|
+
|
|
84
|
+
## Adding More Tests
|
|
85
|
+
|
|
86
|
+
Create new YAML scenarios in `scenarios/`:
|
|
87
|
+
|
|
88
|
+
```yaml
|
|
89
|
+
simulation:
|
|
90
|
+
name: "Stress Test"
|
|
91
|
+
duration: 120
|
|
92
|
+
|
|
93
|
+
nodes:
|
|
94
|
+
- template: "basic_example"
|
|
95
|
+
count: 50 # Test with 50 nodes
|
|
96
|
+
|
|
97
|
+
topology:
|
|
98
|
+
type: "ring" # Different topology
|
|
99
|
+
|
|
100
|
+
events:
|
|
101
|
+
- type: "network_partition" # Inject failures
|
|
102
|
+
time: 60
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Metrics and Validation
|
|
106
|
+
|
|
107
|
+
Test results are saved to `results/basic_test_results.csv`:
|
|
108
|
+
|
|
109
|
+
```csv
|
|
110
|
+
timestamp,node_id,messages_sent,messages_received,topology_changes
|
|
111
|
+
0,6481,0,0,1
|
|
112
|
+
1,6481,1,0,1
|
|
113
|
+
2,6481,1,2,1
|
|
114
|
+
...
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Analyze with:
|
|
118
|
+
```python
|
|
119
|
+
import pandas as pd
|
|
120
|
+
df = pd.read_csv('results/basic_test_results.csv')
|
|
121
|
+
print(df.groupby('node_id')['messages_received'].sum())
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Documentation
|
|
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)
|
|
129
|
+
|
|
130
|
+
## Troubleshooting
|
|
131
|
+
|
|
132
|
+
**Simulator not found:**
|
|
133
|
+
```bash
|
|
134
|
+
cd ../../../../test
|
|
135
|
+
git submodule update --init simulator
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Build errors:**
|
|
139
|
+
```bash
|
|
140
|
+
# Check dependencies
|
|
141
|
+
sudo apt-get install libboost-dev libyaml-cpp-dev
|
|
142
|
+
|
|
143
|
+
# Clean build
|
|
144
|
+
rm -rf build && mkdir build && cd build
|
|
145
|
+
cmake -G Ninja ..
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Test failures:**
|
|
149
|
+
Check the simulator output for details on which validation failed.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "simulator/firmware/firmware_base.hpp"
|
|
4
|
+
#include <painlessMesh.h>
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @brief Firmware adapter for basic.ino example
|
|
8
|
+
*
|
|
9
|
+
* This wraps the basic example sketch logic for testing with the simulator.
|
|
10
|
+
* It allows running the exact same code that runs on ESP32/ESP8266 hardware
|
|
11
|
+
* in a simulated environment with 100+ virtual nodes.
|
|
12
|
+
*/
|
|
13
|
+
class BasicFirmware : public FirmwareBase {
|
|
14
|
+
public:
|
|
15
|
+
BasicFirmware() : mesh_(nullptr), userScheduler_(nullptr) {}
|
|
16
|
+
|
|
17
|
+
~BasicFirmware() override {
|
|
18
|
+
if (taskSendMessage_) {
|
|
19
|
+
delete taskSendMessage_;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
void setup(painlessMesh* mesh, Scheduler* userScheduler) override {
|
|
24
|
+
mesh_ = mesh;
|
|
25
|
+
userScheduler_ = userScheduler;
|
|
26
|
+
|
|
27
|
+
// Same configuration as basic.ino
|
|
28
|
+
const char* MESH_PREFIX = "whateverYouLike";
|
|
29
|
+
const char* MESH_PASSWORD = "somethingSneaky";
|
|
30
|
+
const uint16_t MESH_PORT = 5555;
|
|
31
|
+
|
|
32
|
+
// Set debug message types (same as basic.ino)
|
|
33
|
+
mesh_->setDebugMsgTypes(ERROR | STARTUP | CONNECTION);
|
|
34
|
+
|
|
35
|
+
// Initialize mesh
|
|
36
|
+
mesh_->init(MESH_PREFIX, MESH_PASSWORD, userScheduler_, MESH_PORT);
|
|
37
|
+
|
|
38
|
+
// Register callbacks
|
|
39
|
+
mesh_->onReceive([this](uint32_t from, String& msg) {
|
|
40
|
+
this->receivedCallback(from, msg);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
mesh_->onNewConnection([this](uint32_t nodeId) {
|
|
44
|
+
this->newConnectionCallback(nodeId);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
mesh_->onChangedConnections([this]() {
|
|
48
|
+
this->changedConnectionCallback();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
mesh_->onNodeTimeAdjusted([this](int32_t offset) {
|
|
52
|
+
this->nodeTimeAdjustedCallback(offset);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Setup periodic message sending task
|
|
56
|
+
taskSendMessage_ = new Task(TASK_SECOND * 1, TASK_FOREVER,
|
|
57
|
+
[this]() { this->sendMessage(); });
|
|
58
|
+
userScheduler_->addTask(*taskSendMessage_);
|
|
59
|
+
taskSendMessage_->enable();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
void loop() override {
|
|
63
|
+
if (mesh_) {
|
|
64
|
+
mesh_->update();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const char* getName() const override {
|
|
69
|
+
return "BasicExample";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private:
|
|
73
|
+
painlessMesh* mesh_;
|
|
74
|
+
Scheduler* userScheduler_;
|
|
75
|
+
Task* taskSendMessage_ = nullptr;
|
|
76
|
+
|
|
77
|
+
void sendMessage() {
|
|
78
|
+
if (!mesh_) return;
|
|
79
|
+
|
|
80
|
+
String msg = "Hello from node ";
|
|
81
|
+
msg += mesh_->getNodeId();
|
|
82
|
+
mesh_->sendBroadcast(msg);
|
|
83
|
+
|
|
84
|
+
// Random interval between 1 and 5 seconds (same as basic.ino)
|
|
85
|
+
taskSendMessage_->setInterval(random(TASK_SECOND * 1, TASK_SECOND * 5));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
void receivedCallback(uint32_t from, String& msg) {
|
|
89
|
+
// In simulator, we can log to stdout or collect metrics
|
|
90
|
+
printf("Node %u: Received from %u msg=%s\n",
|
|
91
|
+
mesh_->getNodeId(), from, msg.c_str());
|
|
92
|
+
|
|
93
|
+
// Track metrics for validation
|
|
94
|
+
messagesReceived_++;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
void newConnectionCallback(uint32_t nodeId) {
|
|
98
|
+
printf("Node %u: New Connection, nodeId = %u\n",
|
|
99
|
+
mesh_->getNodeId(), nodeId);
|
|
100
|
+
connectionsEstablished_++;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
void changedConnectionCallback() {
|
|
104
|
+
printf("Node %u: Changed connections\n", mesh_->getNodeId());
|
|
105
|
+
topologyChanges_++;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
void nodeTimeAdjustedCallback(int32_t offset) {
|
|
109
|
+
printf("Node %u: Adjusted time %u. Offset = %d\n",
|
|
110
|
+
mesh_->getNodeId(), mesh_->getNodeTime(), offset);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Metrics for test validation
|
|
114
|
+
uint32_t messagesReceived_ = 0;
|
|
115
|
+
uint32_t connectionsEstablished_ = 0;
|
|
116
|
+
uint32_t topologyChanges_ = 0;
|
|
117
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Test scenario for basic.ino example
|
|
2
|
+
# This validates that the basic example works correctly with multiple nodes
|
|
3
|
+
|
|
4
|
+
simulation:
|
|
5
|
+
name: "Basic Example - Mesh Formation and Broadcasting"
|
|
6
|
+
description: "Validates basic.ino with 10 nodes forming a mesh and exchanging messages"
|
|
7
|
+
duration: 60 # seconds
|
|
8
|
+
time_scale: 1.0
|
|
9
|
+
seed: 12345
|
|
10
|
+
|
|
11
|
+
network:
|
|
12
|
+
default_latency:
|
|
13
|
+
min_ms: 1
|
|
14
|
+
max_ms: 10
|
|
15
|
+
default_packet_loss:
|
|
16
|
+
rate: 0.0
|
|
17
|
+
default_bandwidth:
|
|
18
|
+
bits_per_second: 1000000
|
|
19
|
+
|
|
20
|
+
nodes:
|
|
21
|
+
# Create individual nodes - simulator doesn't support template/count syntax
|
|
22
|
+
- id: "node1"
|
|
23
|
+
firmware: "SimpleBroadcast"
|
|
24
|
+
config:
|
|
25
|
+
mesh_prefix: "whateverYouLike"
|
|
26
|
+
mesh_password: "somethingSneaky"
|
|
27
|
+
mesh_port: 5555
|
|
28
|
+
broadcast_interval: "1000"
|
|
29
|
+
broadcast_message: "Hello from node"
|
|
30
|
+
|
|
31
|
+
- id: "node2"
|
|
32
|
+
firmware: "SimpleBroadcast"
|
|
33
|
+
config:
|
|
34
|
+
mesh_prefix: "whateverYouLike"
|
|
35
|
+
mesh_password: "somethingSneaky"
|
|
36
|
+
mesh_port: 5555
|
|
37
|
+
broadcast_interval: "1000"
|
|
38
|
+
broadcast_message: "Hello from node"
|
|
39
|
+
|
|
40
|
+
- id: "node3"
|
|
41
|
+
firmware: "SimpleBroadcast"
|
|
42
|
+
config:
|
|
43
|
+
mesh_prefix: "whateverYouLike"
|
|
44
|
+
mesh_password: "somethingSneaky"
|
|
45
|
+
mesh_port: 5555
|
|
46
|
+
broadcast_interval: "1000"
|
|
47
|
+
broadcast_message: "Hello from node"
|
|
48
|
+
|
|
49
|
+
- id: "node4"
|
|
50
|
+
firmware: "SimpleBroadcast"
|
|
51
|
+
config:
|
|
52
|
+
mesh_prefix: "whateverYouLike"
|
|
53
|
+
mesh_password: "somethingSneaky"
|
|
54
|
+
mesh_port: 5555
|
|
55
|
+
broadcast_interval: "1000"
|
|
56
|
+
broadcast_message: "Hello from node"
|
|
57
|
+
|
|
58
|
+
- id: "node5"
|
|
59
|
+
firmware: "SimpleBroadcast"
|
|
60
|
+
config:
|
|
61
|
+
mesh_prefix: "whateverYouLike"
|
|
62
|
+
mesh_password: "somethingSneaky"
|
|
63
|
+
mesh_port: 5555
|
|
64
|
+
broadcast_interval: "1000"
|
|
65
|
+
broadcast_message: "Hello from node"
|
|
66
|
+
|
|
67
|
+
topology:
|
|
68
|
+
type: "mesh" # Full mesh - all nodes connected
|
|
69
|
+
|
|
70
|
+
# No events - nodes start automatically
|
|
71
|
+
events: []
|
|
72
|
+
|
|
73
|
+
# Metrics to collect
|
|
74
|
+
metrics:
|
|
75
|
+
output: "basic_test_results.csv"
|
|
76
|
+
interval: 5
|
|
77
|
+
collect:
|
|
78
|
+
- "messages_sent"
|
|
79
|
+
- "messages_received"
|
|
80
|
+
- "bytes_sent"
|
|
81
|
+
- "bytes_received"
|