Skip to content

Commit f5d7d4a

Browse files
committed
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.
1 parent cf24024 commit f5d7d4a

File tree

9 files changed

+490
-32
lines changed

9 files changed

+490
-32
lines changed

cores/esp8266/Esp.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "umm_malloc/umm_malloc.h"
3232
#include <pgmspace.h>
3333
#include "reboot_uart_dwnld.h"
34+
#include "hardware_reset.h"
3435

3536
extern "C" {
3637
#include "user_interface.h"
@@ -518,8 +519,8 @@ struct rst_info * EspClass::getResetInfoPtr(void) {
518519
return &resetInfo;
519520
}
520521

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

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

532+
if (reset) {
533+
// Must be called in WiFi.mode(WIFI_OFF) state.
534+
hardware_reset();
535+
}
536+
531537
return true;
532538
}
533539

cores/esp8266/Esp.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,20 @@ class EspClass {
210210
static String getResetInfo();
211211
static struct rst_info * getResetInfoPtr();
212212

213-
static bool eraseConfig();
213+
/*
214+
Erases 4 sectors at the end of flash, 1 - RF_CAL and 3 - SYSTEMPARM.
215+
These are the same additional sectors that are erase when you select
216+
Erase Flash: "Sketch + WiFi Settings" from the Arduino IDE Tools menu.
217+
218+
As a precaution, since this operation erases the running SDKs flash
219+
configuration space, use reset flag "true" with eraseConfig. Also, for
220+
additional protection, call "WiFi.mode(WIFI_OFF)" before calling.
221+
222+
If you need to erase "WiFi Settings" and reboot consider using
223+
"ArduinoOTA.eraseConfigAndReset()" it handles shutting down WiFi
224+
before the erase.
225+
*/
226+
static bool eraseConfig(bool reset = false);
214227

215228
static uint8_t *random(uint8_t *resultArray, const size_t outputSizeBytes);
216229
static uint32_t random();

cores/esp8266/hardware_reset.cpp

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
Make the reset look like an EXT_RST reset by:
3+
* Set INTLEVEL to 15 blocking NMI Software WDT interference
4+
* set "restart reason" to REASON_EXT_SYS_RST
5+
* Config Hardware WDT for 1.6ms
6+
* Disable Hardware WDT Level-1 interrupt option
7+
* wait, ...
8+
9+
Inspired by RTOS SDK hardware_restart in panic.c
10+
*/
11+
12+
#include "Arduino.h"
13+
#include <user_interface.h>
14+
#include <ets_sys.h>
15+
#include "hardware_reset.h"
16+
17+
18+
// Extracted from RTOS_SDK eagle_soc.h
19+
/*
20+
* ESPRSSIF MIT License
21+
*
22+
* Copyright (c) 2015 <ESPRESSIF SYSTEMS (SHANGHAI) PTE LTD>
23+
*
24+
* Permission is hereby granted for use on ESPRESSIF SYSTEMS ESP8266 only, in which case,
25+
* it is free of charge, to any person obtaining a copy of this software and associated
26+
* documentation files (the "Software"), to deal in the Software without restriction, including
27+
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
28+
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished
29+
* to do so, subject to the following conditions:
30+
*
31+
* The above copyright notice and this permission notice shall be included in all copies or
32+
* substantial portions of the Software.
33+
*
34+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
35+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
36+
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
37+
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
38+
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
39+
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
40+
*
41+
*/
42+
#define REG_WRITE(_r, _v) (*(volatile uint32_t *)(_r)) = (_v)
43+
#define REG_READ(_r) (*(volatile uint32_t *)(_r))
44+
45+
//Watchdog reg {{
46+
#define PERIPHS_WDT_BASEADDR 0x60000900
47+
48+
#define WDT_CTL_ADDRESS 0
49+
#define WDT_OP_ADDRESS 0x4
50+
#define WDT_OP_ND_ADDRESS 0x8
51+
#define WDT_RST_ADDRESS 0x14
52+
53+
#define WDT_CTL_RSTLEN_MASK 0x38
54+
#define WDT_CTL_RSPMOD_MASK 0x6
55+
#define WDT_CTL_EN_MASK 0x1
56+
57+
#define WDT_CTL_RSTLEN_LSB 0x3
58+
#define WDT_CTL_RSPMOD_LSB 0x1
59+
#define WDT_CTL_EN_LSB 0
60+
61+
#define WDT_FEED_VALUE 0x73
62+
63+
#define WDT_REG_READ(_reg) REG_READ(PERIPHS_WDT_BASEADDR + _reg)
64+
#define WDT_REG_WRITE(_reg, _val) REG_WRITE(PERIPHS_WDT_BASEADDR + _reg, _val)
65+
#define CLEAR_WDT_REG_MASK(_reg, _mask) WDT_REG_WRITE(_reg, WDT_REG_READ(_reg) & (~_mask))
66+
#define SET_WDT_REG_MASK(_reg, _mask, _val) SET_PERI_REG_BITS((PERIPHS_WDT_BASEADDR + _reg), _mask, _val, 0)
67+
#undef WDT_FEED
68+
#define WDT_FEED() WDT_REG_WRITE(WDT_RST_ADDRESS, WDT_FEED_VALUE)
69+
//}}
70+
71+
// Inspired by RTOS SDK task_wdt.c and hardware_restart in panic.c
72+
73+
// Copyright 2018-2019 Espressif Systems (Shanghai) PTE LTD
74+
//
75+
// Licensed under the Apache License, Version 2.0 (the "License");
76+
// you may not use this file except in compliance with the License.
77+
// You may obtain a copy of the License at
78+
//
79+
// http://www.apache.org/licenses/LICENSE-2.0
80+
//
81+
// Unless required by applicable law or agreed to in writing, software
82+
// distributed under the License is distributed on an "AS IS" BASIS,
83+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
84+
// See the License for the specific language governing permissions and
85+
// limitations under the License.
86+
87+
extern "C" {
88+
[[noreturn]] void hardware_reset(void) {
89+
volatile uint32_t* const rtc_mem = (volatile uint32_t *)0x60001100u;
90+
91+
// Block NMI WDT from disturbing out restart reason
92+
xt_rsil(15);
93+
94+
// SDK restart reason location
95+
rtc_mem[0] = REASON_EXT_SYS_RST;
96+
97+
// Disable WDT
98+
CLEAR_WDT_REG_MASK(WDT_CTL_ADDRESS, WDT_CTL_EN_MASK);
99+
100+
// Set Reset pulse to maximum
101+
// Select Reset only - no level-1 interrupt
102+
SET_WDT_REG_MASK(WDT_CTL_ADDRESS,
103+
WDT_CTL_RSTLEN_MASK | WDT_CTL_RSPMOD_MASK,
104+
(7 << WDT_CTL_RSTLEN_LSB) | (2 << WDT_CTL_RSPMOD_LSB));
105+
106+
// Set WDT Reset timer to 1.6 ms.
107+
WDT_REG_WRITE(WDT_OP_ADDRESS, 1); // 2^n * 0.8ms, mask 0xf, n = 1 -> (2^1 = 2) * 0.8 * 0.001 = 0.0016
108+
109+
// Enable WDT
110+
SET_WDT_REG_MASK(WDT_CTL_ADDRESS, WDT_CTL_EN_MASK, 1 << WDT_CTL_EN_LSB);
111+
112+
while (true);
113+
}
114+
};

cores/esp8266/hardware_reset.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#ifndef HARDWARE_RESET_H
2+
#define HARDWARE_RESET_H
3+
#ifdef __cplusplus
4+
extern "C" {
5+
#endif
6+
7+
[[noreturn]] extern void hardware_reset(void);
8+
9+
#ifdef __cplusplus
10+
}
11+
#endif
12+
#endif

libraries/ArduinoOTA/ArduinoOTA.cpp

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,11 @@ extern "C" {
2424
#include <ESP8266mDNS.h>
2525
#endif
2626

27-
#ifdef DEBUG_ESP_OTA
28-
#ifdef DEBUG_ESP_PORT
27+
#if defined(DEBUG_ESP_OTA) && defined(DEBUG_ESP_PORT)
2928
#define OTA_DEBUG DEBUG_ESP_PORT
30-
#endif
29+
#define OTA_DEBUG_PRINTF(fmt, ...) OTA_DEBUG.printf_P(PSTR(fmt), ##__VA_ARGS__)
30+
#else
31+
#define OTA_DEBUG_PRINTF(...)
3132
#endif
3233

3334
ArduinoOTAClass::ArduinoOTAClass()
@@ -89,8 +90,9 @@ void ArduinoOTAClass::setPasswordHash(const char * password) {
8990
}
9091
}
9192

92-
void ArduinoOTAClass::setRebootOnSuccess(bool reboot){
93+
void ArduinoOTAClass::setRebootOnSuccess(bool reboot, bool eraseConfig){
9394
_rebootOnSuccess = reboot;
95+
_eraseConfig = eraseConfig;
9496
}
9597

9698
void ArduinoOTAClass::begin(bool useMDNS) {
@@ -119,7 +121,7 @@ void ArduinoOTAClass::begin(bool useMDNS) {
119121
if(!_udp_ota->listen(IP_ADDR_ANY, _port))
120122
return;
121123
_udp_ota->onRx(std::bind(&ArduinoOTAClass::_onRx, this));
122-
124+
123125
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_MDNS)
124126
if(_useMDNS) {
125127
MDNS.begin(_hostname.c_str());
@@ -133,9 +135,7 @@ void ArduinoOTAClass::begin(bool useMDNS) {
133135
#endif
134136
_initialized = true;
135137
_state = OTA_IDLE;
136-
#ifdef OTA_DEBUG
137-
OTA_DEBUG.printf("OTA server at: %s.local:%u\n", _hostname.c_str(), _port);
138-
#endif
138+
OTA_DEBUG_PRINTF("OTA server at: %s.local:%u\n", _hostname.c_str(), _port);
139139
}
140140

141141
int ArduinoOTAClass::parseInt(){
@@ -243,13 +243,11 @@ void ArduinoOTAClass::_runUpdate() {
243243
IPAddress ota_ip = _ota_ip;
244244

245245
if (!Update.begin(_size, _cmd)) {
246-
#ifdef OTA_DEBUG
247-
OTA_DEBUG.println("Update Begin Error");
248-
#endif
246+
OTA_DEBUG_PRINTF("Update Begin Error\n");
249247
if (_error_callback) {
250248
_error_callback(OTA_BEGIN_ERROR);
251249
}
252-
250+
253251
StreamString ss;
254252
Update.printError(ss);
255253
_udp_ota->append("ERR: ", 5);
@@ -275,9 +273,7 @@ void ArduinoOTAClass::_runUpdate() {
275273

276274
WiFiClient client;
277275
if (!client.connect(_ota_ip, _ota_port)) {
278-
#ifdef OTA_DEBUG
279-
OTA_DEBUG.printf("Connect Failed\n");
280-
#endif
276+
OTA_DEBUG_PRINTF("Connect Failed\n");
281277
_udp_ota->listen(IP_ADDR_ANY, _port);
282278
if (_error_callback) {
283279
_error_callback(OTA_CONNECT_ERROR);
@@ -293,9 +289,7 @@ void ArduinoOTAClass::_runUpdate() {
293289
while (!client.available() && waited--)
294290
delay(1);
295291
if (!waited){
296-
#ifdef OTA_DEBUG
297-
OTA_DEBUG.printf("Receive Failed\n");
298-
#endif
292+
OTA_DEBUG_PRINTF("Receive Failed\n");
299293
_udp_ota->listen(IP_ADDR_ANY, _port);
300294
if (_error_callback) {
301295
_error_callback(OTA_RECEIVE_ERROR);
@@ -320,18 +314,26 @@ void ArduinoOTAClass::_runUpdate() {
320314
client.flush();
321315
delay(1000);
322316
client.stop();
323-
#ifdef OTA_DEBUG
324-
OTA_DEBUG.printf("Update Success\n");
325-
#endif
317+
OTA_DEBUG_PRINTF("Update Success\n");
326318
if (_end_callback) {
327319
_end_callback();
328320
}
329321
if(_rebootOnSuccess){
330-
#ifdef OTA_DEBUG
331-
OTA_DEBUG.printf("Rebooting...\n");
332-
#endif
322+
OTA_DEBUG_PRINTF("Rebooting...\n");
333323
//let serial/network finish tasks that might be given in _end_callback
334324
delay(100);
325+
if (_eraseConfig) {
326+
OTA_DEBUG_PRINTF("Erase Config and Hard Reset ...\n");
327+
eraseConfigAndReset(); // returns on ESP.eraseConfig failure
328+
OTA_DEBUG_PRINTF("ESP.eraseConfig(true) failed!\n");
329+
//C What is the best action to take on failure?
330+
//C 1) On failure, we could invalidate eboot_command buffer -
331+
//C aborting the flash update.
332+
//C 2) Or, just leave it znc restart.
333+
if (_error_callback) {
334+
_error_callback(OTA_ERASE_SETTINGS_ERROR);
335+
}
336+
}
335337
ESP.restart();
336338
}
337339
} else {
@@ -357,10 +359,14 @@ void ArduinoOTAClass::end() {
357359
}
358360
#endif
359361
_state = OTA_IDLE;
360-
#ifdef OTA_DEBUG
361-
OTA_DEBUG.printf("OTA server stopped.\n");
362-
#endif
362+
OTA_DEBUG_PRINTF("OTA server stopped.\n");
363+
}
364+
365+
void ArduinoOTAClass::eraseConfigAndReset() {
366+
WiFi.mode(WIFI_OFF);
367+
ESP.eraseConfig(true); // No return testing - Only returns on failure
363368
}
369+
364370
//this needs to be called in the loop()
365371
void ArduinoOTAClass::handle() {
366372
if (_state == OTA_RUNUPDATE) {

libraries/ArduinoOTA/ArduinoOTA.h

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ typedef enum {
1818
OTA_BEGIN_ERROR,
1919
OTA_CONNECT_ERROR,
2020
OTA_RECEIVE_ERROR,
21-
OTA_END_ERROR
21+
OTA_END_ERROR,
22+
OTA_ERASE_SETTINGS_ERROR
2223
} ota_error_t;
2324

2425
class ArduinoOTAClass
@@ -45,7 +46,11 @@ class ArduinoOTAClass
4546
void setPasswordHash(const char *password);
4647

4748
//Sets if the device should be rebooted after successful update. Default true
48-
void setRebootOnSuccess(bool reboot);
49+
//Sets eraseConfig conditional on RebootOnSuccess. Default false
50+
void setRebootOnSuccess(bool reboot, bool eraseConfig = false);
51+
52+
//Sets flag to erase WiFi Settings at reboot/reset.
53+
void setEraseConfig(bool eraseConfig = true);
4954

5055
//This callback will be called when OTA connection has begun
5156
void onStart(THandlerFunction fn);
@@ -64,6 +69,11 @@ class ArduinoOTAClass
6469

6570
//Ends the ArduinoOTA service
6671
void end();
72+
73+
//Has the effect of Arduino IDE Tools "Erase Flash" with "+ WiFi Settings"
74+
//selection. Only returns on failure to erase flash.
75+
void eraseConfigAndReset();
76+
6777
//Call this in loop() to run the service. Also calls MDNS.update() when begin() or begin(true) is used.
6878
void handle();
6979

@@ -84,6 +94,7 @@ class ArduinoOTAClass
8494
bool _initialized = false;
8595
bool _rebootOnSuccess = true;
8696
bool _useMDNS = true;
97+
bool _eraseConfig = false;
8798
ota_state_t _state = OTA_IDLE;
8899
int _size = 0;
89100
int _cmd = 0;

libraries/ArduinoOTA/examples/BasicOTA/BasicOTA.ino

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ void setup() {
6464
Serial.println("Receive Failed");
6565
} else if (error == OTA_END_ERROR) {
6666
Serial.println("End Failed");
67+
} else if (error == OTA_ERASE_SETTINGS_ERROR) {
68+
Serial.println("Failed to erase WiFi Settings");
6769
}
6870
});
6971
ArduinoOTA.begin();

0 commit comments

Comments
 (0)