@alteriom/painlessmesh 2.0.0 → 2.0.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 +114 -0
- package/README.md +9 -5
- package/RELEASE_GUIDE.md +22 -5
- package/examples/sendToInternet/platformio.ini +2 -2
- 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 +42 -18
- package/src/painlessmesh/gateway.hpp +48 -0
- package/src/painlessmesh/mesh.hpp +66 -16
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,115 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.0.2] - 2026-09-08
|
|
11
|
+
|
|
12
|
+
Two gateway defects that reached users on 2.0.1, both in code the desktop test
|
|
13
|
+
suite cannot compile, and both now asserted on real hardware. **Upgrade if a
|
|
14
|
+
node of yours is a bridge**: on 2.0.1 a bridge could not use its own uplink at
|
|
15
|
+
all, and any request that failed below HTTP was reported to the origin node as
|
|
16
|
+
`HTTP 65535`.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- **A bridge could not reach the Internet through its own uplink** (#445).
|
|
21
|
+
`sendToInternet()` serves a request locally only when `hasLocalInternet()` is
|
|
22
|
+
true. That flag is set by the Internet health checker, and `initAsBridge()`
|
|
23
|
+
never started it — only `initAsSharedGateway()` did — so on a bridge it was
|
|
24
|
+
false for the life of the node. Every request the bridge itself made fell
|
|
25
|
+
through to the mesh-routing path and failed with *"No active mesh
|
|
26
|
+
connections"* whenever no peer had joined yet, which is exactly what a
|
|
27
|
+
single-node sketch does at start-up. `initAsBridge()` now starts the checker,
|
|
28
|
+
without touching a `setInternetCheckTarget()` the sketch made before `init()`.
|
|
29
|
+
|
|
30
|
+
- **A request that never reached a server was reported as `HTTP 65535`** (#446).
|
|
31
|
+
`HTTPClient` returns an `int`: positive is an HTTP status, negative is one of
|
|
32
|
+
its own transport errors (`-1` refused, `-11` read timeout). The gateway
|
|
33
|
+
stored that in a `uint16_t`, so `-1` wrapped to 65535 — a value that passes
|
|
34
|
+
`httpCode > 0`. Three things followed: the origin node was told "HTTP 65535",
|
|
35
|
+
the `errorToString()` branch was unreachable, and `handleGatewayAck()` filed a
|
|
36
|
+
retryable network failure as a permanent HTTP status. A transport failure now
|
|
37
|
+
arrives as `httpStatus 0` with the real reason in the error string, which is
|
|
38
|
+
what the origin node already treats as retryable.
|
|
39
|
+
|
|
40
|
+
- **A lone gateway consumed its retries without issuing a single request.**
|
|
41
|
+
`retryInternetRequest()` knew only the remote path: it required an active mesh
|
|
42
|
+
connection and then routed to a discovered bridge, so a gateway serving its
|
|
43
|
+
own request satisfied neither check and reported *"Max retries exceeded"* in
|
|
44
|
+
place of the transport error that actually happened — the same dishonest
|
|
45
|
+
failure as #446, one layer up. A retry whose gateway is this node now goes
|
|
46
|
+
back to the local handler.
|
|
47
|
+
|
|
48
|
+
### Changed
|
|
49
|
+
|
|
50
|
+
- The HTTP result classification moved into `gateway::classifyHttpResult()`, so
|
|
51
|
+
the desktop test suite exercises the same function `wifi.hpp` calls. The
|
|
52
|
+
previous test duplicated the classification — and duplicated the `uint16_t`
|
|
53
|
+
with it, which is why it mirrored #446 instead of catching it.
|
|
54
|
+
|
|
55
|
+
### Validated
|
|
56
|
+
|
|
57
|
+
Both fixes are asserted on the ESP32 farm, not only in desktop tests: a bridge
|
|
58
|
+
must report Internet on its own uplink, must relay its own request through it,
|
|
59
|
+
must do so with **no mesh peer at all**, and must return `httpStatus 0` with a
|
|
60
|
+
real reason for a refused port, an unresolvable host and a destination that
|
|
61
|
+
answers past the timeout. Those rows did not exist when 2.0.1 shipped — the
|
|
62
|
+
gateway suite passed on the run that carried both bugs.
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
## [2.0.1] - 2026-09-07
|
|
66
|
+
|
|
67
|
+
A packaging and documentation release. **No library behaviour changed**: the
|
|
68
|
+
mesh, routing, gateway, failover and OTA code of 2.0.1 is 2.0.0's, and the only
|
|
69
|
+
edit under `src/` is the version macro. It exists to repair two defects in what
|
|
70
|
+
2.0.0 *shipped*, both of which cost a user time before they ever compile
|
|
71
|
+
anything.
|
|
72
|
+
|
|
73
|
+
### Fixed
|
|
74
|
+
|
|
75
|
+
- **The installation instructions shipped in 2.0.0 name a PlatformIO package
|
|
76
|
+
that has no 2.0.0.** The README, user guide and documentation site gave
|
|
77
|
+
`alteriom/AlteriomPainlessMesh`, and that registry owner stops at 1.10.0 — its
|
|
78
|
+
account is not one this project can publish from. Every automated release
|
|
79
|
+
since 1.7.6 went out under `sparck75`, which is now what the documentation
|
|
80
|
+
names. **Name the owner in `lib_deps`**: the bare name `AlteriomPainlessMesh`
|
|
81
|
+
matches both owners and PlatformIO warns rather than choosing.
|
|
82
|
+
|
|
83
|
+
```ini
|
|
84
|
+
lib_deps =
|
|
85
|
+
sparck75/AlteriomPainlessMesh@^2.0.0
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
- **The v2.0.0 GitHub release carries no library archive.** This repository
|
|
89
|
+
publishes immutable releases, so the workflow's upload step — which ran after
|
|
90
|
+
the release was created — was refused with HTTP 422. The job failed before its
|
|
91
|
+
release decision, which also skipped the npm, GitHub Packages and PlatformIO
|
|
92
|
+
jobs; those three were published by hand. The archive is now attached in the
|
|
93
|
+
same call that creates the release, and the decision to publish is taken
|
|
94
|
+
before any step that can fail, so a failed upload can no longer suppress the
|
|
95
|
+
publications.
|
|
96
|
+
|
|
97
|
+
- **PlatformIO publication went under the token's account rather than the
|
|
98
|
+
organisation.** `pio pkg publish` files a package under the account unless
|
|
99
|
+
`--owner` names the owner, and the workflow never passed it — which is how the
|
|
100
|
+
duplicate ownership above arose in the first place. It now passes `--owner`,
|
|
101
|
+
checks the version against that owner's own version table (the previous check
|
|
102
|
+
read a `Version:` line `pio pkg show` never prints), publishes the release
|
|
103
|
+
tag's sources rather than whichever branch was dispatched, and treats an
|
|
104
|
+
already-listed version as success instead of a failure.
|
|
105
|
+
|
|
106
|
+
- **`scripts/validate-release.sh` reported a version mismatch that was not
|
|
107
|
+
there.** Without `jq` it fell back to a grep that matches every `"version"`
|
|
108
|
+
line in `library.json`, dependencies included, so the value it compared was
|
|
109
|
+
multi-line. It takes the first match now — the top-level one. CI was never
|
|
110
|
+
affected; it has `jq`.
|
|
111
|
+
|
|
112
|
+
### Upgrading
|
|
113
|
+
|
|
114
|
+
Nothing in the library behaves differently, so upgrading from 2.0.0 is optional.
|
|
115
|
+
Take it if you install through PlatformIO, or if you want the release archive
|
|
116
|
+
attached to the GitHub release. Everything in the 2.0.0 entry below still
|
|
117
|
+
applies, including its *Before you upgrade* section.
|
|
118
|
+
|
|
10
119
|
## [2.0.0] - 2026-09-07
|
|
11
120
|
|
|
12
121
|
painlessMesh 2.0 is a major release. It adds per-message delivery
|
|
@@ -317,6 +426,11 @@ processes scan results from the loop for that reason (see the ESP32 entry).
|
|
|
317
426
|
every PR like the other twenty.
|
|
318
427
|
- `keywords.txt` now lists the bridge, gateway, failover, queue and capacity
|
|
319
428
|
API so the Arduino IDE highlights it.
|
|
429
|
+
- **PlatformIO package owner.** 2.0.0 is published as `sparck75/AlteriomPainlessMesh`,
|
|
430
|
+
the owner every automated release since 1.7.6 went under. The registry also
|
|
431
|
+
holds `alteriom/AlteriomPainlessMesh`, which stops at 1.10.0: its account is
|
|
432
|
+
not one the project can publish from. Name the owner in `lib_deps`; the bare
|
|
433
|
+
name matches both.
|
|
320
434
|
|
|
321
435
|
### Added (post-review series)
|
|
322
436
|
|
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
[](https://github.com/Alteriom/painlessMesh/actions/workflows/release.yml)
|
|
12
12
|
[](https://github.com/Alteriom/painlessMesh/releases)
|
|
13
13
|
[](https://www.npmjs.com/package/@alteriom/painlessmesh)
|
|
14
|
-
[](https://registry.platformio.org/libraries/sparck75/AlteriomPainlessMesh)
|
|
15
15
|
|
|
16
16
|
</div>
|
|
17
17
|
|
|
@@ -410,11 +410,11 @@ git clone https://github.com/Alteriom/painlessMesh.git AlteriomPainlessMesh
|
|
|
410
410
|
|
|
411
411
|
### PlatformIO
|
|
412
412
|
|
|
413
|
-
The library is published on the [PlatformIO registry](https://registry.platformio.org/libraries/
|
|
413
|
+
The library is published on the [PlatformIO registry](https://registry.platformio.org/libraries/sparck75/AlteriomPainlessMesh) as `sparck75/AlteriomPainlessMesh`. The registry also lists an `alteriom/AlteriomPainlessMesh` that stops at 1.10.0; its owner account is not one the project can publish from, so 2.0.0 and later live under `sparck75`, which has carried every automated release since 1.7.6. Name the owner in `lib_deps`, since the bare name matches both:
|
|
414
414
|
|
|
415
415
|
```ini
|
|
416
416
|
lib_deps =
|
|
417
|
-
|
|
417
|
+
sparck75/AlteriomPainlessMesh@^2.0.0
|
|
418
418
|
```
|
|
419
419
|
|
|
420
420
|
### Dependencies
|
|
@@ -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.2 (September 8, 2026)
|
|
600
600
|
|
|
601
|
-
**
|
|
601
|
+
**Two gateway fixes — upgrade if any node of yours is a bridge.** 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.
|
|
602
|
+
|
|
603
|
+
**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.
|
|
604
|
+
|
|
605
|
+
**2.0.0 — delivery confirmation, a unified send path, and a mesh that holds together on real hardware**
|
|
602
606
|
|
|
603
607
|
- `sendSingle()` and `sendBroadcast()` accept a delivery callback — it fires with `delivered=true` and the round-trip latency on acknowledgment, or `delivered=false` on timeout — and a `SendOptions` struct that carries a priority and a callback in one call. Priority is kept across hops.
|
|
604
608
|
- Thirty-odd defects in gateway failover, channel following, routing and the station scan, every one found in the serial logs of a six-family hardware rig and fixed there: a bridge that stops says so, the mesh follows an elected bridge to its channel and treats it as home, a node is in one place in every neighbour's tree, a dead connection is not a route, a stale scan does not consume a live one.
|
package/RELEASE_GUIDE.md
CHANGED
|
@@ -21,7 +21,12 @@ the version commit reaches `main`.
|
|
|
21
21
|
workflow owns tags and publication.
|
|
22
22
|
|
|
23
23
|
The workflow tags only when the push to `main` carries a version-file
|
|
24
|
-
change or a head commit whose message starts with `release:`.
|
|
24
|
+
change or a head commit whose message starts with `release:`. The
|
|
25
|
+
repository publishes immutable releases: a release takes its assets and
|
|
26
|
+
notes when it is created and cannot be edited afterwards, so a failed
|
|
27
|
+
publication is repaired by `manual-publish.yml` (npm, GitHub Packages) and
|
|
28
|
+
`platformio-publish.yml`, both dispatched with the tag, not by re-running
|
|
29
|
+
the release workflow. When the
|
|
25
30
|
version metadata was bumped earlier in the cycle (as for 2.0.0), merge the
|
|
26
31
|
release pull request with a commit titled `release: vX.Y.Z` — a squash or
|
|
27
32
|
merge commit with that title — or the push is ignored.
|
|
@@ -77,12 +82,24 @@ The pull request must also pass all required GitHub checks, including:
|
|
|
77
82
|
- formatting and documentation checks
|
|
78
83
|
- CodeQL
|
|
79
84
|
- simulator scenarios
|
|
80
|
-
-
|
|
85
|
+
- hardware-in-the-loop coverage on the six-family board bank when the change
|
|
81
86
|
touches radio, routing, gateway, OTA, or platform-specific behavior
|
|
82
87
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
### How the hardware result arrives
|
|
89
|
+
|
|
90
|
+
The Alteriom ESP32 farm is a separate, private repository, so nothing in this
|
|
91
|
+
one triggers it — a public workflow can hold no credential for it. The farm
|
|
92
|
+
watches instead: it picks up `main` and open non-fork pull requests that touch
|
|
93
|
+
`src/`, `examples/`, `test/` or the library metadata, runs the simulator gate
|
|
94
|
+
and then the physical suite, and posts the verdict back as a **`farm/hil`
|
|
95
|
+
commit status** with a link to the run. Expect it within roughly half an hour
|
|
96
|
+
of a push; nothing is required of the author.
|
|
97
|
+
|
|
98
|
+
For a hardware defect, link that run in the pull request. A unit test alone is
|
|
99
|
+
not sufficient evidence for a radio or multi-device timing fix — and a desktop
|
|
100
|
+
test that re-implements the logic it is checking is not evidence at all
|
|
101
|
+
(`catch_http_status_codes.cpp` copied the `uint16_t` that caused #446 and so
|
|
102
|
+
mirrored the bug instead of catching it).
|
|
86
103
|
|
|
87
104
|
## Review gate
|
|
88
105
|
|
|
@@ -9,7 +9,7 @@ platform = espressif32
|
|
|
9
9
|
board = esp32dev
|
|
10
10
|
framework = arduino
|
|
11
11
|
lib_deps =
|
|
12
|
-
|
|
12
|
+
sparck75/AlteriomPainlessMesh
|
|
13
13
|
bblanchon/ArduinoJson@^7.0.0
|
|
14
14
|
monitor_speed = 115200
|
|
15
15
|
|
|
@@ -18,6 +18,6 @@ platform = espressif8266
|
|
|
18
18
|
board = nodemcuv2
|
|
19
19
|
framework = arduino
|
|
20
20
|
lib_deps =
|
|
21
|
-
|
|
21
|
+
sparck75/AlteriomPainlessMesh
|
|
22
22
|
bblanchon/ArduinoJson@^7.0.0
|
|
23
23
|
monitor_speed = 115200
|
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.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": "2.0.
|
|
3
|
+
"version": "2.0.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",
|
|
@@ -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.2"
|
|
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 2
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* @brief Library description and usage information
|
package/src/arduino/wifi.hpp
CHANGED
|
@@ -492,6 +492,20 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
492
492
|
// Step 6: Setup gateway Internet handler
|
|
493
493
|
initGatewayInternetHandler();
|
|
494
494
|
|
|
495
|
+
// Step 7: Start the health checker that drives hasLocalInternet().
|
|
496
|
+
// Without it that flag stays false for the life of the node, so the
|
|
497
|
+
// local short-circuit in sendToInternet() never fires and a bridge
|
|
498
|
+
// asking its own uplink fell through to the mesh-routing path -- which
|
|
499
|
+
// fails with "No active mesh connections" whenever the bridge has no
|
|
500
|
+
// peer yet, even though the router link is up (issue #445).
|
|
501
|
+
// initAsSharedGateway() has always done this; initAsBridge() did not.
|
|
502
|
+
//
|
|
503
|
+
// No configureInternetHealthCheck() call here: a bridge has no
|
|
504
|
+
// SharedGatewayConfig, the checker's own defaults (8.8.8.8:53, 30 s,
|
|
505
|
+
// 5 s) already match that struct's, and configuring would clobber any
|
|
506
|
+
// setInternetCheckTarget() the sketch made before init.
|
|
507
|
+
enableInternetHealthCheck();
|
|
508
|
+
|
|
495
509
|
Log(STARTUP, "=== Bridge Mode Active ===\n");
|
|
496
510
|
Log(STARTUP, " Mesh SSID: %s\n", meshSSID.c_str());
|
|
497
511
|
Log(STARTUP, " Mesh Channel: %d%s\n", detectedChannel,
|
|
@@ -2864,7 +2878,15 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
2864
2878
|
http.setTimeout(GATEWAY_HTTP_TIMEOUT_MS);
|
|
2865
2879
|
|
|
2866
2880
|
bool success = false;
|
|
2867
|
-
|
|
2881
|
+
// HTTPClient returns an int: a positive value is an HTTP status
|
|
2882
|
+
// code, a negative one is its own transport error (HTTPC_ERROR_*,
|
|
2883
|
+
// -1 for a refused connection, -11 for a read timeout, ...). This
|
|
2884
|
+
// was a uint16_t, which wrapped -1 to 65535 -- a value that passes
|
|
2885
|
+
// `httpCode > 0`, so every transport failure was reported to the
|
|
2886
|
+
// origin node as "HTTP 65535" (issue #446), the errorToString()
|
|
2887
|
+
// branch below was unreachable, and handleGatewayAck() classified a
|
|
2888
|
+
// transient network failure as a non-retryable HTTP status.
|
|
2889
|
+
int httpCode = 0;
|
|
2868
2890
|
TSTRING error = "";
|
|
2869
2891
|
|
|
2870
2892
|
#ifdef ESP8266
|
|
@@ -2903,21 +2925,22 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
2903
2925
|
httpCode = http.GET();
|
|
2904
2926
|
}
|
|
2905
2927
|
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2928
|
+
// Only specific 2xx status codes indicate genuine success:
|
|
2929
|
+
// 200 OK, 201 Created, 202 Accepted, 204 No Content.
|
|
2930
|
+
//
|
|
2931
|
+
// Other 2xx codes like 203 (Non-Authoritative Information) often
|
|
2932
|
+
// indicate cached/proxied responses that may not represent actual
|
|
2933
|
+
// delivery to the destination service (e.g., WhatsApp API).
|
|
2934
|
+
//
|
|
2935
|
+
// 3xx redirects are not automatically followed.
|
|
2936
|
+
//
|
|
2937
|
+
// The classification lives in painlessmesh::gateway so the desktop
|
|
2938
|
+
// test suite can exercise it; this file is ESP-only and never
|
|
2939
|
+
// compiled by the Catch2 build.
|
|
2940
|
+
const auto outcome = gateway::classifyHttpResult(httpCode);
|
|
2941
|
+
success = outcome.success;
|
|
2942
|
+
|
|
2943
|
+
if (!outcome.transportError) {
|
|
2921
2944
|
if (success) {
|
|
2922
2945
|
Log(COMMUNICATION, "HTTP request completed: code=%d\n", httpCode);
|
|
2923
2946
|
} else if (httpCode >= 200 && httpCode < 300) {
|
|
@@ -2957,8 +2980,9 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
2957
2980
|
|
|
2958
2981
|
http.end();
|
|
2959
2982
|
|
|
2960
|
-
// Send acknowledgment back
|
|
2961
|
-
|
|
2983
|
+
// Send acknowledgment back. Transport errors carry status 0, the
|
|
2984
|
+
// value handleGatewayAck() treats as a retryable network error.
|
|
2985
|
+
finish(success, outcome.ackStatus, error);
|
|
2962
2986
|
#else
|
|
2963
2987
|
// Non-ESP platform - send error
|
|
2964
2988
|
finish(false, 0, "HTTP client not available on this platform");
|
|
@@ -965,6 +965,54 @@ class GatewayDataPackage : public plugin::SinglePackage {
|
|
|
965
965
|
|
|
966
966
|
};
|
|
967
967
|
|
|
968
|
+
/**
|
|
969
|
+
* @brief The outcome of a gateway HTTP request, classified for the ack
|
|
970
|
+
*
|
|
971
|
+
* HTTPClient::GET()/POST() return an int with two distinct meanings: a
|
|
972
|
+
* positive value is an HTTP status code from the destination, while a
|
|
973
|
+
* negative value is one of the client's own transport errors (HTTPC_ERROR_*,
|
|
974
|
+
* e.g. -1 for a refused connection or -11 for a read timeout). Zero is not
|
|
975
|
+
* produced by either.
|
|
976
|
+
*
|
|
977
|
+
* GatewayAckPackage::httpStatus is a uint16_t, so a negative code cannot be
|
|
978
|
+
* forwarded as-is. Transport errors are reported as httpStatus 0, which is
|
|
979
|
+
* what Mesh::handleGatewayAck() already treats as a retryable network error;
|
|
980
|
+
* the human-readable cause travels in GatewayAckPackage::error instead.
|
|
981
|
+
*/
|
|
982
|
+
struct HttpRequestOutcome {
|
|
983
|
+
/** True only for status codes that indicate genuine delivery. */
|
|
984
|
+
bool success = false;
|
|
985
|
+
|
|
986
|
+
/** Value to place in GatewayAckPackage::httpStatus (0 for transport errors). */
|
|
987
|
+
uint16_t ackStatus = 0;
|
|
988
|
+
|
|
989
|
+
/** True when the client failed before any HTTP status was received. */
|
|
990
|
+
bool transportError = false;
|
|
991
|
+
};
|
|
992
|
+
|
|
993
|
+
/**
|
|
994
|
+
* @brief Classify an HTTPClient return value for the gateway acknowledgment
|
|
995
|
+
*
|
|
996
|
+
* Only 200, 201, 202 and 204 count as success. Other 2xx codes (notably 203,
|
|
997
|
+
* Non-Authoritative Information) indicate a cached or proxied response rather
|
|
998
|
+
* than delivery to the destination service, so they are failures.
|
|
999
|
+
*
|
|
1000
|
+
* @param rawCode The int returned by HTTPClient::GET() or ::POST()
|
|
1001
|
+
* @return Classified outcome, safe to place in a GatewayAckPackage
|
|
1002
|
+
*/
|
|
1003
|
+
inline HttpRequestOutcome classifyHttpResult(int rawCode) {
|
|
1004
|
+
HttpRequestOutcome outcome;
|
|
1005
|
+
if (rawCode <= 0) {
|
|
1006
|
+
outcome.transportError = true;
|
|
1007
|
+
return outcome;
|
|
1008
|
+
}
|
|
1009
|
+
outcome.ackStatus =
|
|
1010
|
+
static_cast<uint16_t>(rawCode > 0xFFFF ? 0xFFFF : rawCode);
|
|
1011
|
+
outcome.success = (rawCode == 200 || rawCode == 201 || rawCode == 202 ||
|
|
1012
|
+
rawCode == 204);
|
|
1013
|
+
return outcome;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
968
1016
|
/**
|
|
969
1017
|
* @brief Gateway Acknowledgment Package for delivery confirmations
|
|
970
1018
|
*
|
|
@@ -1546,8 +1546,17 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
1546
1546
|
void enableInternetHealthCheck() {
|
|
1547
1547
|
using namespace logger;
|
|
1548
1548
|
if (internetHealthCheckTask != nullptr) {
|
|
1549
|
-
|
|
1550
|
-
|
|
1549
|
+
if (internetHealthCheckTask->isEnabled()) {
|
|
1550
|
+
Log(GENERAL, "enableInternetHealthCheck(): Already enabled\n");
|
|
1551
|
+
return;
|
|
1552
|
+
}
|
|
1553
|
+
// A stop()/re-init cycle -- what bridge promotion does before calling
|
|
1554
|
+
// initAsBridge() again -- disables every task and drops it from the
|
|
1555
|
+
// reusable pool, but this member still points at it. Refusing here
|
|
1556
|
+
// would leave a re-initialised node with no health check at all, and
|
|
1557
|
+
// so with hasLocalInternet() stuck false (issue #445). Re-arm instead,
|
|
1558
|
+
// releasing the spent task so the pool can reclaim it.
|
|
1559
|
+
internetHealthCheckTask = nullptr;
|
|
1551
1560
|
}
|
|
1552
1561
|
|
|
1553
1562
|
Log(GENERAL, "enableInternetHealthCheck(): Starting health check task (interval: %u ms)\n",
|
|
@@ -1678,20 +1687,8 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
1678
1687
|
request.callback = callback;
|
|
1679
1688
|
pendingInternetRequests[messageId] = request;
|
|
1680
1689
|
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
pkg.dest = this->nodeId;
|
|
1684
|
-
pkg.messageId = messageId;
|
|
1685
|
-
pkg.originNode = this->nodeId;
|
|
1686
|
-
pkg.timestamp = this->getNodeTime();
|
|
1687
|
-
pkg.priority = priority;
|
|
1688
|
-
pkg.destination = destination;
|
|
1689
|
-
pkg.payload = payload;
|
|
1690
|
-
pkg.contentType = "application/json";
|
|
1691
|
-
pkg.retryCount = 0;
|
|
1692
|
-
pkg.requiresAck = true;
|
|
1693
|
-
protocol::Variant variant(&pkg);
|
|
1694
|
-
this->callbackList.execute(protocol::GATEWAY_DATA, variant, nullptr, 0);
|
|
1690
|
+
dispatchInternetRequestLocally(messageId, priority, 0, destination,
|
|
1691
|
+
payload);
|
|
1695
1692
|
return messageId;
|
|
1696
1693
|
}
|
|
1697
1694
|
|
|
@@ -2041,6 +2038,37 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
2041
2038
|
}
|
|
2042
2039
|
}
|
|
2043
2040
|
|
|
2041
|
+
/**
|
|
2042
|
+
* Run a gateway request against this node's own uplink
|
|
2043
|
+
*
|
|
2044
|
+
* The package goes to this node's own GATEWAY_DATA handler, so the
|
|
2045
|
+
* acknowledgment, pending-request tracking and callback semantics stay
|
|
2046
|
+
* identical to a request served by a peer gateway.
|
|
2047
|
+
*
|
|
2048
|
+
* The handler runs synchronously and its acknowledgment can erase the
|
|
2049
|
+
* pending entry, so callers must not hold a reference into
|
|
2050
|
+
* pendingInternetRequests across this call -- pass copies.
|
|
2051
|
+
*/
|
|
2052
|
+
void dispatchInternetRequestLocally(uint32_t messageId, uint8_t priority,
|
|
2053
|
+
uint8_t retryCount,
|
|
2054
|
+
const TSTRING& destination,
|
|
2055
|
+
const TSTRING& payload) {
|
|
2056
|
+
gateway::GatewayDataPackage pkg;
|
|
2057
|
+
pkg.from = this->nodeId;
|
|
2058
|
+
pkg.dest = this->nodeId;
|
|
2059
|
+
pkg.messageId = messageId;
|
|
2060
|
+
pkg.originNode = this->nodeId;
|
|
2061
|
+
pkg.timestamp = this->getNodeTime();
|
|
2062
|
+
pkg.priority = priority;
|
|
2063
|
+
pkg.destination = destination;
|
|
2064
|
+
pkg.payload = payload;
|
|
2065
|
+
pkg.contentType = "application/json";
|
|
2066
|
+
pkg.retryCount = retryCount;
|
|
2067
|
+
pkg.requiresAck = true;
|
|
2068
|
+
protocol::Variant variant(&pkg);
|
|
2069
|
+
this->callbackList.execute(protocol::GATEWAY_DATA, variant, nullptr, 0);
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2044
2072
|
/**
|
|
2045
2073
|
* Schedule retry for a failed Internet request
|
|
2046
2074
|
*/
|
|
@@ -2087,6 +2115,28 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
2087
2115
|
|
|
2088
2116
|
PendingInternetRequest& request = it->second;
|
|
2089
2117
|
|
|
2118
|
+
// A request this node is serving from its own uplink never had a mesh
|
|
2119
|
+
// hop to repeat. The routing path below needs an active peer and a
|
|
2120
|
+
// discovered bridge, so a lone gateway would burn every retry without
|
|
2121
|
+
// issuing a single HTTP request and then report "Max retries exceeded"
|
|
2122
|
+
// in place of the transport error that actually occurred -- the same
|
|
2123
|
+
// dishonest failure issue #446 was about.
|
|
2124
|
+
//
|
|
2125
|
+
// Copy what the redispatch needs first: the local handler runs
|
|
2126
|
+
// synchronously and can erase this entry, leaving `request` dangling.
|
|
2127
|
+
if (request.gatewayNodeId == this->nodeId && hasLocalInternet()) {
|
|
2128
|
+
const uint8_t priority = request.priority;
|
|
2129
|
+
const uint8_t retryCount = request.retryCount;
|
|
2130
|
+
const TSTRING destination = request.destination;
|
|
2131
|
+
const TSTRING payload = request.payload;
|
|
2132
|
+
Log(logger::COMMUNICATION,
|
|
2133
|
+
"retryInternetRequest(): Retrying msgId=%u on the local uplink\n",
|
|
2134
|
+
messageId);
|
|
2135
|
+
dispatchInternetRequestLocally(messageId, priority, retryCount,
|
|
2136
|
+
destination, payload);
|
|
2137
|
+
return;
|
|
2138
|
+
}
|
|
2139
|
+
|
|
2090
2140
|
// Check mesh connectivity before attempting retry
|
|
2091
2141
|
// During bridge failover, connection may be temporarily lost
|
|
2092
2142
|
if (!hasActiveMeshConnections()) {
|