diff --git a/eastmesh-docs/custom-cli.md b/eastmesh-docs/custom-cli.md index 8834d710..66e1da07 100644 --- a/eastmesh-docs/custom-cli.md +++ b/eastmesh-docs/custom-cli.md @@ -182,6 +182,7 @@ Notes: - commands run with the same care as if you typed them into the repeater CLI directly - this is intended for local admin use on a trusted network - `start ota` releases the local HTTP redirect listener on port `80` so the OTA HTTP listener can take over without stopping the rest of the repeater services, regardless of whether the command is run from the web panel, serial CLI, or a remote companion/app CLI session +- `start ota` uses the repeater's existing Wi-Fi address when already connected, or starts the `MeshCore-OTA` access point when Wi-Fi is not connected - the `/app` Regions shortcut runs the existing MeshCore region commands in sequence: `region put au`, `region put au-STATE`, `region allowf au`, `region allowf au-STATE`, then `region save` ## Companion Wi-Fi Rescue Commands diff --git a/eastmesh-docs/web-panel.md b/eastmesh-docs/web-panel.md index fa5e0950..2e2f86df 100644 --- a/eastmesh-docs/web-panel.md +++ b/eastmesh-docs/web-panel.md @@ -340,8 +340,11 @@ On mobile: 1. Press `Start OTA`. 2. Confirm the action. -3. The local HTTP redirect listener on port `80` is released so OTA can take over that port. -4. Continue with your normal OTA workflow. +3. The panel starts OTA, then opens the returned `http://.../update` URL. +4. The local HTTP redirect listener on port `80` is released so OTA can take over that port. +5. Continue with your normal OTA workflow. + +If the repeater is already connected to Wi-Fi, OTA uses the existing LAN address. If Wi-Fi is not connected, the repeater starts the `MeshCore-OTA` access point and returns that OTA address instead. If an older build sends you through a strange redirect after `start ota`, use the web `Start OTA` button to begin the upgrade. This redirect issue is fixed in `observer-eastmesh-v1.3.11`. diff --git a/src/helpers/ESP32Board.cpp b/src/helpers/ESP32Board.cpp index fc34fc14..9b5ccafc 100644 --- a/src/helpers/ESP32Board.cpp +++ b/src/helpers/ESP32Board.cpp @@ -10,6 +10,40 @@ #include +namespace { +AsyncWebServer* ota_server = nullptr; +char ota_id_buf[60]; +char ota_home_buf[90]; +volatile bool ota_start_pending = false; + +void startOTAServerNow() { + if (ota_server != nullptr) { + delete ota_server; + ota_server = nullptr; + } + + ota_server = new AsyncWebServer(80); + + ota_server->on("/", HTTP_GET, [](AsyncWebServerRequest *request) { + request->send(200, "text/html", ota_home_buf); + }); + ota_server->on("/log", HTTP_GET, [](AsyncWebServerRequest *request) { + request->send(SPIFFS, "/packet_log", "text/plain"); + }); + + AsyncElegantOTA.setID(ota_id_buf); + AsyncElegantOTA.begin(ota_server); // Start ElegantOTA + ota_server->begin(); +} + +void startOTAServerTask(void*) { + vTaskDelay(pdMS_TO_TICKS(750)); + startOTAServerNow(); + ota_start_pending = false; + vTaskDelete(nullptr); +} +} + bool ESP32Board::startOTAUpdate(const char* id, char reply[]) { inhibit_sleep = true; // prevent sleep during OTA @@ -28,28 +62,16 @@ bool ESP32Board::startOTAUpdate(const char* id, char reply[]) { sprintf(reply, "Started: http://%s/update", ota_ip.toString().c_str()); MESH_DEBUG_PRINTLN("startOTAUpdate: %s", reply); - static char id_buf[60]; - snprintf(id_buf, sizeof(id_buf), "%s (%s)", id, getManufacturerName()); - static char home_buf[90]; - snprintf(home_buf, sizeof(home_buf), "

Hi! I am a MeshCore Repeater. ID: %s

", id); + snprintf(ota_id_buf, sizeof(ota_id_buf), "%s (%s)", id, getManufacturerName()); + snprintf(ota_home_buf, sizeof(ota_home_buf), "

Hi! I am a MeshCore Repeater. ID: %s

", id); - static AsyncWebServer* server = nullptr; - if (server != nullptr) { - delete server; - server = nullptr; + if (!ota_start_pending) { + ota_start_pending = true; + if (xTaskCreate(startOTAServerTask, "ota-start", 4096, nullptr, 1, nullptr) != pdPASS) { + startOTAServerNow(); + ota_start_pending = false; + } } - server = new AsyncWebServer(80); - - server->on("/", HTTP_GET, [](AsyncWebServerRequest *request) { - request->send(200, "text/html", home_buf); - }); - server->on("/log", HTTP_GET, [](AsyncWebServerRequest *request) { - request->send(SPIFFS, "/packet_log", "text/plain"); - }); - - AsyncElegantOTA.setID(id_buf); - AsyncElegantOTA.begin(server); // Start ElegantOTA - server->begin(); return true; } diff --git a/src/helpers/web/WebPanelServer.cpp b/src/helpers/web/WebPanelServer.cpp index ad7748d4..7da5ef98 100644 --- a/src/helpers/web/WebPanelServer.cpp +++ b/src/helpers/web/WebPanelServer.cpp @@ -2678,10 +2678,14 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML( }; document.getElementById("otaBtn").onclick = async () => { if (confirm("Start OTA mode now?")) { + const otaBtn = document.getElementById("otaBtn"); + otaBtn.disabled = true; const result = await runCommand("start ota"); const match = result.text && result.text.match(/https?:\/\/\S+/); if (result.ok && match) { window.location.href = match[0]; + } else { + otaBtn.disabled = false; } } };