Skip to content

A new approach for erasing WiFi Settings #8828

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Aug 29, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
f5d7d4a
A new approach for erasing WiFi Settings
mhightower83 Jan 23, 2023
1a03a41
style
mhightower83 Jan 24, 2023
d061a3c
improve wording
mhightower83 Jan 25, 2023
8375000
Add new state to retry eraseConfigAndReset
mhightower83 Jan 26, 2023
b1e772c
Merge branch 'master' into pr-eraseconfig-reset
mhightower83 Feb 8, 2023
dd78bc0
Merge branch 'pr-eraseconfig-reset' of github.com:mhightower83/Arduin…
mhightower83 Feb 8, 2023
9ccd11c
Removed unreachable error test from examples.
mhightower83 Feb 9, 2023
feb8a99
In eboot for function ets_wdt_enable() added missing arguments
mhightower83 Feb 9, 2023
eb7aa17
Update comments and example
mhightower83 Feb 10, 2023
5f7adcf
Merge branch 'master' into pr-eraseconfig-reset
mhightower83 Mar 28, 2023
c7a3ed8
Wording
mhightower83 Mar 28, 2023
cd5a852
Merge branch 'master' into pr-eraseconfig-reset
mhightower83 Mar 30, 2023
892e6a2
Rebuilt eboot.elf with current tools from ./get.py
mhightower83 Mar 30, 2023
c952ffc
Merge branch 'master' into pr-eraseconfig-reset
mhightower83 Apr 16, 2023
f8a8251
Requested changes.
mhightower83 Apr 16, 2023
40fba0a
Merge branch 'master' into pr-eraseconfig-reset
mhightower83 Jun 16, 2023
f3a492e
Merge branch 'master' into pr-eraseconfig-reset
mhightower83 Jul 21, 2023
0e1059d
cleanup comments
mhightower83 Jul 24, 2023
721e6d3
Merge branch 'master' into pr-eraseconfig-reset
mhightower83 Jul 27, 2023
696b96b
Update hardware_reset
mhightower83 Jul 28, 2023
ef0c1e2
Merge branch 'master' into pr-eraseconfig-reset
mcspr Aug 15, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions cores/esp8266/Esp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "umm_malloc/umm_malloc.h"
#include <pgmspace.h>
#include "reboot_uart_dwnld.h"
#include "hardware_reset.h"

extern "C" {
#include "user_interface.h"
Expand Down Expand Up @@ -518,8 +519,8 @@ struct rst_info * EspClass::getResetInfoPtr(void) {
return &resetInfo;
}

bool EspClass::eraseConfig(void) {
const size_t cfgSize = 0x4000;
bool EspClass::eraseConfig(bool reset) {
const size_t cfgSize = 0x4000; // Sectors: RF_CAL + SYSTEMPARAM[3]
size_t cfgAddr = ESP.getFlashChipSize() - cfgSize;

for (size_t offset = 0; offset < cfgSize; offset += SPI_FLASH_SEC_SIZE) {
Expand All @@ -528,6 +529,11 @@ bool EspClass::eraseConfig(void) {
}
}

if (reset) {
// Must be called in WiFi.mode(WIFI_OFF) state.
hardware_reset();
}

return true;
}

Expand Down
15 changes: 14 additions & 1 deletion cores/esp8266/Esp.h
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,20 @@ class EspClass {
static String getResetInfo();
static struct rst_info * getResetInfoPtr();

static bool eraseConfig();
/*
Erases 4 sectors at the end of flash, 1 - RF_CAL and 3 - SYSTEMPARM.
These are the same additional sectors that are erase when you select
Erase Flash: "Sketch + WiFi Settings" from the Arduino IDE Tools menu.

As a precaution, since this operation erases the running SDKs flash
configuration space, use reset flag "true" with eraseConfig. Also, for
additional protection, call "WiFi.mode(WIFI_OFF)" before calling.

If you need to erase "WiFi Settings" and reboot consider using
"ArduinoOTA.eraseConfigAndReset()" it handles shutting down WiFi
before the erase.
*/
static bool eraseConfig(bool reset = false);

static uint8_t *random(uint8_t *resultArray, const size_t outputSizeBytes);
static uint32_t random();
Expand Down
114 changes: 114 additions & 0 deletions cores/esp8266/hardware_reset.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
Make the reset look like an EXT_RST reset by:
* Set INTLEVEL to 15 blocking NMI Software WDT interference
* set "restart reason" to REASON_EXT_SYS_RST
* Config Hardware WDT for 1.6ms
* Disable Hardware WDT Level-1 interrupt option
* wait, ...

Inspired by RTOS SDK hardware_restart in panic.c
*/

#include "Arduino.h"
#include <user_interface.h>
#include <ets_sys.h>
#include "hardware_reset.h"


// Extracted from RTOS_SDK eagle_soc.h
/*
* ESPRSSIF MIT License
*
* Copyright (c) 2015 <ESPRESSIF SYSTEMS (SHANGHAI) PTE LTD>
*
* Permission is hereby granted for use on ESPRESSIF SYSTEMS ESP8266 only, in which case,
* it is free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished
* to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
#define REG_WRITE(_r, _v) (*(volatile uint32_t *)(_r)) = (_v)
#define REG_READ(_r) (*(volatile uint32_t *)(_r))

//Watchdog reg {{
#define PERIPHS_WDT_BASEADDR 0x60000900

#define WDT_CTL_ADDRESS 0
#define WDT_OP_ADDRESS 0x4
#define WDT_OP_ND_ADDRESS 0x8
#define WDT_RST_ADDRESS 0x14

#define WDT_CTL_RSTLEN_MASK 0x38
#define WDT_CTL_RSPMOD_MASK 0x6
#define WDT_CTL_EN_MASK 0x1

#define WDT_CTL_RSTLEN_LSB 0x3
#define WDT_CTL_RSPMOD_LSB 0x1
#define WDT_CTL_EN_LSB 0

#define WDT_FEED_VALUE 0x73

#define WDT_REG_READ(_reg) REG_READ(PERIPHS_WDT_BASEADDR + _reg)
#define WDT_REG_WRITE(_reg, _val) REG_WRITE(PERIPHS_WDT_BASEADDR + _reg, _val)
#define CLEAR_WDT_REG_MASK(_reg, _mask) WDT_REG_WRITE(_reg, WDT_REG_READ(_reg) & (~_mask))
#define SET_WDT_REG_MASK(_reg, _mask, _val) SET_PERI_REG_BITS((PERIPHS_WDT_BASEADDR + _reg), _mask, _val, 0)
#undef WDT_FEED
#define WDT_FEED() WDT_REG_WRITE(WDT_RST_ADDRESS, WDT_FEED_VALUE)
//}}

// Inspired by RTOS SDK task_wdt.c and hardware_restart in panic.c

// Copyright 2018-2019 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

extern "C" {
[[noreturn]] void hardware_reset(void) {
volatile uint32_t* const rtc_mem = (volatile uint32_t *)0x60001100u;

// Block NMI WDT from disturbing out restart reason
xt_rsil(15);

// SDK restart reason location
rtc_mem[0] = REASON_EXT_SYS_RST;

// Disable WDT
CLEAR_WDT_REG_MASK(WDT_CTL_ADDRESS, WDT_CTL_EN_MASK);

// Set Reset pulse to maximum
// Select Reset only - no level-1 interrupt
SET_WDT_REG_MASK(WDT_CTL_ADDRESS,
WDT_CTL_RSTLEN_MASK | WDT_CTL_RSPMOD_MASK,
(7 << WDT_CTL_RSTLEN_LSB) | (2 << WDT_CTL_RSPMOD_LSB));

// Set WDT Reset timer to 1.6 ms.
WDT_REG_WRITE(WDT_OP_ADDRESS, 1); // 2^n * 0.8ms, mask 0xf, n = 1 -> (2^1 = 2) * 0.8 * 0.001 = 0.0016

// Enable WDT
SET_WDT_REG_MASK(WDT_CTL_ADDRESS, WDT_CTL_EN_MASK, 1 << WDT_CTL_EN_LSB);

while (true);
}
};
12 changes: 12 additions & 0 deletions cores/esp8266/hardware_reset.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#ifndef HARDWARE_RESET_H
#define HARDWARE_RESET_H
#ifdef __cplusplus
extern "C" {
#endif

[[noreturn]] extern void hardware_reset(void);

#ifdef __cplusplus
}
#endif
#endif
73 changes: 46 additions & 27 deletions libraries/ArduinoOTA/ArduinoOTA.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,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()
Expand Down Expand Up @@ -89,8 +90,9 @@ void ArduinoOTAClass::setPasswordHash(const char * password) {
}
}

void ArduinoOTAClass::setRebootOnSuccess(bool reboot){
void ArduinoOTAClass::setRebootOnSuccess(bool reboot, bool eraseConfig){
_rebootOnSuccess = reboot;
_eraseConfig = eraseConfig;
}

void ArduinoOTAClass::begin(bool useMDNS) {
Expand Down Expand Up @@ -119,7 +121,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());
Expand All @@ -133,9 +135,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(){
Expand Down Expand Up @@ -243,13 +243,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);
Expand All @@ -275,9 +273,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);
Expand All @@ -293,9 +289,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);
Expand All @@ -320,18 +314,27 @@ 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 (_eraseConfig) {
eraseConfigAndReset(); // returns on failure
//C What is the best action to take on failure?
//C 1) On failure, we could invalidate eboot_command buffer -
//C aborting the flash update.
//C 2) Just ignore it and restart.
//C 3) Retry forever
if (_error_callback) {
_error_callback(OTA_ERASE_SETTINGS_ERROR);
}
Copy link
Contributor Author

@mhightower83 mhightower83 Feb 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At failure, there are no good choices.
I picked 3 to retry forever.
If they power cycle, the download is aborted.

Update:
I think an EXT_RST would result in the flash update completing. RTC should be preserved.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why a separate condition & callback though? We can retrieve error id

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand your reference. I am using the existing error callback with a new error number to identify the situation.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(review UI sent a draft :/)

Why even mention it? Since we want this to be done, probably unroll is a better option than leave things in an indeterminate state?
Another part - it is the only state that continues execution. We still have eboot part like you said, even after ::end()ing the instance (suppose that from user pov it is an option to recover).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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();

When enabling erase WiFi settings, the user does so by selecting the fail option ignore or abort. Does that satisfy your concerns?

_state = OTA_ERASEWIFI;
return;
}
ESP.restart();
}
} else {
Expand All @@ -357,17 +360,33 @@ 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.eraseConfig(true); // No return testing - Only returns on failure
OTA_DEBUG_PRINTF(" ESP.eraseConfig(true) failed!\n");
} else {
OTA_DEBUG_PRINTF(" WiFi.mode(WIFI_OFF) Timeout!\n");
}

delay(2000); // force a gap between retries
}

//this needs to be called in the loop()
void ArduinoOTAClass::handle() {
if (_state == OTA_RUNUPDATE) {
_runUpdate();
_state = OTA_IDLE;
}

if (_state == OTA_ERASEWIFI) {
eraseConfigAndReset();
return;
}

#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_MDNS)
if(_useMDNS)
MDNS.update(); //handle MDNS update as well, given that ArduinoOTA relies on it anyways
Expand Down
19 changes: 16 additions & 3 deletions libraries/ArduinoOTA/ArduinoOTA.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,17 @@ class UdpContext;
typedef enum {
OTA_IDLE,
OTA_WAITAUTH,
OTA_RUNUPDATE
OTA_RUNUPDATE,
OTA_ERASEWIFI
} ota_state_t;

typedef enum {
OTA_AUTH_ERROR,
OTA_BEGIN_ERROR,
OTA_CONNECT_ERROR,
OTA_RECEIVE_ERROR,
OTA_END_ERROR
OTA_END_ERROR,
OTA_ERASE_SETTINGS_ERROR
} ota_error_t;

class ArduinoOTAClass
Expand All @@ -45,7 +47,12 @@ class ArduinoOTAClass
void setPasswordHash(const char *password);

//Sets if the device should be rebooted after successful update. Default true
void setRebootOnSuccess(bool reboot);
//"eraseConfig" selects to erase WiFi Settings in conjunction with a reboot
//after a successful update. Default false - Legacy behavior
void setRebootOnSuccess(bool reboot, bool eraseConfig = false);

//Sets flag to erase WiFi Settings at reboot/reset.
void setEraseConfig(bool eraseConfig = true);

//This callback will be called when OTA connection has begun
void onStart(THandlerFunction fn);
Expand All @@ -64,6 +71,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();

Expand All @@ -84,6 +96,7 @@ class ArduinoOTAClass
bool _initialized = false;
bool _rebootOnSuccess = true;
bool _useMDNS = true;
bool _eraseConfig = false;
ota_state_t _state = OTA_IDLE;
int _size = 0;
int _cmd = 0;
Expand Down
2 changes: 2 additions & 0 deletions libraries/ArduinoOTA/examples/BasicOTA/BasicOTA.ino
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ void setup() {
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();
Expand Down
Loading