mirror of
https://github.com/alexhopeoconnor/WiFiManager.git
synced 2026-10-19 09:21:30 +10:00
fix: harden portal OTA release
This commit is contained in:
@@ -25,12 +25,14 @@ the `WM_NMCLI_AUTH` options.
|
||||
|
||||
## A/B portal OTA fixture
|
||||
|
||||
The same fixture has dedicated `*_ota_a` and `*_ota_b` PlatformIO environments
|
||||
for the physical portal HTTP OTA test harness. A and B differ only by a compiled
|
||||
marker served from the fixture-only `/api/test/firmware-marker` endpoint and an
|
||||
immutable serial boot marker. The test harness requires serial A, updater
|
||||
start/completion, then serial B, so it proves a B boot without trusting saved
|
||||
portal values, EEPROM, or a filename.
|
||||
The physical portal HTTP OTA test harness uses one `*_ota` environment per
|
||||
platform. It writes a harness-only A/B identity header to an ignored, owner-only
|
||||
directory unique to that run before each build, so PlatformIO reuses dependency
|
||||
objects while the fixture-only `/api/test/firmware-marker` endpoint still proves
|
||||
the newly booted image. The test harness requires the real form's successful response, its automatic
|
||||
restart, and two fresh B-marker responses. Passive serial capture is retained
|
||||
for failure diagnosis, but a product log-message wording change cannot turn a
|
||||
successful A-to-B update into a failed test.
|
||||
|
||||
```bash
|
||||
./tools/portal-hardware ota \
|
||||
|
||||
@@ -6,6 +6,9 @@ framework = arduino
|
||||
lib_ldf_mode = deep+
|
||||
lib_deps =
|
||||
WiFiManager=symlink://../..
|
||||
build_flags =
|
||||
extra_scripts =
|
||||
pre:tools/ota_fixture_input.py
|
||||
|
||||
[env:esp8266]
|
||||
extends = common
|
||||
@@ -27,34 +30,21 @@ build_flags =
|
||||
-I${platformio.packages_dir}/framework-arduinoespressif32/libraries/Network/src
|
||||
-DWM_LOG_LEVEL=4
|
||||
|
||||
; OTA test-harness images deliberately differ only by their compile-time marker.
|
||||
; ESP8266 needs an OTA-capable linker layout; ESP32 needs a tracked two-slot
|
||||
; partition table. The hardware runner always flashes A over serial then
|
||||
; uploads B through the real portal form.
|
||||
[env:esp8266_ota_a]
|
||||
; The OTA test harness writes its A/B marker into an ignored header included
|
||||
; only by the portal fixture. One environment per platform keeps the explicit
|
||||
; update layout while allowing the A-to-B rebuild to reuse every library object.
|
||||
[env:esp8266_ota]
|
||||
extends = env:esp8266
|
||||
board_build.ldscript = eagle.flash.4m1m.ld
|
||||
build_flags =
|
||||
${env:esp8266.build_flags}
|
||||
-DWM_OTA_TEST_IMAGE=\"A\"
|
||||
${common.build_flags}
|
||||
-DWM_PORTAL_OTA_TEST=1
|
||||
|
||||
[env:esp8266_ota_b]
|
||||
extends = env:esp8266
|
||||
board_build.ldscript = eagle.flash.4m1m.ld
|
||||
build_flags =
|
||||
${env:esp8266.build_flags}
|
||||
-DWM_OTA_TEST_IMAGE=\"B\"
|
||||
|
||||
[env:esp32_ota_a]
|
||||
[env:esp32_ota]
|
||||
extends = env:esp32
|
||||
board_build.partitions = partitions/esp32_ota_4m_no_fs.csv
|
||||
build_flags =
|
||||
${env:esp32.build_flags}
|
||||
-DWM_OTA_TEST_IMAGE=\"A\"
|
||||
|
||||
[env:esp32_ota_b]
|
||||
extends = env:esp32
|
||||
board_build.partitions = partitions/esp32_ota_4m_no_fs.csv
|
||||
build_flags =
|
||||
${env:esp32.build_flags}
|
||||
-DWM_OTA_TEST_IMAGE=\"B\"
|
||||
${common.build_flags}
|
||||
-DWM_PORTAL_OTA_TEST=1
|
||||
|
||||
@@ -3,19 +3,21 @@
|
||||
|
||||
namespace {
|
||||
|
||||
// These markers belong only to the portal hardware fixture. They are compiled
|
||||
// into the image rather than stored in Wi-FiManager settings, so an A -> B
|
||||
// assertion proves that the new firmware booted after the updater restarted
|
||||
// the board. Normal portal test-harness builds retain a descriptive fixture
|
||||
// value.
|
||||
#if defined(WM_OTA_TEST_IMAGE)
|
||||
// These markers belong only to the portal OTA fixture. The hardware runner
|
||||
// writes the ignored header immediately before each A/B build, so the marker
|
||||
// is compiled into firmware rather than saved in WiFiManager settings.
|
||||
#if defined(WM_PORTAL_OTA_TEST)
|
||||
#include <ota_fixture_identity.h>
|
||||
#ifndef WM_OTA_FIXTURE_IMAGE
|
||||
#error "Portal OTA fixture identity is missing."
|
||||
#endif
|
||||
// PlatformIO releases the serial port only after the upload-triggered reset.
|
||||
// The physical OTA test harness then attaches passively, so give it the same
|
||||
// explicit window as the DeviceFramework A/B fixtures before boot evidence or
|
||||
// portal work begins. Normal portal test-harness builds keep the short delay.
|
||||
constexpr unsigned long kSerialMonitorAttachDelayMs = 5000UL;
|
||||
#else
|
||||
#define WM_OTA_TEST_IMAGE "portal-harness"
|
||||
#define WM_OTA_FIXTURE_IMAGE "portal-harness"
|
||||
constexpr unsigned long kSerialMonitorAttachDelayMs = 300UL;
|
||||
#endif
|
||||
|
||||
@@ -87,7 +89,7 @@ void registerOtaTestMarker() {
|
||||
server->on("/api/test/firmware-marker", HTTP_GET,
|
||||
[](AsyncWebServerRequest* request) {
|
||||
String response = F("{\"marker\":\"");
|
||||
response += WM_OTA_TEST_IMAGE;
|
||||
response += WM_OTA_FIXTURE_IMAGE;
|
||||
response += F("\",\"freeSketchSpace\":");
|
||||
response += String(ESP.getFreeSketchSpace());
|
||||
response += F("}");
|
||||
@@ -105,7 +107,7 @@ void setup() {
|
||||
// and after its browser upload. It cannot be faked by saved portal values
|
||||
// or an HTTP response from a stale image.
|
||||
Serial.print(F("WiFiManager portal OTA fixture image: "));
|
||||
Serial.println(WM_OTA_TEST_IMAGE);
|
||||
Serial.println(WM_OTA_FIXTURE_IMAGE);
|
||||
|
||||
// This fixture must be independent of whichever sketch was previously
|
||||
// flashed to the board. Clear saved station credentials before starting
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
"""Add the run-owned A/B identity directory to this portal test-harness build."""
|
||||
|
||||
import os
|
||||
|
||||
from SCons.Script import Exit
|
||||
|
||||
Import("env")
|
||||
|
||||
if env["PIOENV"].endswith("_ota"):
|
||||
identity_dir = os.environ.get("WIFIMANAGER_OTA_IDENTITY_DIR")
|
||||
if not identity_dir:
|
||||
print(
|
||||
"WIFIMANAGER_OTA_IDENTITY_DIR is required; "
|
||||
"run this fixture through its named test harness."
|
||||
)
|
||||
Exit(1)
|
||||
|
||||
identity_header = os.path.join(identity_dir, "ota_fixture_identity.h")
|
||||
if not os.path.isfile(identity_header):
|
||||
print(f"OTA fixture identity header is missing: {identity_header}")
|
||||
Exit(1)
|
||||
|
||||
env.Append(CPPPATH=[identity_dir])
|
||||
Reference in New Issue
Block a user