diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index af612aa..48a9482 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -38,10 +38,11 @@ jobs: cmake -D CMAKE_TOOLCHAIN_FILE=../arduino-toolchain/Arduino-toolchain.cmake .. echo "# Espressif ESP32 Partition Table" > partitions.csv echo "# Name, Type, SubType, Offset, Size, Flags" >> partitions.csv - echo "nvs,data,nvs,0x9000,20K," >> partitions.csv - echo "otadata,data,ota,0xe000,8K," >> partitions.csv - echo "app0,app,ota_0,0x10000,3M," >> partitions.csv - echo "spiffs,data,spiffs,0x310000,960K," >> partitions.csv + echo "nvs, data, nvs, 0x9000, 0x5000," >> partitions.csv + echo "otadata, data, ota, 0xe000, 0x2000," >> partitions.csv + echo "app0, app, ota_0, 0x10000, 0x1E0000," >> partitions.csv + echo "app1, app, ota_1, 0x1F0000,0x1E0000," >> partitions.csv + echo "spiffs, data, spiffs, 0x3D0000,0x30000," >> partitions.csv make - name: Upload artifacts run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 8dd9c71..c0c14e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,7 @@ file(GLOB SRCFILES NukiWrapper.cpp NukiOpenerWrapper.cpp MqttTopics.h + Ota.cpp WebCfgServer.cpp PresenceDetection.cpp PreferencesKeys.h diff --git a/Ota.cpp b/Ota.cpp new file mode 100644 index 0000000..7948266 --- /dev/null +++ b/Ota.cpp @@ -0,0 +1,29 @@ +#include +#include "Ota.h" + +#define FULL_PACKET 1436 // HTTP_UPLOAD_BUFLEN in WebServer,h + +void Ota::updateFirmware(uint8_t* buf, size_t size) +{ + if (!_updateFlag) + { //If it's the first packet of OTA since bootup, begin OTA + Serial.println("BeginOTA"); + esp_ota_begin(esp_ota_get_next_update_partition(NULL), OTA_SIZE_UNKNOWN, &otaHandler); + _updateFlag = true; + } + esp_ota_write(otaHandler, buf, size); + if (size != FULL_PACKET) + { + esp_ota_end(otaHandler); + Serial.println("EndOTA"); + if (ESP_OK == esp_ota_set_boot_partition(esp_ota_get_next_update_partition(NULL))) + { + delay(2000); + esp_restart(); + } + else + { + Serial.println("Upload Error"); + } + } +} diff --git a/Ota.h b/Ota.h new file mode 100644 index 0000000..967c1d5 --- /dev/null +++ b/Ota.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include +#include "esp_ota_ops.h" + +class Ota +{ +public: + void updateFirmware(uint8_t* buf, size_t size); + +private: + bool _updateFlag = false; + esp_ota_handle_t otaHandler = 0; +}; diff --git a/Version.h b/Version.h index dc8683d..c184fc7 100644 --- a/Version.h +++ b/Version.h @@ -1,3 +1,3 @@ #pragma once -#define nuki_hub_version "3.1" \ No newline at end of file +#define nuki_hub_version "4.0" \ No newline at end of file diff --git a/WebCfgServer.cpp b/WebCfgServer.cpp index 8cec783..8771ada 100644 --- a/WebCfgServer.cpp +++ b/WebCfgServer.cpp @@ -112,6 +112,27 @@ void WebCfgServer::initialize() waitAndProcess(false, 1000); } }); + _server.on("/ota", [&]() { + if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { + return _server.requestAuthentication(); + } + String response = ""; + buildOtaHtml(response); + _server.send(200, "text/html", response); + }); + _server.on("/uploadota", HTTP_POST, [&]() { + if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { + return _server.requestAuthentication(); + } + + _server.send(200, "text/html", ""); + }, [&]() { + if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { + return _server.requestAuthentication(); + } + + handleOtaUpload(); + }); _server.begin(); } @@ -366,6 +387,11 @@ void WebCfgServer::buildHtml(String& response) response.concat(""); response.concat(""); + response.concat("

Firmware update

"); + response.concat("
"); + response.concat(""); + response.concat("
"); + if(_allowRestartToPortal) { response.concat("

WiFi

"); @@ -438,14 +464,32 @@ void WebCfgServer::buildCredHtml(String &response) } - response.concat("\n"); - response.concat("\n"); +} + +void WebCfgServer::buildOtaHtml(String &response) +{ + buildHtmlHeader(response); + response.concat("
Choose a file to upload:
"); + response.concat("
"); + response.concat("
Initiating Over-the-air update. This will take about a minute, please be patient.
You will be forwarwed automatically when the update is complete.
"); + response.concat(""); + response.concat("\n\n"); } void WebCfgServer::buildMqttConfigHtml(String &response) { response.concat("
"); - response.concat("

MQTT COnfiguration

"); + response.concat("

MQTT Configuration

"); response.concat(""); printInputField(response, "HOSTNAME", "Host name", _preferences->getString(preference_hostname).c_str(), 100); printInputField(response, "MQTTSERVER", "MQTT Broker", _preferences->getString(preference_mqtt_broker).c_str(), 100); @@ -661,3 +705,28 @@ void WebCfgServer::waitAndProcess(const bool blocking, const uint32_t duration) } } +void WebCfgServer::handleOtaUpload() +{ + if (_server.uri() != "/uploadota") { + return; + } + + esp_task_wdt_init(30, false); + + HTTPUpload& upload = _server.upload(); + if (upload.status == UPLOAD_FILE_START) { + String filename = upload.filename; + if (!filename.startsWith("/")) { + filename = "/" + filename; + } + Serial.print("handleFileUpload Name: "); Serial.println(filename); + } else if (upload.status == UPLOAD_FILE_WRITE) { + _transferredSize = _transferredSize + upload.currentSize; + Serial.println(_transferredSize); + _ota.updateFirmware(upload.buf, upload.currentSize); + } else if (upload.status == UPLOAD_FILE_END) { + Serial.println(); + Serial.print("handleFileUpload Size: "); Serial.println(upload.totalSize); + } +} + diff --git a/WebCfgServer.h b/WebCfgServer.h index 6d0b4b3..b9bdd59 100644 --- a/WebCfgServer.h +++ b/WebCfgServer.h @@ -5,6 +5,7 @@ #include "NukiWrapper.h" #include "Network.h" #include "NukiOpenerWrapper.h" +#include "Ota.h" enum class TokenType { @@ -32,6 +33,7 @@ private: bool processArgs(String& message); void buildHtml(String& response); void buildCredHtml(String& response); + void buildOtaHtml(String& response); void buildMqttConfigHtml(String& response); void buildConfirmHtml(String& response, const String &message, uint32_t redirectDelay = 5); void buildConfigureWifiHtml(String& response); @@ -47,17 +49,21 @@ private: String generateConfirmCode(); void waitAndProcess(const bool blocking, const uint32_t duration); + void handleOtaUpload(); WebServer _server; NukiWrapper* _nuki; NukiOpenerWrapper* _nukiOpener; Network* _network; Preferences* _preferences; + Ota _ota; bool _hasCredentials = false; char _credUser[20] = {0}; char _credPassword[20] = {0}; bool _allowRestartToPortal = false; + uint32_t _transferredSize = 0; + bool _otaStart = true; String _confirmCode = "----"; diff --git a/main.cpp b/main.cpp index d346620..94d1cfd 100644 --- a/main.cpp +++ b/main.cpp @@ -89,7 +89,7 @@ void setupTasks() { // configMAX_PRIORITIES is 25 - xTaskCreate(networkTask, "ntw", 8192, NULL, 3, NULL); + xTaskCreatePinnedToCore(networkTask, "ntw", 8192, NULL, 3, NULL, 1); xTaskCreate(nukiTask, "nuki", 4096, NULL, 2, NULL); xTaskCreate(presenceDetectionTask, "prdet", 768, NULL, 5, NULL); xTaskCreate(checkMillisTask, "mlchk", 512, NULL, 1, NULL); diff --git a/webflash/nuki_hub.bin b/webflash/nuki_hub.bin index dfc2a77..816504a 100644 Binary files a/webflash/nuki_hub.bin and b/webflash/nuki_hub.bin differ diff --git a/webflash/nuki_hub.partitions.bin b/webflash/nuki_hub.partitions.bin index 94a1995..fa042d6 100644 Binary files a/webflash/nuki_hub.partitions.bin and b/webflash/nuki_hub.partitions.bin differ