1
0
mirror of https://github.com/esp8266/Arduino.git synced 2025-06-15 00:02:49 +03:00

A new approach for erasing WiFi Settings (#8828)

* A new approach for erasing WiFi Settings

Add support for hardware reset function call - simulates EXT_RST via HWDT.

Add reset selection to `ESP.eraseConfig()` for calling hardware reset
after erasing the WiFi Settings.

Update ArduinoOTA to use `ESP.eraseConfig(true)`

Externalized `ArduinoOTA.eraseConfigAndReset()`

Add OTA examples to illustrate using erase config changes.

* style
fixed confused example

* improve wording

* Add new state to retry eraseConfigAndReset

* Removed unreachable error test from examples.

Removed continuous retry of "eraseConfig" and allow the script to
assign an error handling option for "eraseConfig" failure.

Update example to use error handling option.

* In eboot for function ets_wdt_enable() added missing arguments

* Update comments and example

* Wording

* Rebuilt eboot.elf with current tools from ./get.py

* Requested changes.

* cleanup comments

* Update hardware_reset

Avoid using "[[noreturn]]" - not accepted for a .c file function
Updated to use __attribute__((noreturn)) to handle both .cpp and .c
file functions.
This commit is contained in:
M Hightower
2023-08-29 08:24:07 -07:00
committed by GitHub
parent 1a4663fbe8
commit a348833a81
10 changed files with 531 additions and 34 deletions

View File

@ -3,6 +3,7 @@
#endif
#include <functional>
#include <WiFiUdp.h>
#include <eboot_command.h>
#include "ArduinoOTA.h"
#include "MD5Builder.h"
#include "StreamString.h"
@ -24,10 +25,11 @@ extern "C" {
#include <ESP8266mDNS.h>
#endif
#ifdef DEBUG_ESP_OTA
#ifdef DEBUG_ESP_PORT
#if defined(DEBUG_ESP_OTA) && defined(DEBUG_ESP_PORT)
#define OTA_DEBUG DEBUG_ESP_PORT
#endif
#define OTA_DEBUG_PRINTF(fmt, ...) OTA_DEBUG.printf_P(PSTR(fmt), ##__VA_ARGS__)
#else
#define OTA_DEBUG_PRINTF(...)
#endif
ArduinoOTAClass::ArduinoOTAClass()
@ -93,6 +95,10 @@ void ArduinoOTAClass::setRebootOnSuccess(bool reboot){
_rebootOnSuccess = reboot;
}
void ArduinoOTAClass::setEraseConfig(ota_erase_cfg_t eraseConfig){
_eraseConfig = eraseConfig;
}
void ArduinoOTAClass::begin(bool useMDNS) {
if (_initialized)
return;
@ -119,7 +125,7 @@ void ArduinoOTAClass::begin(bool useMDNS) {
if(!_udp_ota->listen(IP_ADDR_ANY, _port))
return;
_udp_ota->onRx(std::bind(&ArduinoOTAClass::_onRx, this));
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_MDNS)
if(_useMDNS) {
MDNS.begin(_hostname.c_str());
@ -133,9 +139,7 @@ void ArduinoOTAClass::begin(bool useMDNS) {
#endif
_initialized = true;
_state = OTA_IDLE;
#ifdef OTA_DEBUG
OTA_DEBUG.printf("OTA server at: %s.local:%u\n", _hostname.c_str(), _port);
#endif
OTA_DEBUG_PRINTF("OTA server at: %s.local:%u\n", _hostname.c_str(), _port);
}
int ArduinoOTAClass::parseInt(){
@ -243,13 +247,11 @@ void ArduinoOTAClass::_runUpdate() {
IPAddress ota_ip = _ota_ip;
if (!Update.begin(_size, _cmd)) {
#ifdef OTA_DEBUG
OTA_DEBUG.println("Update Begin Error");
#endif
OTA_DEBUG_PRINTF("Update Begin Error\n");
if (_error_callback) {
_error_callback(OTA_BEGIN_ERROR);
}
StreamString ss;
Update.printError(ss);
_udp_ota->append("ERR: ", 5);
@ -275,9 +277,7 @@ void ArduinoOTAClass::_runUpdate() {
WiFiClient client;
if (!client.connect(_ota_ip, _ota_port)) {
#ifdef OTA_DEBUG
OTA_DEBUG.printf("Connect Failed\n");
#endif
OTA_DEBUG_PRINTF("Connect Failed\n");
_udp_ota->listen(IP_ADDR_ANY, _port);
if (_error_callback) {
_error_callback(OTA_CONNECT_ERROR);
@ -293,9 +293,7 @@ void ArduinoOTAClass::_runUpdate() {
while (!client.available() && waited--)
delay(1);
if (!waited){
#ifdef OTA_DEBUG
OTA_DEBUG.printf("Receive Failed\n");
#endif
OTA_DEBUG_PRINTF("Receive Failed\n");
_udp_ota->listen(IP_ADDR_ANY, _port);
if (_error_callback) {
_error_callback(OTA_RECEIVE_ERROR);
@ -320,18 +318,31 @@ void ArduinoOTAClass::_runUpdate() {
client.flush();
delay(1000);
client.stop();
#ifdef OTA_DEBUG
OTA_DEBUG.printf("Update Success\n");
#endif
OTA_DEBUG_PRINTF("Update Success\n");
if (_end_callback) {
_end_callback();
}
if(_rebootOnSuccess){
#ifdef OTA_DEBUG
OTA_DEBUG.printf("Rebooting...\n");
#endif
OTA_DEBUG_PRINTF("Rebooting...\n");
//let serial/network finish tasks that might be given in _end_callback
delay(100);
if (OTA_ERASE_CFG_NO != _eraseConfig) {
eraseConfigAndReset(); // returns on failure
if (_error_callback) {
_error_callback(OTA_ERASE_SETTINGS_ERROR);
}
if (OTA_ERASE_CFG_ABORT_ON_ERROR == _eraseConfig) {
eboot_command_clear();
return;
}
#ifdef OTA_DEBUG
else if (OTA_ERASE_CFG_IGNORE_ERROR == _eraseConfig) {
// Fallthrough and restart
} else {
panic();
}
#endif
}
ESP.restart();
}
} else {
@ -357,10 +368,19 @@ void ArduinoOTAClass::end() {
}
#endif
_state = OTA_IDLE;
#ifdef OTA_DEBUG
OTA_DEBUG.printf("OTA server stopped.\n");
#endif
OTA_DEBUG_PRINTF("OTA server stopped.\n");
}
void ArduinoOTAClass::eraseConfigAndReset() {
OTA_DEBUG_PRINTF("Erase Config and Hard Reset ...\n");
if (WiFi.mode(WIFI_OFF)) {
ESP.eraseConfigAndReset(); // No return testing - Only returns on failure
OTA_DEBUG_PRINTF(" ESP.eraseConfigAndReset() failed!\n");
} else {
OTA_DEBUG_PRINTF(" WiFi.mode(WIFI_OFF) Timeout!\n");
}
}
//this needs to be called in the loop()
void ArduinoOTAClass::handle() {
if (_state == OTA_RUNUPDATE) {

View File

@ -18,9 +18,16 @@ typedef enum {
OTA_BEGIN_ERROR,
OTA_CONNECT_ERROR,
OTA_RECEIVE_ERROR,
OTA_END_ERROR
OTA_END_ERROR,
OTA_ERASE_SETTINGS_ERROR
} ota_error_t;
typedef enum {
OTA_ERASE_CFG_NO = 0,
OTA_ERASE_CFG_IGNORE_ERROR,
OTA_ERASE_CFG_ABORT_ON_ERROR
} ota_erase_cfg_t;
class ArduinoOTAClass
{
public:
@ -47,6 +54,10 @@ class ArduinoOTAClass
//Sets if the device should be rebooted after successful update. Default true
void setRebootOnSuccess(bool reboot);
//Sets flag to erase WiFi Settings at reboot/reset. "eraseConfig" selects to
//abort erase on failure or ignore error and erase.
void setEraseConfig(ota_erase_cfg_t eraseConfig = OTA_ERASE_CFG_ABORT_ON_ERROR);
//This callback will be called when OTA connection has begun
void onStart(THandlerFunction fn);
@ -64,6 +75,11 @@ class ArduinoOTAClass
//Ends the ArduinoOTA service
void end();
//Has the effect of the "+ WiFi Settings" in the Arduino IDE Tools "Erase
//Flash" selection. Only returns on erase flash failure.
void eraseConfigAndReset();
//Call this in loop() to run the service. Also calls MDNS.update() when begin() or begin(true) is used.
void handle();
@ -84,6 +100,7 @@ class ArduinoOTAClass
bool _initialized = false;
bool _rebootOnSuccess = true;
bool _useMDNS = true;
ota_erase_cfg_t _eraseConfig = OTA_ERASE_CFG_NO;
ota_state_t _state = OTA_IDLE;
int _size = 0;
int _cmd = 0;

View File

@ -0,0 +1,106 @@
/*
This example is a variation on BasicOTA.
As is, this example will "always" erase WiFi Settings and reset after a
successful update. You can make this conditional.
*/
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
#ifndef STASSID
#define STASSID "your-ssid"
#define STAPSK "your-password"
#endif
const char* ssid = STASSID;
const char* password = STAPSK;
void setup() {
Serial.begin(115200);
Serial.println("Booting");
Serial.println(String("Reset Reason: ") + ESP.getResetReason());
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.waitForConnectResult() != WL_CONNECTED) {
Serial.println("Connection Failed! Rebooting...");
delay(5000);
ESP.restart();
}
// Port defaults to 8266
// ArduinoOTA.setPort(8266);
// Hostname defaults to esp8266-[ChipID]
// ArduinoOTA.setHostname("myesp8266");
// No authentication by default
// ArduinoOTA.setPassword("admin");
// Password can be set with it's md5 value as well
// MD5(admin) = 21232f297a57a5a743894a0e4a801fc3
// ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3");
ArduinoOTA.onStart([]() {
String type;
if (ArduinoOTA.getCommand() == U_FLASH) {
type = "sketch";
} else { // U_FS
type = "filesystem";
}
// NOTE: if updating FS this would be the place to unmount FS using FS.end()
Serial.println("Start updating " + type);
});
ArduinoOTA.onEnd([]() {
Serial.println("\nEnd");
/*
By calling "ArduinoOTA.setEraseConfig(ArduinoOTA::OTA_ERASE_CFG_ABORT_ON_ERROR),"
this example will erase the "WiFi Settings" as part of an OTA update. When
erasing WiFi Settings fails, the OTA Update aborts, and eboot will not
copy the new ".bin" in place.
Without the call to "ArduinoOTA.setEraseConfig" legacy behavior, the
system restarts without touching the WiFi Settings.
Options for "setEraseConfig" to handle eraseConfig failures:
OTA_ERASE_CFG_NO - Do not erase WiFi Settings
OTA_ERASE_CFG_IGNORE_ERROR - Ignore the error and continue with update ".bin" copy
OTA_ERASE_CFG_ABORT_ON_ERROR - Cancel flash update copy at reboot
To meet unique requirements, you can make the call below conditional.
Also, this call could be enabled before ArduinoOTA.onEnd() and canceled
here with "ArduinoOTA.setEraseConfig(OTA_ERASE_CFG_NO)."
*/
ArduinoOTA.setEraseConfig(OTA_ERASE_CFG_ABORT_ON_ERROR);
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
});
ArduinoOTA.onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) {
Serial.println("Auth Failed");
} else if (error == OTA_BEGIN_ERROR) {
Serial.println("Begin Failed");
} else if (error == OTA_CONNECT_ERROR) {
Serial.println("Connect Failed");
} else if (error == OTA_RECEIVE_ERROR) {
Serial.println("Receive Failed");
} else if (error == OTA_END_ERROR) {
Serial.println("End Failed");
} else if (error == OTA_ERASE_SETTINGS_ERROR) {
Serial.println("Erase WiFi Settings Failed");
}
});
ArduinoOTA.begin();
Serial.println("Ready");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
}
void loop() {
ArduinoOTA.handle();
}

View File

@ -0,0 +1,162 @@
/*
This example is a variation on BasicOTA.
Logic added to look for a change in SDK Version. If changed, erase the WiFi
Settings and Reset the system.
Added extra debug printing to aid in cutting through the confusion of the
multiple reboots.
*/
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
#include <EEPROM.h>
// You can control the extra debug printing here. To turn off, change 1 to 0.
#if 1
#ifdef DEBUG_ESP_PORT
#define CONSOLE DEBUG_ESP_PORT
#else
#define CONSOLE Serial
#endif
#define DEBUG_PRINTF(fmt, ...) CONSOLE.printf_P(PSTR(fmt), ##__VA_ARGS__)
#else
#define DEBUG_PRINTF(...)
#endif
#ifndef STASSID
#define STASSID "your-ssid"
#define STAPSK "your-password"
#endif
const char* ssid = STASSID;
const char* password = STAPSK;
struct YourEEPROMData {
// list of parameters you need to keep
// ...
// To efficiently save and compare SDK version strings, we use their computed
// CRC32 value.
uint32_t sdkCrc;
};
bool checkSdkCrc() {
auto reason = ESP.getResetInfoPtr()->reason;
// In this example, the OTA update does a software restart. As coded, SDK
// version checks are only performed after a hard reset. Change the lines
// below at your discretion.
//
// Boot loop guard
// Limit crash loops erasing flash. Only run at Power On or Hardware Reset.
if (REASON_DEFAULT_RST != reason && REASON_EXT_SYS_RST != reason) {
DEBUG_PRINTF(" Boot loop guard - SDK version not checked. To perform check, do a hardware reset.\r\n");
return true;
}
const char* sdkVerStr = ESP.getSdkVersion();
uint32_t sdkVersionCrc = crc32(sdkVerStr, strlen(sdkVerStr));
uint32_t savedSdkVersionCrc;
EEPROM.begin((sizeof(struct YourEEPROMData) + 3) & ~3);
EEPROM.get(offsetof(struct YourEEPROMData, sdkCrc), savedSdkVersionCrc);
DEBUG_PRINTF(" Current SDK Verison: %s CRC(0x%08X)\r\n", sdkVerStr, sdkVersionCrc);
DEBUG_PRINTF(" Previous saved SDK CRC(0x%08X)\r\n", savedSdkVersionCrc);
if (sdkVersionCrc == savedSdkVersionCrc) {
return EEPROM.end();
}
DEBUG_PRINTF(" Handle wew SDK Version\r\n");
// Remember new SDK CRC
EEPROM.put(offsetof(struct YourEEPROMData, sdkCrc), sdkVersionCrc);
if (EEPROM.commit() && EEPROM.end()) {
// Erase WiFi Settings and Reset
DEBUG_PRINTF(" EEPROM update successful. New SDK CRC saved.\r\n");
DEBUG_PRINTF(" Erase config and reset: ...\r\n");
ArduinoOTA.eraseConfigAndReset(); // Only returns on fail
DEBUG_PRINTF(" ArduinoOTA.eraseConfigAndReset() failed!\r\n");
} else {
DEBUG_PRINTF(" EEPROM.commit() or EEPROM.end() failed!\r\n");
}
return false;
}
void setup() {
Serial.begin(115200);
Serial.println("Booting");
// It is normal for resets generated by "ArduinoOTA.eraseConfigAndReset()"
// to be reported as "External System".
Serial.println(String("Reset Reason: ") + ESP.getResetReason());
Serial.println("Check for changes in SDK Version:");
if (checkSdkCrc()) {
Serial.println(" SDK version has not changed.");
} else {
Serial.println(" SDK version changed and update to saved details failed.");
}
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.waitForConnectResult() != WL_CONNECTED) {
Serial.println("Connection Failed! Rebooting...");
delay(5000);
ESP.restart();
}
// Port defaults to 8266
// ArduinoOTA.setPort(8266);
// Hostname defaults to esp8266-[ChipID]
// ArduinoOTA.setHostname("myesp8266");
// No authentication by default
// ArduinoOTA.setPassword("admin");
// Password can be set with it's md5 value as well
// MD5(admin) = 21232f297a57a5a743894a0e4a801fc3
// ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3");
ArduinoOTA.onStart([]() {
String type;
if (ArduinoOTA.getCommand() == U_FLASH) {
type = "sketch";
} else { // U_FS
type = "filesystem";
}
// NOTE: if updating FS this would be the place to unmount FS using FS.end()
Serial.println("Start updating " + type);
});
ArduinoOTA.onEnd([]() {
Serial.println("\nEnd");
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
});
ArduinoOTA.onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) {
Serial.println("Auth Failed");
} else if (error == OTA_BEGIN_ERROR) {
Serial.println("Begin Failed");
} else if (error == OTA_CONNECT_ERROR) {
Serial.println("Connect Failed");
} else if (error == OTA_RECEIVE_ERROR) {
Serial.println("Receive Failed");
} else if (error == OTA_END_ERROR) {
Serial.println("End Failed");
}
});
ArduinoOTA.begin();
Serial.println("Ready");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
}
void loop() {
ArduinoOTA.handle();
}