@alteriom/painlessmesh 2.0.1 → 2.0.3
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 +145 -0
- package/CONTRIBUTING.md +15 -0
- package/README.md +6 -2
- package/RELEASE_GUIDE.md +27 -5
- package/examples/sendToInternet/README.md +3 -3
- package/examples/sendToInternet/sendToInternet.ino +58 -23
- package/library.json +1 -1
- package/library.properties +1 -1
- package/package.json +1 -1
- package/src/AlteriomPainlessMesh.h +2 -2
- package/src/arduino/wifi.hpp +178 -77
- package/src/painlessmesh/callback.hpp +7 -0
- package/src/painlessmesh/gateway.hpp +335 -7
- package/src/painlessmesh/logger.hpp +14 -1
- package/src/painlessmesh/mesh.hpp +232 -20
- package/src/painlessmesh/router.hpp +17 -7
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,151 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.0.3] - 2026-09-11
|
|
11
|
+
|
|
12
|
+
`sendToInternet()` fixes from one user's WhatsApp integration (#450, #452,
|
|
13
|
+
#453), and one the hardware rig found while validating them. **Upgrade if a
|
|
14
|
+
node of yours is a bridge or relays to the Internet**: on 2.0.2 a bridge's own
|
|
15
|
+
sends were refused for 30 s after boot, a service that answered a refusal with
|
|
16
|
+
HTTP 201 was reported as delivered, a destination that does not resolve
|
|
17
|
+
stalled an ESP32 gateway on every attempt, and a bridge that rebooted as a
|
|
18
|
+
regular node swallowed every request its peers still sent it. Every fix was
|
|
19
|
+
reproduced first -- against the new HTTP test point, which CI now runs, or
|
|
20
|
+
over real TCP on the desktop -- and is covered by a row on the hardware farm.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- **A bridge's own `sendToInternet()` was refused for the first 30 s after
|
|
25
|
+
`initAsBridge()`** (#450). The Internet health check is armed one line after
|
|
26
|
+
`stationManual()` re-issues `WiFi.begin()`, so its first probe ran while the
|
|
27
|
+
station was still associating and failed, and the next was a full interval
|
|
28
|
+
away. Every send from the bridge in that window fell through to the mesh
|
|
29
|
+
path and was refused with "No active mesh connections" -- #445 again, with
|
|
30
|
+
a 30 s hole instead of forever. `sendToInternet()` and the retry path now
|
|
31
|
+
spend one on-demand probe per interval when the flag says no, and the
|
|
32
|
+
station's got-IP event re-probes at once on a bridge or shared gateway.
|
|
33
|
+
- **A gateway reported a refusal as delivered, and every failure as a bare
|
|
34
|
+
number** (#450, #452). The gateway decided on the HTTP status alone and
|
|
35
|
+
discarded the response body. CallMeBot's WhatsApp API answers "Too many
|
|
36
|
+
requests" with HTTP 201 and HTTP 203, the same page under both, so a refused
|
|
37
|
+
message sent through a gateway on 2.0.2 could be reported as delivered; and
|
|
38
|
+
it answers HTTP 208 to messages it never delivers, which 2.0.2 reported as
|
|
39
|
+
"Ambiguous response" with nothing to say why. The gateway now reads the
|
|
40
|
+
start of the body (bounded to 512 bytes and 250 ms). Only 200, 201, 202 and
|
|
41
|
+
204 count as delivered, and not when the body says the service refused the
|
|
42
|
+
request; every other 2xx is a failure. Every failure reaches the origin
|
|
43
|
+
node's callback with a one-line, tag-free excerpt of the body -- for example
|
|
44
|
+
`HTTP 201: service refused the request: Oops! Too many requests...` or
|
|
45
|
+
`HTTP 208: not a delivery the gateway can confirm: ...` -- and is logged at
|
|
46
|
+
ERROR level, so a sketch on the default log levels sees what the service
|
|
47
|
+
said.
|
|
48
|
+
- **A destination whose name does not resolve stalled an ESP32 gateway on
|
|
49
|
+
every attempt** (#453). A failed lookup surfaced as `connection refused`
|
|
50
|
+
after the resolver's own patience, which this library cannot bound on ESP32,
|
|
51
|
+
and every attempt -- the origin node's retries and, since the bridge fix
|
|
52
|
+
above, the bridge's own sends -- paid it again, until relayed requests timed
|
|
53
|
+
out on their origin nodes. An ESP32 gateway now resolves the host once,
|
|
54
|
+
reports `DNS lookup failed for <host>`, and refuses that host for 60 s
|
|
55
|
+
without another lookup. ESP8266 is unchanged: its core bounds the lookup by
|
|
56
|
+
the HTTP timeout. The gateway blocking budget is unchanged.
|
|
57
|
+
- **A bridge that rebooted as a regular node swallowed every Internet request
|
|
58
|
+
routed to it.** Found on the hardware rig while validating this release. A
|
|
59
|
+
bridge that reboots, crashes, loses power or is reflashed announces nothing
|
|
60
|
+
-- only a bridge stepping down in-process sends `leaving` -- so its peers
|
|
61
|
+
kept it in their bridge list for the 60 s they trust a status, and could
|
|
62
|
+
prefer it over a live bridge on RSSI. A regular node had no handler for
|
|
63
|
+
gateway requests and dropped them without a reply; each sender waited out
|
|
64
|
+
its 30 s request timeout and reported "Request timed out". Every node now
|
|
65
|
+
answers a gateway request it cannot serve with
|
|
66
|
+
`Node <id> is not an Internet gateway`; the sender forgets that node as a
|
|
67
|
+
gateway and sends again at once through the next one, without spending a
|
|
68
|
+
retry. When it knows no other gateway, the request rides the ordinary retry
|
|
69
|
+
backoff -- on the rig the live bridge was advertised half a second later --
|
|
70
|
+
and fails with `No Internet gateway available: ...` only when none appears.
|
|
71
|
+
- **Mismatched log format arguments in `routePackage()`** (CodeQL
|
|
72
|
+
`cpp/wrong-type-format-argument`). A message that failed to parse was logged
|
|
73
|
+
with its `size_t` lengths through `%d`/`%u` -- the wrong width on 64-bit
|
|
74
|
+
hosts -- and with its `DeserializationError` object passed through `%u`,
|
|
75
|
+
undefined behaviour on every platform, in the ArduinoJson 7 path every
|
|
76
|
+
current build compiles as well as the legacy one CodeQL flagged.
|
|
77
|
+
|
|
78
|
+
### Changed
|
|
79
|
+
|
|
80
|
+
- **The mock HTTP server is now the gateway test point.** It keeps a delivery
|
|
81
|
+
ledger (`GET /requests/{tag}`) and emulates CallMeBot's status quirks
|
|
82
|
+
(`GET /callmebot/whatsapp.php`, profile chosen by `apikey`). The desktop CI
|
|
83
|
+
job starts it and `PAINLESSMESH_TESTPOINT` points the suite at it, so the
|
|
84
|
+
library's delivery verdict is checked against what the service actually did,
|
|
85
|
+
not against a table of status codes. The Alteriom farm's gateway probe serves
|
|
86
|
+
the same routes.
|
|
87
|
+
- **`Mesh::sendGatewayAck()` is public and portable.** It moved from the ESP
|
|
88
|
+
gateway code into `painlessmesh::Mesh`, because every node now needs it to
|
|
89
|
+
answer a request it cannot serve.
|
|
90
|
+
- **`Log()` is format-checked in the desktop build.** The test build marks it
|
|
91
|
+
printf-like, so CI's `-Wall -Werror` rejects an argument that does not match
|
|
92
|
+
its specifier. Device builds are unchanged: on ESP-IDF 5 `uint32_t` is
|
|
93
|
+
`unsigned long`, and every `%u` of a node id would warn there.
|
|
94
|
+
- **`sendToInternet` example.** The startup WhatsApp retries every 30 s until
|
|
95
|
+
a gateway with Internet is known instead of firing once before a regular
|
|
96
|
+
node has joined, and the cloud endpoint moved to a `CLOUD_URL` define whose
|
|
97
|
+
placeholder is skipped with a message rather than reported as
|
|
98
|
+
`connection refused`.
|
|
99
|
+
|
|
100
|
+
## [2.0.2] - 2026-09-08
|
|
101
|
+
|
|
102
|
+
Two gateway defects that reached users on 2.0.1, both in code the desktop test
|
|
103
|
+
suite cannot compile, and both now asserted on real hardware. **Upgrade if a
|
|
104
|
+
node of yours is a bridge**: on 2.0.1 a bridge could not use its own uplink at
|
|
105
|
+
all, and any request that failed below HTTP was reported to the origin node as
|
|
106
|
+
`HTTP 65535`.
|
|
107
|
+
|
|
108
|
+
### Fixed
|
|
109
|
+
|
|
110
|
+
- **A bridge could not reach the Internet through its own uplink** (#445).
|
|
111
|
+
`sendToInternet()` serves a request locally only when `hasLocalInternet()` is
|
|
112
|
+
true. That flag is set by the Internet health checker, and `initAsBridge()`
|
|
113
|
+
never started it — only `initAsSharedGateway()` did — so on a bridge it was
|
|
114
|
+
false for the life of the node. Every request the bridge itself made fell
|
|
115
|
+
through to the mesh-routing path and failed with *"No active mesh
|
|
116
|
+
connections"* whenever no peer had joined yet, which is exactly what a
|
|
117
|
+
single-node sketch does at start-up. `initAsBridge()` now starts the checker,
|
|
118
|
+
without touching a `setInternetCheckTarget()` the sketch made before `init()`.
|
|
119
|
+
|
|
120
|
+
- **A request that never reached a server was reported as `HTTP 65535`** (#446).
|
|
121
|
+
`HTTPClient` returns an `int`: positive is an HTTP status, negative is one of
|
|
122
|
+
its own transport errors (`-1` refused, `-11` read timeout). The gateway
|
|
123
|
+
stored that in a `uint16_t`, so `-1` wrapped to 65535 — a value that passes
|
|
124
|
+
`httpCode > 0`. Three things followed: the origin node was told "HTTP 65535",
|
|
125
|
+
the `errorToString()` branch was unreachable, and `handleGatewayAck()` filed a
|
|
126
|
+
retryable network failure as a permanent HTTP status. A transport failure now
|
|
127
|
+
arrives as `httpStatus 0` with the real reason in the error string, which is
|
|
128
|
+
what the origin node already treats as retryable.
|
|
129
|
+
|
|
130
|
+
- **A lone gateway consumed its retries without issuing a single request.**
|
|
131
|
+
`retryInternetRequest()` knew only the remote path: it required an active mesh
|
|
132
|
+
connection and then routed to a discovered bridge, so a gateway serving its
|
|
133
|
+
own request satisfied neither check and reported *"Max retries exceeded"* in
|
|
134
|
+
place of the transport error that actually happened — the same dishonest
|
|
135
|
+
failure as #446, one layer up. A retry whose gateway is this node now goes
|
|
136
|
+
back to the local handler.
|
|
137
|
+
|
|
138
|
+
### Changed
|
|
139
|
+
|
|
140
|
+
- The HTTP result classification moved into `gateway::classifyHttpResult()`, so
|
|
141
|
+
the desktop test suite exercises the same function `wifi.hpp` calls. The
|
|
142
|
+
previous test duplicated the classification — and duplicated the `uint16_t`
|
|
143
|
+
with it, which is why it mirrored #446 instead of catching it.
|
|
144
|
+
|
|
145
|
+
### Validated
|
|
146
|
+
|
|
147
|
+
Both fixes are asserted on the ESP32 farm, not only in desktop tests: a bridge
|
|
148
|
+
must report Internet on its own uplink, must relay its own request through it,
|
|
149
|
+
must do so with **no mesh peer at all**, and must return `httpStatus 0` with a
|
|
150
|
+
real reason for a refused port, an unresolvable host and a destination that
|
|
151
|
+
answers past the timeout. Those rows did not exist when 2.0.1 shipped — the
|
|
152
|
+
gateway suite passed on the run that carried both bugs.
|
|
153
|
+
|
|
154
|
+
|
|
10
155
|
## [2.0.1] - 2026-09-07
|
|
11
156
|
|
|
12
157
|
A packaging and documentation release. **No library behaviour changed**: the
|
package/CONTRIBUTING.md
CHANGED
|
@@ -55,6 +55,21 @@ and OTA behaviour is validated on the Alteriom hardware-in-the-loop farm; a
|
|
|
55
55
|
maintainer runs it on a pull request by adding the `run-hil` label, and the
|
|
56
56
|
release gate is three consecutive clean runs of the whole suite.
|
|
57
57
|
|
|
58
|
+
### The HTTP test point
|
|
59
|
+
|
|
60
|
+
`test/mock-http-server/server.py` is the controlled Internet destination for
|
|
61
|
+
`sendToInternet()` tests at every level. It answers the way real services do,
|
|
62
|
+
including the ways they get it wrong (its CallMeBot emulation returns refusals
|
|
63
|
+
as HTTP 201 and 203, as the real API does), and it keeps a delivery ledger:
|
|
64
|
+
`GET /requests/{tag}` says whether the service actually accepted a request.
|
|
65
|
+
The desktop CI job starts one and exports `PAINLESSMESH_TESTPOINT`, and
|
|
66
|
+
`catch_issue450_testpoint_semantics` makes real HTTP requests to it and
|
|
67
|
+
requires the library's verdict to match the ledger. The farm's gateway probe
|
|
68
|
+
serves the same routes with the same record shape, so a hardware row and a
|
|
69
|
+
desktop scenario are measured against one source of truth. When a gateway bug
|
|
70
|
+
comes in, add the service behaviour that exposed it to the server first, then
|
|
71
|
+
the test that fails against it.
|
|
72
|
+
|
|
58
73
|
### Adding tests for new features
|
|
59
74
|
|
|
60
75
|
1. **Unit tests**: add to `test/catch/` for new components.
|
package/README.md
CHANGED
|
@@ -596,9 +596,13 @@ These are the message types used by applications built on painlessMesh:
|
|
|
596
596
|
- **Event Coordination** - Synchronized displays, distributed processing
|
|
597
597
|
- **Bridge Networks** - Connect mesh to WiFi/Internet/MQTT - [📖 Bridge Guide](BRIDGE_TO_INTERNET.md)
|
|
598
598
|
|
|
599
|
-
## Latest Release: v2.0.
|
|
599
|
+
## Latest Release: v2.0.3 (September 11, 2026)
|
|
600
600
|
|
|
601
|
-
|
|
601
|
+
**`sendToInternet()` fixes — upgrade if any node of yours is a bridge or relays to the Internet.** On 2.0.2 a bridge's own sends were refused with "No active mesh connections" for the first 30 s after `initAsBridge()`, because the health check's first probe ran while the station was still associating. The gateway also decided delivery on the HTTP status alone: CallMeBot answers a refusal with HTTP 201, so a message it refused could be reported as sent, and every failure reached the origin node as a bare number. The gateway now reads the start of the response body, counts only 200/201/202/204 without a refusing body as delivered, and hands every failure to your callback with the service's own words. A destination whose name does not resolve no longer stalls an ESP32 gateway on every attempt: it is refused for 60 s after one failed lookup. And a bridge that reboots, crashes or is reflashed as a regular node no longer swallows the requests its peers still send it for a minute: it answers that it is not a gateway, and the sender moves to the next one at once.
|
|
602
|
+
|
|
603
|
+
**2.0.2 — two gateway fixes.** On 2.0.1 a bridge could not reach the Internet through its own uplink at all (`initAsBridge()` never started the health checker that `sendToInternet()`'s local path depends on, so a bridge with no peer yet failed with "No active mesh connections"), and any request that failed *below* HTTP — refused, unresolvable, timed out — was reported to the origin node as `HTTP 65535`, a truncated `-1`, which also stopped it from being retried. Both are now asserted on the hardware rig, including a bridge with no mesh peer at all.
|
|
604
|
+
|
|
605
|
+
**2.0.1 — a packaging fix over 2.0.0, no library behaviour changed.** 2.0.0's installation instructions named a PlatformIO package that has no 2.0.0 (`alteriom/…` stops at 1.10.0; releases go out under `sparck75`), and its GitHub release carried no library archive because the upload was refused by an immutable release.
|
|
602
606
|
|
|
603
607
|
**2.0.0 — delivery confirmation, a unified send path, and a mesh that holds together on real hardware**
|
|
604
608
|
|
package/RELEASE_GUIDE.md
CHANGED
|
@@ -38,13 +38,23 @@ These files must always contain the same semantic version:
|
|
|
38
38
|
- `library.properties`
|
|
39
39
|
- `library.json`
|
|
40
40
|
- `package.json`
|
|
41
|
+
- `package-lock.json` (the root `version` and `packages[""].version`)
|
|
42
|
+
- `src/AlteriomPainlessMesh.h` (the version string and the MAJOR/MINOR/PATCH
|
|
43
|
+
defines a sketch compiles against)
|
|
44
|
+
- `doxygen/Doxyfile` (`PROJECT_NUMBER`, the number on every generated API page)
|
|
41
45
|
|
|
42
|
-
Use the repository script to change them together
|
|
46
|
+
Use the repository script to change them together; it updates all six and then
|
|
47
|
+
verifies they agree:
|
|
43
48
|
|
|
44
49
|
```bash
|
|
45
50
|
./scripts/bump-version.sh patch
|
|
51
|
+
./scripts/bump-version.sh patch 2.0.3 --yes # no prompt, for CI or an agent
|
|
46
52
|
```
|
|
47
53
|
|
|
54
|
+
`jq` is used when it is installed and awk otherwise, and both paths change only
|
|
55
|
+
the package's own version — never a dependency range. `test/ci/test_bump_version.sh`
|
|
56
|
+
holds that, and the Code Quality job runs it.
|
|
57
|
+
|
|
48
58
|
You may use `minor`, `major`, or an explicit version when the release plan
|
|
49
59
|
requires it. Version 2.0 patch releases must remain wire-compatible with the
|
|
50
60
|
2.0 protocol.
|
|
@@ -82,12 +92,24 @@ The pull request must also pass all required GitHub checks, including:
|
|
|
82
92
|
- formatting and documentation checks
|
|
83
93
|
- CodeQL
|
|
84
94
|
- simulator scenarios
|
|
85
|
-
-
|
|
95
|
+
- hardware-in-the-loop coverage on the six-family board bank when the change
|
|
86
96
|
touches radio, routing, gateway, OTA, or platform-specific behavior
|
|
87
97
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
98
|
+
### How the hardware result arrives
|
|
99
|
+
|
|
100
|
+
The Alteriom ESP32 farm is a separate, private repository, so nothing in this
|
|
101
|
+
one triggers it — a public workflow can hold no credential for it. The farm
|
|
102
|
+
watches instead: it picks up `main` and open non-fork pull requests that touch
|
|
103
|
+
`src/`, `examples/`, `test/` or the library metadata, runs the simulator gate
|
|
104
|
+
and then the physical suite, and posts the verdict back as a **`farm/hil`
|
|
105
|
+
commit status** with a link to the run. Expect it within roughly half an hour
|
|
106
|
+
of a push; nothing is required of the author.
|
|
107
|
+
|
|
108
|
+
For a hardware defect, link that run in the pull request. A unit test alone is
|
|
109
|
+
not sufficient evidence for a radio or multi-device timing fix — and a desktop
|
|
110
|
+
test that re-implements the logic it is checking is not evidence at all
|
|
111
|
+
(`catch_http_status_codes.cpp` copied the `uint16_t` that caused #446 and so
|
|
112
|
+
mirrored the bug instead of catching it).
|
|
91
113
|
|
|
92
114
|
## Review gate
|
|
93
115
|
|
|
@@ -187,10 +187,10 @@ The callback provides `httpStatus` to indicate the result:
|
|
|
187
187
|
|
|
188
188
|
**FAILURE (success = false):**
|
|
189
189
|
- `203 Non-Authoritative Information` - **Cached/proxied response, NOT actual delivery**
|
|
190
|
-
- `4xx` - Client error (bad request, unauthorized, not found, etc.)
|
|
191
|
-
- `5xx` - Server error (service unavailable, gateway timeout, etc.)
|
|
192
190
|
|
|
193
|
-
⚠️ **
|
|
191
|
+
⚠️ **The status alone does not say whether WhatsApp got the message.** CallMeBot answers a refusal (for example "Too many requests") with HTTP 201 or HTTP 203, the same error page under both. The gateway therefore reads the start of the response body: a 2xx whose body says the service refused the request is reported as a failure, with the service's own words in the callback's `error` string, and HTTP 203 stays a failure because a proxy, not the WhatsApp API, may have answered. If your callback prints `HTTP 201: service refused the request: Oops! Too many requests...`, that is CallMeBot talking, and the message was **NOT delivered**. Likewise `HTTP 208: not a delivery the gateway can confirm: ...`: CallMeBot answers 208 to messages that never arrive (issues #450 and #452), so only 200, 201, 202 and 204 with a clean body are reported as sent, and the rest of the line is whatever the service said.
|
|
192
|
+
|
|
193
|
+
Also replace `CLOUD_URL` at the top of the sketch: the placeholder `api.example.com` does not resolve, and until you change it the sketch skips the cloud send and says so instead of reporting `connection refused` every minute.
|
|
194
194
|
|
|
195
195
|
## Files
|
|
196
196
|
|
|
@@ -74,6 +74,19 @@
|
|
|
74
74
|
#define WHATSAPP_PHONE "+1234567890" // Your phone number with country code
|
|
75
75
|
#define WHATSAPP_APIKEY "your_api_key" // Your Callmebot API key
|
|
76
76
|
|
|
77
|
+
// ============================================
|
|
78
|
+
// Cloud API Configuration
|
|
79
|
+
// ============================================
|
|
80
|
+
// The endpoint the periodic sensor readings are POSTed to. Replace it with
|
|
81
|
+
// your own; the placeholder below does not resolve, so until you do the
|
|
82
|
+
// sketch skips the cloud send and says so rather than reporting
|
|
83
|
+
// "connection refused" every minute (issue #450).
|
|
84
|
+
// Example endpoints:
|
|
85
|
+
// - ThingsBoard: "https://demo.thingsboard.io/api/v1/YOUR_TOKEN/telemetry"
|
|
86
|
+
// - AWS IoT: "https://YOUR_ENDPOINT.iot.us-east-1.amazonaws.com/topics/sensors"
|
|
87
|
+
// - Custom API: "https://api.yourserver.com/sensors/data"
|
|
88
|
+
#define CLOUD_URL "https://api.example.com/sensors"
|
|
89
|
+
|
|
77
90
|
// ============================================
|
|
78
91
|
// Sensor Simulation Configuration
|
|
79
92
|
// ============================================
|
|
@@ -115,6 +128,15 @@ String urlEncode(const String& str);
|
|
|
115
128
|
// Task to periodically send sensor data (simulated)
|
|
116
129
|
Task taskSendSensorData(60000, TASK_FOREVER, &sendSensorDataToCloud);
|
|
117
130
|
|
|
131
|
+
// The startup WhatsApp used to be a single 30 s one-shot. A regular node that
|
|
132
|
+
// took longer than that to find the mesh -- following a bridge to another
|
|
133
|
+
// channel takes about a minute -- fired it with no gateway in sight and never
|
|
134
|
+
// tried again (issue #450). It now retries until a gateway with Internet is
|
|
135
|
+
// known, then disables itself.
|
|
136
|
+
void sendStartupAlert();
|
|
137
|
+
Task taskStartupAlert(30000, TASK_FOREVER, &sendStartupAlert);
|
|
138
|
+
String startupMsg;
|
|
139
|
+
|
|
118
140
|
// ============================================
|
|
119
141
|
// URL Encoding Helper
|
|
120
142
|
// ============================================
|
|
@@ -184,13 +206,12 @@ void sendAlertToWhatsApp(String message) {
|
|
|
184
206
|
|
|
185
207
|
// Use sendToInternet() to route the request through a gateway
|
|
186
208
|
// The callback will be invoked when we get a response (or timeout)
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
//
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
// it indicates a cached/proxied response, not actual delivery to WhatsApp.
|
|
209
|
+
//
|
|
210
|
+
// The gateway reads the response body, not only the status: CallMeBot
|
|
211
|
+
// answers a refusal ("Too many requests") with HTTP 201 or 203, so a 2xx
|
|
212
|
+
// alone proves nothing. A refusal arrives here as success=false with the
|
|
213
|
+
// service's own words in `error`; HTTP 203 stays a failure because a proxy,
|
|
214
|
+
// not WhatsApp, may have answered.
|
|
194
215
|
uint32_t msgId = mesh.sendToInternet(
|
|
195
216
|
url,
|
|
196
217
|
"", // No payload needed for GET request - params are in URL
|
|
@@ -249,14 +270,13 @@ void sendSensorDataToCloud() {
|
|
|
249
270
|
return;
|
|
250
271
|
}
|
|
251
272
|
|
|
252
|
-
// Send to cloud API
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
273
|
+
// Send to the cloud API configured at the top of the sketch
|
|
274
|
+
String cloudUrl = CLOUD_URL;
|
|
275
|
+
if (cloudUrl.indexOf("example.com") >= 0) {
|
|
276
|
+
Serial.println(" (CLOUD_URL is still the placeholder; set your endpoint to send readings)");
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
260
280
|
uint32_t msgId = mesh.sendToInternet(
|
|
261
281
|
cloudUrl,
|
|
262
282
|
payload,
|
|
@@ -276,6 +296,21 @@ void sendSensorDataToCloud() {
|
|
|
276
296
|
// Mesh Callbacks
|
|
277
297
|
// ============================================
|
|
278
298
|
|
|
299
|
+
/**
|
|
300
|
+
* Send the startup notification once a gateway with Internet is known.
|
|
301
|
+
*
|
|
302
|
+
* Runs every 30 s from taskStartupAlert until it has handed the message to
|
|
303
|
+
* sendToInternet(), then disables itself.
|
|
304
|
+
*/
|
|
305
|
+
void sendStartupAlert() {
|
|
306
|
+
if (!mesh.hasInternetConnection()) {
|
|
307
|
+
Serial.println("(startup WhatsApp waiting for a gateway with Internet; retrying in 30 s)");
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
sendAlertToWhatsApp(startupMsg);
|
|
311
|
+
taskStartupAlert.disable();
|
|
312
|
+
}
|
|
313
|
+
|
|
279
314
|
void receivedCallback(uint32_t from, String& msg) {
|
|
280
315
|
Serial.printf("📨 Received from %u: %s\n", from, msg.c_str());
|
|
281
316
|
}
|
|
@@ -347,14 +382,14 @@ void setup() {
|
|
|
347
382
|
Serial.printf("Is Bridge: %s\n", mesh.isBridge() ? "YES" : "NO");
|
|
348
383
|
Serial.println("================================================\n");
|
|
349
384
|
|
|
350
|
-
// Send a startup notification via WhatsApp (demonstrates sendToInternet)
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
//
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
385
|
+
// Send a startup notification via WhatsApp (demonstrates sendToInternet).
|
|
386
|
+
// First attempt in 30 s, then every 30 s until a gateway with Internet is
|
|
387
|
+
// known: a bridge can use its own uplink at once, a regular node has to
|
|
388
|
+
// find the mesh first.
|
|
389
|
+
startupMsg = "🚀 Node " + String(mesh.getNodeId()) + " started!";
|
|
390
|
+
Serial.println("Will send the startup WhatsApp as soon as a gateway with Internet is known (first try in 30 s)...\n");
|
|
391
|
+
userScheduler.addTask(taskStartupAlert);
|
|
392
|
+
taskStartupAlert.enableDelayed();
|
|
358
393
|
}
|
|
359
394
|
|
|
360
395
|
// ============================================
|
package/library.json
CHANGED
package/library.properties
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
name=Alteriom PainlessMesh
|
|
2
|
-
version=2.0.
|
|
2
|
+
version=2.0.3
|
|
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": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
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",
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
/**
|
|
30
30
|
* @brief AlteriomPainlessMesh library version information
|
|
31
31
|
*/
|
|
32
|
-
#define ALTERIOM_PAINLESS_MESH_VERSION "2.0.
|
|
32
|
+
#define ALTERIOM_PAINLESS_MESH_VERSION "2.0.3"
|
|
33
33
|
#define ALTERIOM_PAINLESS_MESH_VERSION_MAJOR 2
|
|
34
34
|
#define ALTERIOM_PAINLESS_MESH_VERSION_MINOR 0
|
|
35
|
-
#define ALTERIOM_PAINLESS_MESH_VERSION_PATCH
|
|
35
|
+
#define ALTERIOM_PAINLESS_MESH_VERSION_PATCH 3
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* @brief Library description and usage information
|