@alteriom/painlessmesh 2.0.1 → 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 +55 -0
- package/README.md +4 -2
- package/RELEASE_GUIDE.md +16 -4
- 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,61 @@ 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
|
+
|
|
10
65
|
## [2.0.1] - 2026-09-07
|
|
11
66
|
|
|
12
67
|
A packaging and documentation release. **No library behaviour changed**: the
|
package/README.md
CHANGED
|
@@ -596,9 +596,11 @@ 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.
|
|
602
604
|
|
|
603
605
|
**2.0.0 — delivery confirmation, a unified send path, and a mesh that holds together on real hardware**
|
|
604
606
|
|
package/RELEASE_GUIDE.md
CHANGED
|
@@ -82,12 +82,24 @@ The pull request must also pass all required GitHub checks, including:
|
|
|
82
82
|
- formatting and documentation checks
|
|
83
83
|
- CodeQL
|
|
84
84
|
- simulator scenarios
|
|
85
|
-
-
|
|
85
|
+
- hardware-in-the-loop coverage on the six-family board bank when the change
|
|
86
86
|
touches radio, routing, gateway, OTA, or platform-specific behavior
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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).
|
|
91
103
|
|
|
92
104
|
## Review gate
|
|
93
105
|
|
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()) {
|