diff --git a/bootloaders/eboot/eboot.c b/bootloaders/eboot/eboot.c index 6e15d137b2..c3d0c278f7 100644 --- a/bootloaders/eboot/eboot.c +++ b/bootloaders/eboot/eboot.c @@ -17,8 +17,39 @@ #define SWRST do { (*((volatile uint32_t*) 0x60000700)) |= 0x80000000; } while(0); -extern void ets_wdt_enable(void); -extern void ets_wdt_disable(void); +/* + After Power Enable Pin, EXT_RST, or HWDT event, at "main()" in eboot, WDT is + disabled. Key WDT hardware registers are zero. + + After "ESP.restart()" and other soft restarts, at "main()" in eboot, WDT is enabled. + + References for the under-documented ets_wdt_* API + https://mongoose-os.com/blog/esp8266-watchdog-timer/ + http://cholla.mmto.org/esp8266/bootrom/boot.txt + + After looking at esp8266-watchdog-timer some more, `ets_wdt_enable(4, 12, 12)` + is good for eboot's needs. From a ".map" the NON-OS SDK does not use the + ets_wdt_* APIs, so our choices are not too critical. + The SDK will set up the WDT as it wants it. + + A rationale for keeping the "ets_wdt_enable()" line, if the system is not + stable during a "soft restart," the HWDT would provide a recovery reboot. +*/ +extern void ets_wdt_enable(uint32_t mode, uint32_t arg1, uint32_t arg2); +/* + "ets_wdt_disable" + + Diables WDT, then feeds the dog. + For current modes other than 1 or 2, returns the current mode. + For current mode 1, calls ets_timer_disarm, then return the current mode. + For current mode 2, calls ets_isr_mask, then return the current mode. + + I always see a return value of 0xFFFFFFFF. + + The return value would normally be used with ets_wdt_restore; however, that is + not an option since a valid prior call to ets_wdt_enable() may not have been done. +*/ +extern uint32_t ets_wdt_disable(void); int print_version(const uint32_t flash_addr) { @@ -241,12 +272,12 @@ int main() ets_wdt_disable(); res = copy_raw(cmd.args[0], cmd.args[1], cmd.args[2], false); - ets_wdt_enable(); + ets_wdt_enable(4, 12, 12); // WDT about 13 secs. ets_printf("%d\n", res); #if 0 - //devyte: this verify step below (cmp:) only works when the end of copy operation above does not overwrite the - //beginning of the image in the empty area, see #7458. Disabling for now. + //devyte: this verify step below (cmp:) only works when the end of copy operation above does not overwrite the + //beginning of the image in the empty area, see #7458. Disabling for now. //TODO: replace the below verify with hash type, crc, or similar. // Verify the copy ets_printf("cmp:"); @@ -257,7 +288,7 @@ int main() } ets_printf("%d\n", res); -#endif +#endif if (res == 0) { cmd.action = ACTION_LOAD_APP; cmd.args[0] = cmd.args[1]; diff --git a/bootloaders/eboot/eboot.elf b/bootloaders/eboot/eboot.elf index c036951ef8..0e679ff46c 100755 Binary files a/bootloaders/eboot/eboot.elf and b/bootloaders/eboot/eboot.elf differ diff --git a/cores/esp8266/Esp.cpp b/cores/esp8266/Esp.cpp index 20da390008..67719dcfe7 100644 --- a/cores/esp8266/Esp.cpp +++ b/cores/esp8266/Esp.cpp @@ -31,6 +31,7 @@ #include "umm_malloc/umm_malloc.h" #include #include "reboot_uart_dwnld.h" +#include "hardware_reset.h" extern "C" { #include "user_interface.h" @@ -519,7 +520,7 @@ struct rst_info * EspClass::getResetInfoPtr(void) { } bool EspClass::eraseConfig(void) { - const size_t cfgSize = 0x4000; + 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) { @@ -531,6 +532,17 @@ bool EspClass::eraseConfig(void) { return true; } +bool EspClass::eraseConfigAndReset(void) { + // Before calling, ensure the WiFi state is equivalent to + // "WiFi.mode(WIFI_OFF)." This will reduce the likelihood of the SDK + // performing WiFi data writes to Flash between erasing and resetting. + bool reset = eraseConfig(); + if (reset) { + hardware_reset(); + } + return reset; +} + uint8_t *EspClass::random(uint8_t *resultArray, const size_t outputSizeBytes) { /** diff --git a/cores/esp8266/Esp.h b/cores/esp8266/Esp.h index 9f97175c29..9cb4141292 100644 --- a/cores/esp8266/Esp.h +++ b/cores/esp8266/Esp.h @@ -212,6 +212,22 @@ class EspClass { static bool eraseConfig(); + /** + * @brief 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. + * + * This operation erases the running SDK's flash configuration space. + * As a precaution before calling, first call "WiFi.mode(WIFI_OFF)." + * + * If you need to erase "WiFi Settings" and reboot consider using + * "ArduinoOTA.eraseConfigAndReset()" it handles shutting down WiFi + * before the erase. + * @return bool result of operation. Always False on return. + * Function does not return on success. + */ + static bool eraseConfigAndReset(); + static uint8_t *random(uint8_t *resultArray, const size_t outputSizeBytes); static uint32_t random(); diff --git a/cores/esp8266/hardware_reset.cpp b/cores/esp8266/hardware_reset.cpp new file mode 100644 index 0000000000..827b402a5a --- /dev/null +++ b/cores/esp8266/hardware_reset.cpp @@ -0,0 +1,119 @@ +/* + 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 +#include +#include "hardware_reset.h" + + +// Extracted from RTOS_SDK eagle_soc.h +/* + * ESPRSSIF MIT License + * + * Copyright (c) 2015 + * + * 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" { + void hardware_reset(void) { + volatile uint32_t* const rtc_mem = (volatile uint32_t *)0x60001100u; + + // Block NMI or Software WDT from disturbing out restart reason + xt_rsil(15); + + // An HWDT reason would imply a fault or bug, but this reset was requested. + // Set hint reason to EXT_RST. From empirical evidence, an HWDT looks a lot + // like an EXT_RST. The WDT registers are reset to zero like an EXT_RST; + // however, the PLL initialization is still set. We can still read the Boot + // ROM serial output messages. + // SDK restart reason/hint 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); + } +}; diff --git a/cores/esp8266/hardware_reset.h b/cores/esp8266/hardware_reset.h new file mode 100644 index 0000000000..38d798b815 --- /dev/null +++ b/cores/esp8266/hardware_reset.h @@ -0,0 +1,14 @@ +#ifndef HARDWARE_RESET_H +#define HARDWARE_RESET_H + +#ifdef __cplusplus +extern "C" { +#endif + +void hardware_reset(void) __attribute__((noreturn)); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libraries/ArduinoOTA/ArduinoOTA.cpp b/libraries/ArduinoOTA/ArduinoOTA.cpp index 109bbb4959..1ef83b895f 100644 --- a/libraries/ArduinoOTA/ArduinoOTA.cpp +++ b/libraries/ArduinoOTA/ArduinoOTA.cpp @@ -3,6 +3,7 @@ #endif #include #include +#include #include "ArduinoOTA.h" #include "MD5Builder.h" #include "StreamString.h" @@ -24,10 +25,11 @@ extern "C" { #include #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) { diff --git a/libraries/ArduinoOTA/ArduinoOTA.h b/libraries/ArduinoOTA/ArduinoOTA.h index d1a81a316e..d3d93b9b36 100644 --- a/libraries/ArduinoOTA/ArduinoOTA.h +++ b/libraries/ArduinoOTA/ArduinoOTA.h @@ -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; diff --git a/libraries/ArduinoOTA/examples/OTAEraseConfig/OTAEraseConfig.ino b/libraries/ArduinoOTA/examples/OTAEraseConfig/OTAEraseConfig.ino new file mode 100644 index 0000000000..85a0797d47 --- /dev/null +++ b/libraries/ArduinoOTA/examples/OTAEraseConfig/OTAEraseConfig.ino @@ -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 +#include +#include +#include + +#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(); +} diff --git a/libraries/ArduinoOTA/examples/OTASdkCheck/OTASdkCheck.ino b/libraries/ArduinoOTA/examples/OTASdkCheck/OTASdkCheck.ino new file mode 100644 index 0000000000..1965321bda --- /dev/null +++ b/libraries/ArduinoOTA/examples/OTASdkCheck/OTASdkCheck.ino @@ -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 +#include +#include +#include +#include + +// 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(); +}