@alteriom/painlessmesh 1.9.1 → 1.9.2
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 +12 -0
- package/README.md +1 -1
- package/examples/bridge_failover/README.md +50 -0
- package/examples/bridge_failover/bridge_failover.ino +26 -1
- package/examples/sendToInternet/README.md +158 -0
- package/examples/sendToInternet/platformio.ini +23 -0
- package/examples/sendToInternet/sendToInternet.ino +340 -0
- package/library.json +1 -1
- package/library.properties +1 -1
- package/package.json +1 -1
- package/src/arduino/wifi.hpp +24 -1
- package/src/painlessmesh/mesh.hpp +30 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.9.2] - 2025-12-01
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **Release Version Update** - Consolidated release across all distribution channels
|
|
13
|
+
- Synchronized version numbers in library.properties, library.json, and package.json
|
|
14
|
+
- Ensures consistent versioning for NPM, PlatformIO, and Arduino Library Manager
|
|
15
|
+
|
|
16
|
+
### Documentation
|
|
17
|
+
|
|
18
|
+
- **README Update** - Updated version banner to reflect 1.9.2 release
|
|
19
|
+
|
|
8
20
|
## [1.9.1] - 2025-12-01
|
|
9
21
|
|
|
10
22
|
### Added
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
<div align="center">
|
|
6
6
|
|
|
7
|
-
**Version 1.9.
|
|
7
|
+
**Version 1.9.2** - Consolidated release across all distribution channels
|
|
8
8
|
|
|
9
9
|
[](https://github.com/Alteriom/painlessMesh/actions/workflows/ci.yml)
|
|
10
10
|
[](https://github.com/Alteriom/painlessMesh/actions/workflows/docs.yml)
|
|
@@ -2,6 +2,56 @@
|
|
|
2
2
|
|
|
3
3
|
This example demonstrates automatic bridge failover with RSSI-based election in painlessMesh networks.
|
|
4
4
|
|
|
5
|
+
## ⚠️ Important: Understanding Internet Connectivity
|
|
6
|
+
|
|
7
|
+
**`hasInternetConnection()` indicates a GATEWAY has Internet access, NOT that this node can make HTTP requests!**
|
|
8
|
+
|
|
9
|
+
Regular mesh nodes do NOT have direct IP routing to the Internet. They only communicate via the painlessMesh protocol (node-to-node messages). When `hasInternetConnection()` returns `true`, it means a bridge/gateway node in the mesh has Internet access.
|
|
10
|
+
|
|
11
|
+
**To send data to the Internet from a regular mesh node, you must:**
|
|
12
|
+
|
|
13
|
+
1. **Use `sendToInternet()`** - Routes data through a gateway node
|
|
14
|
+
2. **Use `initAsSharedGateway()`** - Configures all nodes with direct router access (requires router credentials - see below)
|
|
15
|
+
3. **Send mesh messages to the bridge** - Bridge node handles Internet communication
|
|
16
|
+
|
|
17
|
+
**DON'T do this on regular mesh nodes:**
|
|
18
|
+
```cpp
|
|
19
|
+
// This will FAIL with "connection refused" on regular mesh nodes!
|
|
20
|
+
HTTPClient http;
|
|
21
|
+
http.begin("https://api.example.com");
|
|
22
|
+
int httpCode = http.GET(); // FAILS!
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**DO this instead:**
|
|
26
|
+
```cpp
|
|
27
|
+
// Check if a gateway is available, then use sendToInternet()
|
|
28
|
+
if (mesh.hasInternetConnection()) {
|
|
29
|
+
mesh.sendToInternet(
|
|
30
|
+
"https://api.example.com/data",
|
|
31
|
+
jsonPayload,
|
|
32
|
+
[](bool success, uint16_t status, String error) {
|
|
33
|
+
Serial.printf("Delivery: %s\n", success ? "OK" : error.c_str());
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
For use cases where all nodes need direct Internet access, see the [sharedGateway example](../sharedGateway/).
|
|
40
|
+
|
|
41
|
+
**Note on `initAsSharedGateway()` usage:**
|
|
42
|
+
```cpp
|
|
43
|
+
// initAsSharedGateway() requires ROUTER credentials since all nodes connect to router
|
|
44
|
+
// Signature: initAsSharedGateway(meshSSID, meshPassword, ROUTER_SSID, ROUTER_PASSWORD, scheduler, port)
|
|
45
|
+
mesh.initAsSharedGateway(
|
|
46
|
+
MESH_PREFIX, // Mesh network name
|
|
47
|
+
MESH_PASSWORD, // Mesh network password
|
|
48
|
+
ROUTER_SSID, // Your WiFi router SSID (required!)
|
|
49
|
+
ROUTER_PASSWORD, // Your WiFi router password (required!)
|
|
50
|
+
&userScheduler, // Task scheduler
|
|
51
|
+
MESH_PORT // TCP port for mesh
|
|
52
|
+
);
|
|
53
|
+
```
|
|
54
|
+
|
|
5
55
|
## Problem Statement
|
|
6
56
|
|
|
7
57
|
In a typical mesh network with a bridge node connecting to the Internet, the bridge represents a single point of failure. If the bridge loses Internet connectivity or goes offline, the entire mesh loses its gateway to the Internet.
|
|
@@ -5,6 +5,27 @@
|
|
|
5
5
|
// When the primary bridge loses Internet connectivity, nodes automatically
|
|
6
6
|
// hold an election to select a new bridge based on router signal strength.
|
|
7
7
|
//
|
|
8
|
+
// IMPORTANT - UNDERSTANDING INTERNET CONNECTIVITY:
|
|
9
|
+
// ================================================
|
|
10
|
+
// The mesh.hasInternetConnection() method checks if a GATEWAY (bridge) node
|
|
11
|
+
// has Internet access - it does NOT mean THIS node can make HTTP requests!
|
|
12
|
+
//
|
|
13
|
+
// Regular mesh nodes do NOT have direct IP routing to the Internet.
|
|
14
|
+
// They only communicate via the painlessMesh protocol (node-to-node).
|
|
15
|
+
//
|
|
16
|
+
// To send data to the Internet from a regular node:
|
|
17
|
+
// 1. Use mesh.sendToInternet() to route through a gateway
|
|
18
|
+
// 2. Use initAsSharedGateway() so all nodes have router access
|
|
19
|
+
// NOTE: initAsSharedGateway() requires ROUTER credentials:
|
|
20
|
+
// mesh.initAsSharedGateway(MESH_PREFIX, MESH_PASSWORD,
|
|
21
|
+
// ROUTER_SSID, ROUTER_PASSWORD, // Router creds required!
|
|
22
|
+
// &userScheduler, MESH_PORT);
|
|
23
|
+
// 3. Send mesh messages to bridge node which handles Internet comms
|
|
24
|
+
//
|
|
25
|
+
// DON'T DO THIS on regular mesh nodes:
|
|
26
|
+
// HTTPClient http;
|
|
27
|
+
// http.begin("https://api.example.com"); // FAILS with "connection refused"
|
|
28
|
+
//
|
|
8
29
|
// Hardware Required:
|
|
9
30
|
// - ESP32 or ESP8266
|
|
10
31
|
// - WiFi router with Internet connection
|
|
@@ -204,7 +225,11 @@ void loop() {
|
|
|
204
225
|
|
|
205
226
|
Serial.println("\n--- Bridge Status ---");
|
|
206
227
|
Serial.printf("I am bridge: %s\n", mesh.isBridge() ? "YES" : "NO");
|
|
207
|
-
|
|
228
|
+
|
|
229
|
+
// NOTE: "Internet available" means a GATEWAY node has Internet access.
|
|
230
|
+
// This does NOT mean THIS node can make direct HTTP requests!
|
|
231
|
+
// Regular mesh nodes must use sendToInternet() to reach the Internet.
|
|
232
|
+
Serial.printf("Internet available via gateway: %s\n", mesh.hasInternetConnection() ? "YES" : "NO");
|
|
208
233
|
Serial.printf("Mesh connections active: %s\n", mesh.hasActiveMeshConnections() ? "YES" : "NO");
|
|
209
234
|
|
|
210
235
|
auto bridges = mesh.getBridges();
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# sendToInternet Example - WhatsApp/Callmebot Integration
|
|
2
|
+
|
|
3
|
+
This example demonstrates how to use `mesh.sendToInternet()` to send data to Internet endpoints (like WhatsApp via Callmebot) from **any node** in the mesh network.
|
|
4
|
+
|
|
5
|
+
## ⚠️ Important: Understanding sendToInternet()
|
|
6
|
+
|
|
7
|
+
Regular mesh nodes do **NOT** have direct IP routing to the Internet. The `sendToInternet()` API routes your data **through a gateway node** that has Internet access.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
|
11
|
+
│ Sensor Node │ ──sendToInternet──▶│ Gateway │ ──HTTP Request──▶ │ Internet │
|
|
12
|
+
│ (no WiFi) │ │ (has WiFi) │ │ (Callmebot)│
|
|
13
|
+
└─────────────┘ └─────────────┘ └─────────────┘
|
|
14
|
+
▲ │ │
|
|
15
|
+
│ │ │
|
|
16
|
+
└──────────── ACK/Result ◀─────────┴──────────── HTTP Response ◀──────┘
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**This is different from making direct HTTP requests** (which would fail on regular mesh nodes with "connection refused").
|
|
20
|
+
|
|
21
|
+
## Use Cases
|
|
22
|
+
|
|
23
|
+
- 🐟 **Fish farm sensors** sending O2 alarms to WhatsApp
|
|
24
|
+
- 🏭 **Industrial IoT** sending alerts to cloud APIs
|
|
25
|
+
- 🏠 **Smart home sensors** reporting to home automation servers
|
|
26
|
+
- 📊 **Remote monitoring** sending data to ThingsBoard, AWS IoT, etc.
|
|
27
|
+
|
|
28
|
+
## Prerequisites
|
|
29
|
+
|
|
30
|
+
1. **At least one node must be a bridge/gateway** with Internet access
|
|
31
|
+
2. **OR** use `initAsSharedGateway()` so all nodes have Internet
|
|
32
|
+
3. Enable the API after mesh init: `mesh.enableSendToInternet()`
|
|
33
|
+
|
|
34
|
+
## Setup for Callmebot WhatsApp
|
|
35
|
+
|
|
36
|
+
1. Get your API key from: https://www.callmebot.com/blog/free-api-whatsapp-messages/
|
|
37
|
+
2. Update the configuration in the sketch:
|
|
38
|
+
|
|
39
|
+
```cpp
|
|
40
|
+
#define WHATSAPP_PHONE "+1234567890" // Your phone with country code
|
|
41
|
+
#define WHATSAPP_APIKEY "your_api_key" // Your Callmebot API key
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## API Usage
|
|
45
|
+
|
|
46
|
+
### Basic Usage
|
|
47
|
+
|
|
48
|
+
```cpp
|
|
49
|
+
// Check if Internet is available via any gateway
|
|
50
|
+
if (mesh.hasInternetConnection()) {
|
|
51
|
+
|
|
52
|
+
// Send data to Internet - routed through gateway automatically
|
|
53
|
+
uint32_t msgId = mesh.sendToInternet(
|
|
54
|
+
"https://api.callmebot.com/whatsapp.php?phone=+1234567890&apikey=KEY&text=Hello",
|
|
55
|
+
"", // Empty payload for GET (params in URL)
|
|
56
|
+
[](bool success, uint16_t httpStatus, String error) {
|
|
57
|
+
if (success) {
|
|
58
|
+
Serial.printf("✅ Sent! HTTP: %d\n", httpStatus);
|
|
59
|
+
} else {
|
|
60
|
+
Serial.printf("❌ Failed: %s\n", error.c_str());
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Sending JSON to REST API
|
|
68
|
+
|
|
69
|
+
```cpp
|
|
70
|
+
String payload = "{\"temperature\": 25.5, \"humidity\": 60}";
|
|
71
|
+
|
|
72
|
+
mesh.sendToInternet(
|
|
73
|
+
"https://api.yourserver.com/sensors",
|
|
74
|
+
payload,
|
|
75
|
+
[](bool success, uint16_t httpStatus, String error) {
|
|
76
|
+
Serial.printf("Result: %s, HTTP: %d\n",
|
|
77
|
+
success ? "OK" : error.c_str(), httpStatus);
|
|
78
|
+
},
|
|
79
|
+
static_cast<uint8_t>(painlessmesh::gateway::GatewayPriority::PRIORITY_HIGH)
|
|
80
|
+
);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Priority Levels
|
|
84
|
+
|
|
85
|
+
| Value | Enum | Use Case |
|
|
86
|
+
|-------|------|----------|
|
|
87
|
+
| 0 | `PRIORITY_CRITICAL` | Alarms, emergencies |
|
|
88
|
+
| 1 | `PRIORITY_HIGH` | Important alerts |
|
|
89
|
+
| 2 | `PRIORITY_NORMAL` | Regular sensor data |
|
|
90
|
+
| 3 | `PRIORITY_LOW` | Bulk/background data |
|
|
91
|
+
|
|
92
|
+
## Hardware Setup
|
|
93
|
+
|
|
94
|
+
### Option A: Bridge + Sensor Nodes
|
|
95
|
+
|
|
96
|
+
**Bridge Node** (has Internet):
|
|
97
|
+
```cpp
|
|
98
|
+
#define IS_BRIDGE_NODE true
|
|
99
|
+
// Update ROUTER_SSID and ROUTER_PASSWORD
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Sensor Nodes** (no direct Internet):
|
|
103
|
+
```cpp
|
|
104
|
+
#define IS_BRIDGE_NODE false
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Option B: Shared Gateway Mode
|
|
108
|
+
|
|
109
|
+
All nodes connect to the same router:
|
|
110
|
+
```cpp
|
|
111
|
+
mesh.initAsSharedGateway(
|
|
112
|
+
MESH_PREFIX, MESH_PASSWORD,
|
|
113
|
+
ROUTER_SSID, ROUTER_PASSWORD, // Router credentials required!
|
|
114
|
+
&userScheduler, MESH_PORT
|
|
115
|
+
);
|
|
116
|
+
mesh.enableSendToInternet();
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Common Issues
|
|
120
|
+
|
|
121
|
+
### "No Internet available - no gateway with Internet found"
|
|
122
|
+
|
|
123
|
+
- Make sure at least one node is initialized as a bridge with router credentials
|
|
124
|
+
- Check that the bridge has successfully connected to the router
|
|
125
|
+
- Use `mesh.hasInternetConnection()` to verify gateway availability
|
|
126
|
+
|
|
127
|
+
### "connection refused" when using HTTPClient directly
|
|
128
|
+
|
|
129
|
+
**DON'T do this on regular mesh nodes:**
|
|
130
|
+
```cpp
|
|
131
|
+
// This FAILS on regular mesh nodes!
|
|
132
|
+
HTTPClient http;
|
|
133
|
+
http.begin("https://api.callmebot.com/...");
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**DO this instead:**
|
|
137
|
+
```cpp
|
|
138
|
+
// This works - routes through gateway
|
|
139
|
+
mesh.sendToInternet("https://api.callmebot.com/...", "", callback);
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### WhatsApp message not received
|
|
143
|
+
|
|
144
|
+
1. Verify your Callmebot API key is correct
|
|
145
|
+
2. Ensure phone number includes country code (e.g., `+1234567890`)
|
|
146
|
+
3. Check HTTP status code in callback (200 = success)
|
|
147
|
+
4. URL-encode special characters in the message
|
|
148
|
+
|
|
149
|
+
## Files
|
|
150
|
+
|
|
151
|
+
- `sendToInternet.ino` - Main example sketch
|
|
152
|
+
- `README.md` - This documentation
|
|
153
|
+
|
|
154
|
+
## Related Examples
|
|
155
|
+
|
|
156
|
+
- [sharedGateway](../sharedGateway/) - All nodes with direct Internet access
|
|
157
|
+
- [bridge_failover](../bridge_failover/) - Automatic gateway failover
|
|
158
|
+
- [mqttBridge](../mqttBridge/) - MQTT integration
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
; PlatformIO Project Configuration File
|
|
2
|
+
;
|
|
3
|
+
; sendToInternet Example - WhatsApp/Callmebot Integration
|
|
4
|
+
;
|
|
5
|
+
; Upload to ESP32 or ESP8266 to test mesh.sendToInternet() API
|
|
6
|
+
|
|
7
|
+
[env:esp32dev]
|
|
8
|
+
platform = espressif32
|
|
9
|
+
board = esp32dev
|
|
10
|
+
framework = arduino
|
|
11
|
+
lib_deps =
|
|
12
|
+
alteriom/AlteriomPainlessMesh
|
|
13
|
+
bblanchon/ArduinoJson@^7.0.0
|
|
14
|
+
monitor_speed = 115200
|
|
15
|
+
|
|
16
|
+
[env:esp8266]
|
|
17
|
+
platform = espressif8266
|
|
18
|
+
board = nodemcuv2
|
|
19
|
+
framework = arduino
|
|
20
|
+
lib_deps =
|
|
21
|
+
alteriom/AlteriomPainlessMesh
|
|
22
|
+
bblanchon/ArduinoJson@^7.0.0
|
|
23
|
+
monitor_speed = 115200
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
//************************************************************
|
|
2
|
+
// sendToInternet Example - WhatsApp/Callmebot Integration
|
|
3
|
+
//
|
|
4
|
+
// This example demonstrates how to use mesh.sendToInternet() to send
|
|
5
|
+
// messages to Internet endpoints (like WhatsApp via Callmebot) from
|
|
6
|
+
// ANY node in the mesh network - not just the bridge/gateway.
|
|
7
|
+
//
|
|
8
|
+
// IMPORTANT:
|
|
9
|
+
// Regular mesh nodes do NOT have direct IP routing to the Internet.
|
|
10
|
+
// The sendToInternet() API routes your data THROUGH a gateway node
|
|
11
|
+
// that has Internet access. This is different from making direct
|
|
12
|
+
// HTTP requests (which would fail on regular mesh nodes).
|
|
13
|
+
//
|
|
14
|
+
// How it works:
|
|
15
|
+
// 1. Node calls mesh.sendToInternet() with destination URL and payload
|
|
16
|
+
// 2. The mesh routes the request to a gateway node with Internet
|
|
17
|
+
// 3. Gateway makes the actual HTTP request to the destination
|
|
18
|
+
// 4. Gateway sends acknowledgment back through the mesh
|
|
19
|
+
// 5. Your callback is invoked with the result
|
|
20
|
+
//
|
|
21
|
+
// Use Cases:
|
|
22
|
+
// - Fish farm sensors sending O2 alarms to WhatsApp
|
|
23
|
+
// - Industrial IoT sending alerts to cloud APIs
|
|
24
|
+
// - Smart home sensors reporting to home automation servers
|
|
25
|
+
//
|
|
26
|
+
// Prerequisites:
|
|
27
|
+
// - At least one node must be a bridge/gateway with Internet access
|
|
28
|
+
// - OR use initAsSharedGateway() so all nodes have Internet
|
|
29
|
+
// - Enable sendToInternet() after mesh.init(): mesh.enableSendToInternet()
|
|
30
|
+
//
|
|
31
|
+
// For Callmebot WhatsApp API:
|
|
32
|
+
// - Get your API key from https://www.callmebot.com/blog/free-api-whatsapp-messages/
|
|
33
|
+
// - Format: https://api.callmebot.com/whatsapp.php?phone=PHONE&apikey=KEY&text=MESSAGE
|
|
34
|
+
//
|
|
35
|
+
//************************************************************
|
|
36
|
+
#include "painlessMesh.h"
|
|
37
|
+
#include <WiFiClientSecure.h>
|
|
38
|
+
|
|
39
|
+
// ============================================
|
|
40
|
+
// Mesh Network Configuration
|
|
41
|
+
// ============================================
|
|
42
|
+
#define MESH_PREFIX "SensorMesh"
|
|
43
|
+
#define MESH_PASSWORD "meshPassword123"
|
|
44
|
+
#define MESH_PORT 5555
|
|
45
|
+
|
|
46
|
+
// ============================================
|
|
47
|
+
// Router Configuration (for bridge/gateway)
|
|
48
|
+
// ============================================
|
|
49
|
+
#define ROUTER_SSID "YourRouterSSID"
|
|
50
|
+
#define ROUTER_PASSWORD "YourRouterPassword"
|
|
51
|
+
|
|
52
|
+
// ============================================
|
|
53
|
+
// Callmebot/WhatsApp Configuration
|
|
54
|
+
// ============================================
|
|
55
|
+
// Get your API key from: https://www.callmebot.com/blog/free-api-whatsapp-messages/
|
|
56
|
+
#define WHATSAPP_PHONE "+1234567890" // Your phone number with country code
|
|
57
|
+
#define WHATSAPP_APIKEY "your_api_key" // Your Callmebot API key
|
|
58
|
+
|
|
59
|
+
// ============================================
|
|
60
|
+
// Sensor Simulation Configuration
|
|
61
|
+
// ============================================
|
|
62
|
+
// These define the ranges for simulated sensor values
|
|
63
|
+
#define TEMP_MIN 20.0 // Minimum temperature (°C)
|
|
64
|
+
#define TEMP_RANGE 10.0 // Temperature range (20-30°C)
|
|
65
|
+
#define HUMIDITY_MIN 40.0 // Minimum humidity (%)
|
|
66
|
+
#define HUMIDITY_RANGE 40.0 // Humidity range (40-80%)
|
|
67
|
+
#define O2_MIN 5.0 // Minimum O2 level (mg/L)
|
|
68
|
+
#define O2_RANGE 5.0 // O2 range (5-10 mg/L)
|
|
69
|
+
#define O2_ALARM_THRESHOLD 6.0 // O2 level below this triggers alarm
|
|
70
|
+
|
|
71
|
+
// ============================================
|
|
72
|
+
// Mode Selection
|
|
73
|
+
// ============================================
|
|
74
|
+
// Set to true to make this node a bridge with Internet access
|
|
75
|
+
// Set to false for regular mesh nodes that will send via the bridge
|
|
76
|
+
#define IS_BRIDGE_NODE false
|
|
77
|
+
|
|
78
|
+
// ============================================
|
|
79
|
+
// Task Scheduler and Mesh Instance
|
|
80
|
+
// ============================================
|
|
81
|
+
Scheduler userScheduler;
|
|
82
|
+
painlessMesh mesh;
|
|
83
|
+
|
|
84
|
+
// ============================================
|
|
85
|
+
// Function Prototypes
|
|
86
|
+
// ============================================
|
|
87
|
+
void sendAlertToWhatsApp(String message);
|
|
88
|
+
void sendSensorDataToCloud();
|
|
89
|
+
void receivedCallback(uint32_t from, String& msg);
|
|
90
|
+
void newConnectionCallback(uint32_t nodeId);
|
|
91
|
+
void changedConnectionCallback();
|
|
92
|
+
String urlEncode(const String& str);
|
|
93
|
+
|
|
94
|
+
// ============================================
|
|
95
|
+
// Task Definitions
|
|
96
|
+
// ============================================
|
|
97
|
+
// Task to periodically send sensor data (simulated)
|
|
98
|
+
Task taskSendSensorData(60000, TASK_FOREVER, &sendSensorDataToCloud);
|
|
99
|
+
|
|
100
|
+
// ============================================
|
|
101
|
+
// URL Encoding Helper
|
|
102
|
+
// ============================================
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* URL-encode a string for safe transmission in URLs
|
|
106
|
+
*
|
|
107
|
+
* Encodes special characters to their percent-encoded equivalents.
|
|
108
|
+
* This is required for WhatsApp messages containing special characters.
|
|
109
|
+
*
|
|
110
|
+
* @param str The string to encode
|
|
111
|
+
* @return URL-encoded string
|
|
112
|
+
*/
|
|
113
|
+
String urlEncode(const String& str) {
|
|
114
|
+
String encoded = "";
|
|
115
|
+
for (size_t i = 0; i < str.length(); i++) {
|
|
116
|
+
char c = str.charAt(i);
|
|
117
|
+
if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
|
|
118
|
+
// Safe characters - no encoding needed
|
|
119
|
+
encoded += c;
|
|
120
|
+
} else if (c == ' ') {
|
|
121
|
+
encoded += "%20";
|
|
122
|
+
} else {
|
|
123
|
+
// Encode other characters as %XX
|
|
124
|
+
char hex[4];
|
|
125
|
+
snprintf(hex, sizeof(hex), "%%%02X", (unsigned char)c);
|
|
126
|
+
encoded += hex;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return encoded;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ============================================
|
|
133
|
+
// sendToInternet() Usage Example
|
|
134
|
+
// ============================================
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Send a WhatsApp message via Callmebot using sendToInternet()
|
|
138
|
+
*
|
|
139
|
+
* This function demonstrates how to use mesh.sendToInternet() to send
|
|
140
|
+
* data to an Internet endpoint. The request is automatically routed
|
|
141
|
+
* through a gateway node that has Internet access.
|
|
142
|
+
*
|
|
143
|
+
* @param message The message to send via WhatsApp
|
|
144
|
+
*/
|
|
145
|
+
void sendAlertToWhatsApp(String message) {
|
|
146
|
+
// Check if Internet is available via any gateway
|
|
147
|
+
if (!mesh.hasInternetConnection()) {
|
|
148
|
+
Serial.println("❌ No Internet available - no gateway with Internet found");
|
|
149
|
+
Serial.println(" Make sure at least one node is a bridge with router access");
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// URL-encode the message for safe transmission
|
|
154
|
+
String encodedMessage = urlEncode(message);
|
|
155
|
+
|
|
156
|
+
// Build the Callmebot WhatsApp API URL
|
|
157
|
+
// Format: https://api.callmebot.com/whatsapp.php?phone=PHONE&apikey=KEY&text=MESSAGE
|
|
158
|
+
String url = "https://api.callmebot.com/whatsapp.php";
|
|
159
|
+
url += "?phone=" + String(WHATSAPP_PHONE);
|
|
160
|
+
url += "&apikey=" + String(WHATSAPP_APIKEY);
|
|
161
|
+
url += "&text=" + encodedMessage;
|
|
162
|
+
|
|
163
|
+
Serial.println("\n📱 Sending WhatsApp message via sendToInternet()...");
|
|
164
|
+
Serial.printf(" Message: %s\n", message.c_str());
|
|
165
|
+
Serial.printf(" URL: %s\n", url.c_str());
|
|
166
|
+
|
|
167
|
+
// Use sendToInternet() to route the request through a gateway
|
|
168
|
+
// The callback will be invoked when we get a response (or timeout)
|
|
169
|
+
uint32_t msgId = mesh.sendToInternet(
|
|
170
|
+
url,
|
|
171
|
+
"", // No payload needed for GET request - params are in URL
|
|
172
|
+
[](bool success, uint16_t httpStatus, String error) {
|
|
173
|
+
if (success) {
|
|
174
|
+
Serial.printf("✅ WhatsApp message sent! HTTP Status: %d\n", httpStatus);
|
|
175
|
+
} else {
|
|
176
|
+
Serial.printf("❌ Failed to send WhatsApp: %s (HTTP: %d)\n", error.c_str(), httpStatus);
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
static_cast<uint8_t>(painlessmesh::gateway::GatewayPriority::PRIORITY_HIGH)
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
if (msgId > 0) {
|
|
183
|
+
Serial.printf(" Message queued with ID: %u\n", msgId);
|
|
184
|
+
} else {
|
|
185
|
+
Serial.println(" ❌ Failed to queue message - no gateway available");
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Send sensor data to a cloud API
|
|
191
|
+
*
|
|
192
|
+
* This demonstrates sending JSON sensor data to a REST API endpoint.
|
|
193
|
+
* In a real application, you would replace the URL with your actual
|
|
194
|
+
* cloud API endpoint (AWS, Azure, ThingsBoard, etc.)
|
|
195
|
+
*/
|
|
196
|
+
void sendSensorDataToCloud() {
|
|
197
|
+
// Simulate sensor readings using configured ranges
|
|
198
|
+
float temperature = TEMP_MIN + random(0, (int)(TEMP_RANGE * 10)) / 10.0;
|
|
199
|
+
float humidity = HUMIDITY_MIN + random(0, (int)(HUMIDITY_RANGE * 10)) / 10.0;
|
|
200
|
+
float o2Level = O2_MIN + random(0, (int)(O2_RANGE * 10)) / 10.0;
|
|
201
|
+
|
|
202
|
+
// Create JSON payload
|
|
203
|
+
String payload = "{";
|
|
204
|
+
payload += "\"nodeId\":" + String(mesh.getNodeId()) + ",";
|
|
205
|
+
payload += "\"temperature\":" + String(temperature, 1) + ",";
|
|
206
|
+
payload += "\"humidity\":" + String(humidity, 1) + ",";
|
|
207
|
+
payload += "\"o2Level\":" + String(o2Level, 1) + ",";
|
|
208
|
+
payload += "\"timestamp\":" + String(millis());
|
|
209
|
+
payload += "}";
|
|
210
|
+
|
|
211
|
+
Serial.println("\n📊 Sending sensor data to cloud...");
|
|
212
|
+
Serial.printf(" Payload: %s\n", payload.c_str());
|
|
213
|
+
|
|
214
|
+
// Check for alarm conditions using configured threshold
|
|
215
|
+
if (o2Level < O2_ALARM_THRESHOLD) {
|
|
216
|
+
// O2 level critical - send WhatsApp alert!
|
|
217
|
+
String alertMsg = "⚠️ ALARM: O2 level critical at " + String(o2Level, 1) + " mg/L! Node: " + String(mesh.getNodeId());
|
|
218
|
+
sendAlertToWhatsApp(alertMsg);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Only send if Internet is available
|
|
222
|
+
if (!mesh.hasInternetConnection()) {
|
|
223
|
+
Serial.println(" ⚠️ No Internet - data not sent (would be queued in production)");
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Send to cloud API (replace with your actual endpoint)
|
|
228
|
+
// Example endpoints:
|
|
229
|
+
// - ThingsBoard: "https://demo.thingsboard.io/api/v1/YOUR_TOKEN/telemetry"
|
|
230
|
+
// - AWS IoT: "https://YOUR_ENDPOINT.iot.us-east-1.amazonaws.com/topics/sensors"
|
|
231
|
+
// - Custom API: "https://api.yourserver.com/sensors/data"
|
|
232
|
+
|
|
233
|
+
String cloudUrl = "https://api.example.com/sensors"; // Replace with your endpoint
|
|
234
|
+
|
|
235
|
+
uint32_t msgId = mesh.sendToInternet(
|
|
236
|
+
cloudUrl,
|
|
237
|
+
payload,
|
|
238
|
+
[](bool success, uint16_t httpStatus, String error) {
|
|
239
|
+
if (success) {
|
|
240
|
+
Serial.printf(" ✅ Cloud data sent! HTTP: %d\n", httpStatus);
|
|
241
|
+
} else {
|
|
242
|
+
Serial.printf(" ❌ Cloud send failed: %s\n", error.c_str());
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
Serial.printf(" Message ID: %u\n", msgId);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ============================================
|
|
251
|
+
// Mesh Callbacks
|
|
252
|
+
// ============================================
|
|
253
|
+
|
|
254
|
+
void receivedCallback(uint32_t from, String& msg) {
|
|
255
|
+
Serial.printf("📨 Received from %u: %s\n", from, msg.c_str());
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
void newConnectionCallback(uint32_t nodeId) {
|
|
259
|
+
Serial.printf("✓ New connection: Node %u\n", nodeId);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
void changedConnectionCallback() {
|
|
263
|
+
Serial.printf("🔄 Mesh topology changed. Nodes: %d\n", mesh.getNodeList().size());
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// ============================================
|
|
267
|
+
// Setup Function
|
|
268
|
+
// ============================================
|
|
269
|
+
void setup() {
|
|
270
|
+
Serial.begin(115200);
|
|
271
|
+
delay(1000);
|
|
272
|
+
|
|
273
|
+
Serial.println("\n");
|
|
274
|
+
Serial.println("================================================");
|
|
275
|
+
Serial.println(" painlessMesh - sendToInternet Example");
|
|
276
|
+
Serial.println(" WhatsApp/Callmebot Integration Demo");
|
|
277
|
+
Serial.println("================================================\n");
|
|
278
|
+
|
|
279
|
+
// Configure debug output
|
|
280
|
+
mesh.setDebugMsgTypes(ERROR | STARTUP | CONNECTION);
|
|
281
|
+
|
|
282
|
+
bool success = false;
|
|
283
|
+
|
|
284
|
+
#if IS_BRIDGE_NODE
|
|
285
|
+
// Initialize as bridge with router connection
|
|
286
|
+
Serial.println("Mode: BRIDGE (Gateway with Internet access)\n");
|
|
287
|
+
success = mesh.initAsBridge(
|
|
288
|
+
MESH_PREFIX, MESH_PASSWORD,
|
|
289
|
+
ROUTER_SSID, ROUTER_PASSWORD,
|
|
290
|
+
&userScheduler, MESH_PORT
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
if (success) {
|
|
294
|
+
Serial.println("✓ Bridge initialized - this node has Internet access");
|
|
295
|
+
Serial.println(" Other nodes can use sendToInternet() through this gateway\n");
|
|
296
|
+
} else {
|
|
297
|
+
Serial.println("✗ Bridge init failed - falling back to regular mesh");
|
|
298
|
+
mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT);
|
|
299
|
+
}
|
|
300
|
+
#else
|
|
301
|
+
// Initialize as regular mesh node
|
|
302
|
+
Serial.println("Mode: REGULAR NODE (sends to Internet via gateway)\n");
|
|
303
|
+
mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT);
|
|
304
|
+
success = true;
|
|
305
|
+
#endif
|
|
306
|
+
|
|
307
|
+
// IMPORTANT: Enable the sendToInternet() API
|
|
308
|
+
mesh.enableSendToInternet();
|
|
309
|
+
|
|
310
|
+
// Register callbacks
|
|
311
|
+
mesh.onReceive(&receivedCallback);
|
|
312
|
+
mesh.onNewConnection(&newConnectionCallback);
|
|
313
|
+
mesh.onChangedConnections(&changedConnectionCallback);
|
|
314
|
+
|
|
315
|
+
// Start periodic sensor data task
|
|
316
|
+
userScheduler.addTask(taskSendSensorData);
|
|
317
|
+
taskSendSensorData.enable();
|
|
318
|
+
|
|
319
|
+
// Print startup info
|
|
320
|
+
Serial.println("================================================");
|
|
321
|
+
Serial.printf("Node ID: %u\n", mesh.getNodeId());
|
|
322
|
+
Serial.printf("Is Bridge: %s\n", mesh.isBridge() ? "YES" : "NO");
|
|
323
|
+
Serial.println("================================================\n");
|
|
324
|
+
|
|
325
|
+
// Send a startup notification via WhatsApp (demonstrates sendToInternet)
|
|
326
|
+
String startupMsg = "🚀 Node " + String(mesh.getNodeId()) + " started!";
|
|
327
|
+
|
|
328
|
+
// Delay to allow mesh to connect first
|
|
329
|
+
Serial.println("Will attempt to send startup WhatsApp in 30 seconds...\n");
|
|
330
|
+
mesh.addTask([startupMsg]() {
|
|
331
|
+
sendAlertToWhatsApp(startupMsg);
|
|
332
|
+
}, 30000); // 30 second delay
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// ============================================
|
|
336
|
+
// Main Loop
|
|
337
|
+
// ============================================
|
|
338
|
+
void loop() {
|
|
339
|
+
mesh.update();
|
|
340
|
+
}
|
package/library.json
CHANGED
package/library.properties
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
name=Alteriom PainlessMesh
|
|
2
|
-
version=1.9.
|
|
2
|
+
version=1.9.2
|
|
3
3
|
author=Coopdis,Scotty Franzyshen,Edwin van Leeuwen,Germán Martín,Maximilian Schwarz,Doanh Doanh,Alteriom
|
|
4
4
|
maintainer=Alteriom
|
|
5
5
|
sentence=A painless way to setup a mesh with ESP8266 and ESP32 devices with Alteriom extensions
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alteriom/painlessmesh",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.2",
|
|
4
4
|
"description": "painlessMesh is a user-friendly library for creating mesh networks with ESP8266 and ESP32 devices. This Alteriom fork includes additional packages for sensor data (SensorPackage), device commands (CommandPackage), and status monitoring (StatusPackage). It handles routing and network management automatically, so you can focus on your application. The library uses JSON-based messaging and syncs time across all nodes, making it ideal for coordinated behaviour like synchronized light displays or sensor networks reporting to a central node.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"arduino",
|
package/src/arduino/wifi.hpp
CHANGED
|
@@ -877,12 +877,35 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
877
877
|
}
|
|
878
878
|
|
|
879
879
|
/**
|
|
880
|
-
* Check if any bridge in the mesh has Internet connectivity
|
|
880
|
+
* Check if any bridge/gateway in the mesh has Internet connectivity
|
|
881
|
+
*
|
|
882
|
+
* IMPORTANT: This method checks if a GATEWAY node (bridge) in the mesh has
|
|
883
|
+
* Internet access, NOT whether THIS node can directly make HTTP/HTTPS requests.
|
|
884
|
+
*
|
|
885
|
+
* Regular mesh nodes do NOT have direct IP routing to the Internet. They only
|
|
886
|
+
* communicate via the painlessMesh protocol. To send data to the Internet from
|
|
887
|
+
* a regular node, you must use sendToInternet() which routes through a gateway,
|
|
888
|
+
* or use initAsSharedGateway(meshSSID, meshPwd, ROUTER_SSID, ROUTER_PWD, scheduler, port)
|
|
889
|
+
* to give all nodes direct router access (requires router credentials).
|
|
881
890
|
*
|
|
882
891
|
* Override of base class method to also check if THIS node is a bridge
|
|
883
892
|
* with Internet connectivity, not just other bridges in the mesh.
|
|
884
893
|
*
|
|
894
|
+
* \code
|
|
895
|
+
* if (mesh.hasInternetConnection()) {
|
|
896
|
+
* // A gateway exists - use sendToInternet() to reach Internet
|
|
897
|
+
* mesh.sendToInternet("https://api.example.com", data, callback);
|
|
898
|
+
* }
|
|
899
|
+
*
|
|
900
|
+
* // DON'T DO THIS on regular nodes - will fail with "connection refused":
|
|
901
|
+
* // HTTPClient http;
|
|
902
|
+
* // http.begin("https://api.example.com");
|
|
903
|
+
* \endcode
|
|
904
|
+
*
|
|
885
905
|
* @return true if at least one bridge (including this node) has Internet
|
|
906
|
+
* @see hasLocalInternet() to check if THIS node has direct Internet access
|
|
907
|
+
* @see sendToInternet() to send data to Internet via gateway
|
|
908
|
+
* @see initAsSharedGateway() requires router credentials (ROUTER_SSID, ROUTER_PASSWORD)
|
|
886
909
|
*/
|
|
887
910
|
bool hasInternetConnection() {
|
|
888
911
|
// First check if THIS node is a bridge with Internet
|
|
@@ -606,14 +606,42 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
606
606
|
}
|
|
607
607
|
|
|
608
608
|
/**
|
|
609
|
-
* Check if any bridge in the mesh has Internet connectivity
|
|
609
|
+
* Check if any bridge/gateway in the mesh has Internet connectivity
|
|
610
|
+
*
|
|
611
|
+
* IMPORTANT: This method checks if a GATEWAY node (bridge) in the mesh has
|
|
612
|
+
* Internet access, NOT whether THIS node can directly make HTTP/HTTPS requests.
|
|
613
|
+
*
|
|
614
|
+
* Regular mesh nodes do NOT have direct IP routing to the Internet - they only
|
|
615
|
+
* communicate over the painlessMesh protocol. To send data to the Internet from
|
|
616
|
+
* a regular node, you must either:
|
|
617
|
+
*
|
|
618
|
+
* 1. Use sendToInternet() to route data through a gateway node
|
|
619
|
+
* 2. Use initAsSharedGateway(meshSSID, meshPwd, ROUTER_SSID, ROUTER_PWD, scheduler, port)
|
|
620
|
+
* to give all nodes direct router access (requires router credentials)
|
|
621
|
+
* 3. Send mesh messages to a bridge node that handles Internet communication
|
|
622
|
+
*
|
|
623
|
+
* Use hasLocalInternet() to check if THIS specific node has direct Internet access.
|
|
610
624
|
*
|
|
611
625
|
* When connected to mesh, requires recent bridge status (within timeout).
|
|
612
626
|
* When disconnected from mesh, returns true if any bridge previously
|
|
613
627
|
* reported Internet connectivity - allowing operations to be queued for
|
|
614
628
|
* when mesh connectivity is restored.
|
|
615
629
|
*
|
|
616
|
-
*
|
|
630
|
+
* \code
|
|
631
|
+
* if (mesh.hasInternetConnection()) {
|
|
632
|
+
* // A gateway exists with Internet - use sendToInternet() to reach Internet
|
|
633
|
+
* mesh.sendToInternet("https://api.example.com/data", jsonPayload, callback);
|
|
634
|
+
* }
|
|
635
|
+
*
|
|
636
|
+
* // DON'T DO THIS - regular mesh nodes cannot make direct HTTP requests:
|
|
637
|
+
* // HTTPClient http;
|
|
638
|
+
* // http.begin("https://api.example.com"); // Will fail with "connection refused"
|
|
639
|
+
* \endcode
|
|
640
|
+
*
|
|
641
|
+
* @return true if at least one gateway/bridge reports Internet connection
|
|
642
|
+
* @see hasLocalInternet() to check if THIS node has direct Internet access
|
|
643
|
+
* @see sendToInternet() to send data to Internet via gateway
|
|
644
|
+
* @see initAsSharedGateway() to give all nodes direct Internet access (requires router credentials)
|
|
617
645
|
*/
|
|
618
646
|
bool hasInternetConnection() {
|
|
619
647
|
bool hasConnections = hasActiveMeshConnections();
|