@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 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.1** - Isolated bridge retry mechanism for failed initial bridge setup
7
+ **Version 1.9.2** - Consolidated release across all distribution channels
8
8
 
9
9
  [![CI/CD Pipeline](https://github.com/Alteriom/painlessMesh/actions/workflows/ci.yml/badge.svg)](https://github.com/Alteriom/painlessMesh/actions/workflows/ci.yml)
10
10
  [![Documentation](https://github.com/Alteriom/painlessMesh/actions/workflows/docs.yml/badge.svg)](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
- Serial.printf("Internet available: %s\n", mesh.hasInternetConnection() ? "YES" : "NO");
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
@@ -6,7 +6,7 @@
6
6
  "type": "git",
7
7
  "url": "https://github.com/Alteriom/painlessMesh"
8
8
  },
9
- "version": "1.9.1",
9
+ "version": "1.9.2",
10
10
  "frameworks": [
11
11
  "arduino"
12
12
  ],
@@ -1,5 +1,5 @@
1
1
  name=Alteriom PainlessMesh
2
- version=1.9.1
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.1",
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",
@@ -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
- * @return true if at least one bridge reports Internet connection
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();