Skip to content

Commit f5165b2

Browse files
committed
deepen esp8266 http path
1 parent 629ded3 commit f5165b2

8 files changed

Lines changed: 79 additions & 12 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@
1010

1111
- Added `ZeroNetProfileEsp8266`, a constrained MQTT-first preset for Wemos / ESP8266.
1212
- Added `ZeroNetProfileEsp8266Http`, an opt-in HTTP-first constrained preset for Wemos / ESP8266 so HTTP and MQTT can be tuned separately instead of forcing one compromise path.
13+
- Hardened `ZeroHttpPump` with optional phase-specific timeout budgets and configurable intra-tick phase progress, then used that in the ESP8266 HTTP-first preset so the constrained Wemos path can progress connect/write faster without changing the global default pacing.
1314
- Refined `ZeroNetProfileEsp8266` so HTTP stays truly off by default, MQTT idle churn is lower, and the official Wemos live compare now shows MQTT delivery with timing that beats the naive baseline in the same window.
1415
- Added optional offline queue gating in `ZeroHttpPump` and `ZeroMqttPump` so constrained boards can refuse backlog when link or transport is down.
1516
- Reduced scheduler contention in the ESP8266 preset by staggering network task start offsets and lowering idle network task cadence.
1617
- Updated the ESP8266 preset to recommend `kIdleYield` instead of `kIdleSleep`, which materially reduces live timing jitter in the official Wemos validation node.
1718
- Tuned the ESP8266 preset toward MQTT-first delivery with lighter MQTT dispatch pressure, producing a repeatable Wemos run where MQTT delivery stays live while timing remains at or better than baseline.
19+
- Improved the ESP8266 HTTP-first preset so live HTTP delivery is now real and controlled (`http_rate` can hit `100%` in the official compare) while average lag drops sharply versus the naive baseline; the path remains experimental because worst-case lag and misses still need more cleanup.
1820
- Added generic compare workloads for `EnvMonitor`, `TelemetryGateway`, and `IndustrialLoop`.
1921
- Added repeatable ESP32 compare runners plus a cross-target workload compile matrix.
2022
- Improved telemetry gateway tuning so module throughput rises without queue buildup.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ Recommended board-specific path:
119119

120120
- **ESP32:** use the default network module configs first, then validate against your real endpoint.
121121
- **ESP8266 / Wemos:** start with `ZeroNetProfileEsp8266`. It is a constrained MQTT-first preset that disables periodic HTTP dispatch by default, prevents offline queue buildup, lowers idle MQTT churn, staggers lighter network task cadence, and recommends a lighter idle strategy. In current validation it is the preferred path for Wemos MQTT delivery, while HTTP remains degraded/off by default unless you deliberately opt back in.
122-
- **ESP8266 / Wemos (HTTP-specific):** use `ZeroNetProfileEsp8266Http` only when your node is intentionally HTTP-first. It keeps the same constrained timing discipline, but does not try to combine HTTP and MQTT in the same default path. Treat it as an opt-in experimental profile until your own endpoint validation confirms live delivery under your Wi-Fi conditions.
122+
- **ESP8266 / Wemos (HTTP-specific):** use `ZeroNetProfileEsp8266Http` only when your node is intentionally HTTP-first. It keeps the same constrained timing discipline, but does not try to combine HTTP and MQTT in the same default path. Current validation now shows real HTTP delivery with clean request success under the official local compare, but it is still marked experimental because misses and worst-case lag are not yet as tidy as the MQTT-first constrained path.
123123

124124
Current best module tradeoff reference (ESP32, `LEAN_NET`, manual pattern vs module pattern):
125125

docs/wiki/Beta-Modules.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ They are already useful and validated on desktop plus ESP32 smoke tests, but the
1919
Recommended default:
2020

2121
- For constrained ESP8266 boards, start with `ZeroNetProfileEsp8266`. It is the recommended MQTT-first constrained preset: HTTP stays degraded/off by default, offline queueing is rejected, idle MQTT churn is lower, and the preset uses a lighter recommended idle strategy plus staggered network task starts so the board stays more predictable without hand-tuning every interval.
22-
- If your board is intentionally HTTP-first, use `ZeroNetProfileEsp8266Http` as a separate opt-in path. It keeps the constrained timing discipline but avoids pretending that one default cadence can serve HTTP-heavy and MQTT-heavy nodes equally well. Treat it as experimental until your own endpoint validation confirms live delivery.
22+
- If your board is intentionally HTTP-first, use `ZeroNetProfileEsp8266Http` as a separate opt-in path. It keeps the constrained timing discipline but avoids pretending that one default cadence can serve HTTP-heavy and MQTT-heavy nodes equally well. The official Wemos compare now shows real HTTP delivery on this path, but it is still experimental because miss count and worst-case lag remain rougher than the MQTT-first constrained preset.
2323

2424
## What BETA Means Here
2525

examples/LiveNetworkNode/LiveNetworkNode.ino

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,17 @@ bool isWiFiConnected() {
9999
return WiFi.status() == WL_CONNECTED;
100100
}
101101

102+
bool isHttpTransportReady() {
103+
#if defined(ARDUINO_ARCH_ESP8266)
104+
if (!g_wifiMaintainer.isConnected()) {
105+
return false;
106+
}
107+
return g_wifiMaintainer.stablePolls() >= ZeroNetActiveEsp8266Profile::kHttpStablePollsRequired;
108+
#else
109+
return isWiFiConnected();
110+
#endif
111+
}
112+
102113
void connectWiFi() {
103114
const wl_status_t status = WiFi.status();
104115
if (status == WL_CONNECTED) {
@@ -299,8 +310,8 @@ void dispatchTask() {
299310
(nowMs - g_lastHttpDispatchAtMs) >= kHttpDispatchPeriodMs;
300311
const bool mqttDue = (nowMs - g_lastMqttDispatchAtMs) >= kMqttDispatchPeriodMs;
301312

302-
if (httpDue && g_httpPump.queuedCount() == 0 && !g_httpPump.isBusy()) {
303-
g_lastHttpDispatchAtMs = nowMs;
313+
if (httpDue && g_httpPump.queuedCount() == 0 && !g_httpPump.isBusy() &&
314+
isHttpTransportReady()) {
304315
const int written = snprintf(g_httpPayload, sizeof(g_httpPayload),
305316
"{\"seq\":%lu,\"sensor\":%lu,\"board\":\"module\"}",
306317
g_sampleRuns, g_sensorValue);
@@ -310,7 +321,9 @@ void dispatchTask() {
310321
request.contentType = "application/json";
311322
request.body = g_httpPayload;
312323
request.bodyLength = static_cast<uint16_t>(written);
313-
g_httpPump.enqueue(request);
324+
if (g_httpPump.enqueue(request)) {
325+
g_lastHttpDispatchAtMs = nowMs;
326+
}
314327
}
315328
}
316329

@@ -440,7 +453,7 @@ void setup() {
440453
httpReadStep,
441454
httpCloseStep,
442455
httpConfig);
443-
g_httpPump.setLinkProbe(isWiFiConnected);
456+
g_httpPump.setLinkProbe(isHttpTransportReady);
444457
}
445458

446459
ZeroMqttPump::Config mqttConfig;

src/modules/net/ZeroHttpPump.h

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,12 @@ class ZeroHttpPump {
5555
unsigned long retryMaxMs;
5656
unsigned long retryJitterMs;
5757
unsigned long phaseTimeoutMs;
58+
unsigned long connectPhaseTimeoutMs;
59+
unsigned long writePhaseTimeoutMs;
60+
unsigned long readPhaseTimeoutMs;
61+
unsigned long closePhaseTimeoutMs;
5862
uint8_t maxRetries;
63+
uint8_t immediatePhaseBudget;
5964
bool dropOldestOnFull;
6065
bool queueWhenLinkDown;
6166
bool emitCompletionEvents;
@@ -78,14 +83,18 @@ class ZeroHttpPump {
7883
retryJitterMs(100),
7984
phaseTimeoutMs(300),
8085
#endif
86+
connectPhaseTimeoutMs(0),
87+
writePhaseTimeoutMs(0),
88+
readPhaseTimeoutMs(0),
89+
closePhaseTimeoutMs(0),
8190
maxRetries(2),
91+
immediatePhaseBudget(1),
8292
dropOldestOnFull(true),
8393
queueWhenLinkDown(true),
8494
emitCompletionEvents(true) {}
8595
};
8696

8797
static const uint8_t kQueueCapacity = ZEROKERNEL_HTTP_PUMP_QUEUE_CAPACITY;
88-
static const uint8_t kImmediatePhaseBudget = 1;
8998

9099
ZeroHttpPump()
91100
: kernel_(NULL),
@@ -197,7 +206,9 @@ class ZeroHttpPump {
197206
hasTicked_ = true;
198207
lastTickAtMs_ = nowMs;
199208

200-
for (uint8_t stepBudget = 0; stepBudget < kImmediatePhaseBudget; ++stepBudget) {
209+
const uint8_t immediatePhaseBudget =
210+
config_.immediatePhaseBudget == 0 ? 1 : config_.immediatePhaseBudget;
211+
for (uint8_t stepBudget = 0; stepBudget < immediatePhaseBudget; ++stepBudget) {
201212
if (!active_) {
202213
if (queueCount_ == 0) {
203214
return;
@@ -219,8 +230,9 @@ class ZeroHttpPump {
219230
return;
220231
}
221232

222-
if (config_.phaseTimeoutMs > 0 && phaseStartedAtMs_ != 0 &&
223-
(nowMs - phaseStartedAtMs_) > config_.phaseTimeoutMs) {
233+
const unsigned long phaseTimeoutMs = currentPhaseTimeoutMs_();
234+
if (phaseTimeoutMs > 0 && phaseStartedAtMs_ != 0 &&
235+
(nowMs - phaseStartedAtMs_) > phaseTimeoutMs) {
224236
metrics_.recordPhaseTimeout();
225237
handlePhaseFailure_(nowMs);
226238
return;
@@ -400,6 +412,22 @@ class ZeroHttpPump {
400412
return (nowMs + retrySalt_ + activeRequest_.queuedAtMs) % (config_.retryJitterMs + 1UL);
401413
}
402414

415+
unsigned long currentPhaseTimeoutMs_() const {
416+
if (phase_ == kPhaseConnecting && config_.connectPhaseTimeoutMs > 0) {
417+
return config_.connectPhaseTimeoutMs;
418+
}
419+
if (phase_ == kPhaseWriting && config_.writePhaseTimeoutMs > 0) {
420+
return config_.writePhaseTimeoutMs;
421+
}
422+
if (phase_ == kPhaseReading && config_.readPhaseTimeoutMs > 0) {
423+
return config_.readPhaseTimeoutMs;
424+
}
425+
if (phase_ == kPhaseClosing && config_.closePhaseTimeoutMs > 0) {
426+
return config_.closePhaseTimeoutMs;
427+
}
428+
return config_.phaseTimeoutMs;
429+
}
430+
403431
Kernel* kernel_;
404432
LinkProbe linkProbe_;
405433
StepCallback connectStep_;

src/modules/net/ZeroNetProfileEsp8266.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ struct ZeroNetProfileEsp8266 {
3030
static const unsigned long kHttpDispatchPeriodMs = 0UL;
3131
static const unsigned long kMqttDispatchPeriodMs = 1000UL;
3232
static const unsigned long kHttpIoTimeoutMs = 200UL;
33+
static const uint8_t kHttpStablePollsRequired = 0;
3334

3435
static ZeroWiFiMaintainer::Config wifiConfig() {
3536
ZeroWiFiMaintainer::Config config;

src/modules/net/ZeroNetProfileEsp8266Http.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ struct ZeroNetProfileEsp8266Http {
1818
static const bool kEnableMqttByDefault = false;
1919
static const unsigned long kSampleTaskIntervalMs = 100UL;
2020
static const unsigned long kWiFiTaskIntervalMs = 250UL;
21-
static const unsigned long kHttpTaskIntervalMs = 250UL;
21+
static const unsigned long kHttpTaskIntervalMs = 100UL;
2222
static const unsigned long kMqttTaskIntervalMs = 500UL;
2323
static const unsigned long kDispatchTaskIntervalMs = 250UL;
2424
static const unsigned long kReportTaskIntervalMs = 1000UL;
@@ -30,7 +30,8 @@ struct ZeroNetProfileEsp8266Http {
3030

3131
static const unsigned long kHttpDispatchPeriodMs = 1500UL;
3232
static const unsigned long kMqttDispatchPeriodMs = 0UL;
33-
static const unsigned long kHttpIoTimeoutMs = 200UL;
33+
static const unsigned long kHttpIoTimeoutMs = 450UL;
34+
static const uint8_t kHttpStablePollsRequired = 1;
3435

3536
static ZeroWiFiMaintainer::Config wifiConfig() {
3637
ZeroWiFiMaintainer::Config config;
@@ -50,7 +51,12 @@ struct ZeroNetProfileEsp8266Http {
5051
config.retryMaxMs = 3200UL;
5152
config.retryJitterMs = 180UL;
5253
config.phaseTimeoutMs = 300UL;
54+
config.connectPhaseTimeoutMs = 450UL;
55+
config.writePhaseTimeoutMs = 100UL;
56+
config.readPhaseTimeoutMs = 650UL;
57+
config.closePhaseTimeoutMs = 75UL;
5358
config.maxRetries = 1;
59+
config.immediatePhaseBudget = 2;
5460
config.queueWhenLinkDown = false;
5561
return config;
5662
}

src/modules/net/ZeroWiFiMaintainer.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ class ZeroWiFiMaintainer {
8888
connectAttempts_ = 0;
8989
reconnectTransitions_ = 0;
9090
stateNotifications_ = 0;
91+
consecutiveStablePolls_ = 0;
9192
}
9293

9394
void reset() {
@@ -176,6 +177,22 @@ class ZeroWiFiMaintainer {
176177
return connected_;
177178
}
178179

180+
bool isStable() const {
181+
if (!connected_) {
182+
return false;
183+
}
184+
185+
if (config_.stableThreshold == 0) {
186+
return true;
187+
}
188+
189+
return consecutiveStablePolls_ >= config_.stableThreshold;
190+
}
191+
192+
uint8_t stablePolls() const {
193+
return consecutiveStablePolls_;
194+
}
195+
179196
unsigned long connectAttempts() const {
180197
return connectAttempts_;
181198
}

0 commit comments

Comments
 (0)