diff --git a/ESPMegaPRO-OS-SDK/lib/ESPMegaPRO/ESPMegaProOS.cpp b/ESPMegaPRO-OS-SDK/lib/ESPMegaPRO/ESPMegaProOS.cpp index 47d85b5..5db70e5 100644 --- a/ESPMegaPRO-OS-SDK/lib/ESPMegaPRO/ESPMegaProOS.cpp +++ b/ESPMegaPRO-OS-SDK/lib/ESPMegaPRO/ESPMegaProOS.cpp @@ -73,7 +73,9 @@ bool ESPMegaPRO::begin() esp_restart(); } } - + // Recovery Mode + recovery.bindFRAM(&fram, 600); + recovery.begin(); return true; } @@ -89,6 +91,7 @@ void ESPMegaPRO::loop() { inputs.loop(); outputs.loop(); + recovery.loop(); for (int i = 0; i < 255; i++) { if (cardInstalled[i]) diff --git a/ESPMegaPRO-OS-SDK/lib/ESPMegaPRO/ESPMegaProOS.hpp b/ESPMegaPRO-OS-SDK/lib/ESPMegaPRO/ESPMegaProOS.hpp index b20f1ce..4b6bd14 100644 --- a/ESPMegaPRO-OS-SDK/lib/ESPMegaPRO/ESPMegaProOS.hpp +++ b/ESPMegaPRO-OS-SDK/lib/ESPMegaPRO/ESPMegaProOS.hpp @@ -15,6 +15,7 @@ #include #include #include +#include // ESPMega Pro R3 Board Address #define FRAM_ADDRESS 0x56 @@ -82,6 +83,11 @@ class ESPMegaPRO { * @note You must call enableWebServer() before using this component. */ ESPMegaWebServer *webServer; + /** + * @brief This component is used to enter recovery mode when the ESPMegaPRO board is in a bootloop. + * @typedef ESPMegaRecovery + */ + ESPMegaRecovery recovery; private: bool iotEnabled = false; bool internalDisplayEnabled = false; diff --git a/ESPMegaPRO-OS-SDK/lib/ESPMegaPRO/ESPMegaRecovery.cpp b/ESPMegaPRO-OS-SDK/lib/ESPMegaPRO/ESPMegaRecovery.cpp new file mode 100644 index 0000000..f6fe70e --- /dev/null +++ b/ESPMegaPRO-OS-SDK/lib/ESPMegaPRO/ESPMegaRecovery.cpp @@ -0,0 +1,141 @@ +#include +ESPMegaRecovery::ESPMegaRecovery() +{ + // Initialize all the pointers to null + this->fram = nullptr; + this->fram_address = 0; + this->bootloop_counter = 0; + this->recovery_mode = false; + this->web_server = nullptr; + this->iot = nullptr; +} +void ESPMegaRecovery::begin() { + // Retrieve the bootloop counter from the FRAM + if(this->fram != nullptr) { + this->bootloop_counter = this->fram->read8(this->fram_address); + } + // Inclement the bootloop counter + this->inclementBootloopCounter(); + ESP_LOGV("ESPMegaRecovery", "Bootloop counter: %d", this->getBootloopCounter()); + // If the bootloop counter is greater than 5, enter recovery mode + if(this->getBootloopCounter() > 5) { + ESP_LOGE("ESPMegaRecovery", "Bootloop detected"); + // Reset the bootloop counter to prevent re-entering recovery mode + // The device might unintentionally restart multiple times + // By resetting the counter, the user can press reset once in recovery mode to exit + ESP_LOGD("ESPMegaRecovery", "Resetting bootloop counter"); + this->resetBootloopCounter(); + ESP_LOGW("ESPMegaRecovery", "Entering recovery mode"); + this->enterRecoveryMode(); + this->loop(); + } +} +void ESPMegaRecovery::loop() { + // If the device is in recovery mode, block all other tasks + if(this->isRecoveryMode()) { + int i = 0; + while(true) { + if (i%10 == 0) { + ESP_LOGV("ESPMegaRecovery", "System is in recovery mode, no tasks will be executed"); + ESP_LOGV("ESPMegaRecovery", "Please upload a new firmware to exit recovery mode"); + } + // This code will become the new loop + delay(1000); + i++; + } + } + // Watchdog timer + static bool booted = false; + static uint32_t boot_time = millis(); + if(!booted) { + if(millis() - boot_time > RECOVERY_WATCHDOG_TIMEOUT * 1000) { + ESP_LOGI("ESPMegaRecovery", "System booted successfully, resetting bootloop counter"); + this->resetBootloopCounter(); + booted = true; + } + } +} +void ESPMegaRecovery::enterRecoveryMode() { + // Set the recovery mode flag + this->recovery_mode = true; + // Enabling the IoT module + ESP_LOGI("ESPMegaRecovery", "Enabling the IoT module"); + this->iot = new ESPMegaIoT(); + this->iot->bindFRAM(this->fram); + ETH.begin(); + this->iot->bindEthernetInterface(Ð); + ESP_LOGI("ESPMegaRecovery", "Loading network configuration"); + this->iot->loadNetworkConfig(); + ESP_LOGI("ESPMegaRecovery", "Attempting to connect to the network"); + this->iot->connectNetwork(); + // Start the web server + ESP_LOGI("ESPMegaRecovery", "Starting the web server"); + this->web_server = new ESPMegaWebServer(80, this->iot); + this->web_server->bindFRAM(this->fram); + this->web_server->loadCredentialsFromFRAM(); + ESP_LOGI("ESPMegaRecovery", "Aquiring the web server instance"); + AsyncWebServer *server = this->web_server->getServer(); + server->begin(); + // Add OTA update and restart endpoint + ESP_LOGI("ESPMegaRecovery", "Adding OTA update and reboot endpoints"); + auto bindedDashboardHandler = std::bind(&ESPMegaWebServer::dashboardHandler, this->web_server, std::placeholders::_1); + auto bindedOtaRequestHandler = std::bind(&ESPMegaWebServer::otaRequestHandler, this->web_server, std::placeholders::_1); + auto bindedOtaUploadHandler = std::bind(&ESPMegaWebServer::otaUploadHandler, this->web_server, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6); + auto bindedDeviceInfoHandler = std::bind(&ESPMegaRecovery::getDeviceInfoHandler, this, std::placeholders::_1); + auto bindedConfigHandler = std::bind(&ESPMegaRecovery::configHandler, this, std::placeholders::_1); + server->on("/", HTTP_GET, bindedDashboardHandler); + server->on("/ota_update", HTTP_POST, bindedOtaRequestHandler, bindedOtaUploadHandler); + server->on("/reboot", HTTP_GET, std::bind(&ESPMegaWebServer::rebootHandler, this->web_server, std::placeholders::_1)); + server->on("/get_device_info", HTTP_GET, bindedDeviceInfoHandler); + server->on("/config", HTTP_GET, bindedConfigHandler); +} +void ESPMegaRecovery::bindFRAM(FRAM *fram, uint32_t address) { + this->fram = fram; + this->fram_address = address; +} +uint8_t ESPMegaRecovery::getBootloopCounter() { + return this->bootloop_counter; +} +void ESPMegaRecovery::inclementBootloopCounter() { + this->bootloop_counter++; + if(this->fram != nullptr) { + this->fram->write8(this->fram_address, this->bootloop_counter); + } +} +void ESPMegaRecovery::resetBootloopCounter() { + this->bootloop_counter = 0; + if(this->fram != nullptr) { + this->fram->write8(this->fram_address, this->bootloop_counter); + } +} +bool ESPMegaRecovery::isRecoveryMode() { + return this->recovery_mode; +} + +void ESPMegaRecovery::getDeviceInfoHandler(AsyncWebServerRequest *request) { + if (!request->authenticate(this->web_server->getWebUsername(), this->web_server->getWebPassword())) + { + return request->requestAuthentication(); + } + StaticJsonDocument<512> doc; + doc["hostname"] = this->iot->getNetworkConfig()->hostname; + doc["ip_address"] = this->iot->getIp().toString(); + doc["mac_address"] = this->iot->getMac(); + doc["model"] = BOARD_MODEL; + doc["mqtt_server"] = "Recovery"; + doc["mqtt_port"] = "Mode"; + doc["base_topic"] = "Recovery Mode"; + doc["mqtt_connected"] = "Recovery Mode"; + doc["software_version"] = "EMG-SAFE-1.0.0"; + doc["sdk_version"] = SDK_VESRION; + doc["idf_version"] = IDF_VER; + char buffer[512]; + serializeJson(doc, buffer); + request->send(200, "application/json", buffer); +} + +void ESPMegaRecovery::configHandler(AsyncWebServerRequest *request){ + // Say Not Available in Recovery Mode + // Wait 3s then redirect to / + request->send(500, "text/html", "

RECOVERY MODE

Configuration is not available in recovery mode

"); +} \ No newline at end of file diff --git a/ESPMegaPRO-OS-SDK/lib/ESPMegaPRO/ESPMegaRecovery.hpp b/ESPMegaPRO-OS-SDK/lib/ESPMegaPRO/ESPMegaRecovery.hpp new file mode 100644 index 0000000..407e3eb --- /dev/null +++ b/ESPMegaPRO-OS-SDK/lib/ESPMegaPRO/ESPMegaRecovery.hpp @@ -0,0 +1,36 @@ +#include +#include +#include +/** + * @brief Recovery mode for ESPMega + * Recovery mode is a mode that is entered when the device is in a bootloop + * In this mode, the device will block all other tasks and wait for a new firmware to be uploaded over the air + * To exit recovery mode, the device must be restarted or a new firmware must be uploaded + * This is useful when the device is in a bootloop and the user can't access the device to upload a new firmware (ota bricking) + * The device is considered to be in a bootloop when it is restarted before RECOVERY_WATCHDOG_TIMEOUT seconds for 5 consecutive times + * The timer is started when the device's initialization is complete (enter loop) + */ + +#define RECOVERY_WATCHDOG_TIMEOUT 15 // The time in seconds to wait for a restart before considering it a bootloop + +class ESPMegaRecovery { +public: + ESPMegaRecovery(); + void begin(); + void loop(); + void enterRecoveryMode(); // Enter recovery mode, Block all other tasks, start OTA server + void bindFRAM(FRAM* fram, uint32_t address); // Bind the FRAM block to store the bootloop counter + uint8_t getBootloopCounter(); // Get the bootloop counter + void inclementBootloopCounter(); // Increment the bootloop counter + void resetBootloopCounter(); // Reset the bootloop counter + bool isRecoveryMode(); // Check if the device is in recovery mode +private: + void getDeviceInfoHandler(AsyncWebServerRequest *request); + void configHandler(AsyncWebServerRequest *request); + FRAM* fram; + uint32_t fram_address; + uint8_t bootloop_counter; + ESPMegaIoT* iot; + ESPMegaWebServer* web_server; + bool recovery_mode; +}; \ No newline at end of file diff --git a/ESPMegaPRO-OS-SDK/lib/ESPMegaPRO/ESPMegaWebServer.hpp b/ESPMegaPRO-OS-SDK/lib/ESPMegaPRO/ESPMegaWebServer.hpp index f6356e2..386f91d 100644 --- a/ESPMegaPRO-OS-SDK/lib/ESPMegaPRO/ESPMegaWebServer.hpp +++ b/ESPMegaPRO-OS-SDK/lib/ESPMegaPRO/ESPMegaWebServer.hpp @@ -35,17 +35,6 @@ class ESPMegaWebServer void saveCredentialsToFRAM(); AsyncWebServer* getServer(); bool checkAuthentication(AsyncWebServerRequest *request); - private: - // FRAM - FRAM *fram; - // Credentials - char webUsername[32]; - char webPassword[32]; - // Web Server - AsyncWebServer *server; - uint16_t port; - // ESPMegaIoT - ESPMegaIoT *iot; // Endpoints Handlers void dashboardHandler(AsyncWebServerRequest *request); void configHandler(AsyncWebServerRequest *request); @@ -57,4 +46,15 @@ class ESPMegaWebServer void otaUploadHandler(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); void restAPIHandler(AsyncWebServerRequest *request); void rebootHandler(AsyncWebServerRequest *request); + private: + // FRAM + FRAM *fram; + // Credentials + char webUsername[32]; + char webPassword[32]; + // Web Server + AsyncWebServer *server; + uint16_t port; + // ESPMegaIoT + ESPMegaIoT *iot; }; \ No newline at end of file diff --git a/ESPMegaPRO-OS-SDK/src/main.cpp b/ESPMegaPRO-OS-SDK/src/main.cpp index 10419de..e8e43af 100644 --- a/ESPMegaPRO-OS-SDK/src/main.cpp +++ b/ESPMegaPRO-OS-SDK/src/main.cpp @@ -14,7 +14,7 @@ // #define FRAM_DEBUG // #define MQTT_DEBUG -// #define WRITE_DEFAULT_NETCONF +#define WRITE_DEFAULT_NETCONF //#define CLIMATE_CARD_ENABLE #define MQTT_CARD_REGISTER #define DISPLAY_ENABLE