From 5c28d2aec34d3d7b14f74b80684d19c3e84c4631 Mon Sep 17 00:00:00 2001 From: Mathieu Carbou Date: Tue, 13 Aug 2024 01:40:31 +0200 Subject: [PATCH 1/2] SafeBoot support --- .github/workflows/build.yml | 6 - .github/workflows/cpplint.yml | 1 - .gitmodules | 4 - data/config.html | 18 +++ include/YaSolR.h | 4 +- include/YaSolRWebsite.h | 3 +- include/i18n/en.h | 4 +- include/i18n/fr.h | 4 +- lib/ElegantOTAPro | 1 - platformio.ini | 68 +++++++---- src/Website.cpp | 17 ++- src/init/Boot.cpp | 4 +- src/init/Config.cpp | 13 +- src/init/Core.cpp | 2 +- src/init/Debug.cpp | 2 +- src/init/Events.cpp | 10 -- src/init/REST.cpp | 11 ++ src/tasks/Debug.cpp | 1 + src/tasks/Lights.cpp | 2 +- src/tasks/MQTT.cpp | 52 ++++---- src/tasks/{OTA.cpp => SafeBoot.cpp} | 5 +- tools/cacerts.py | 179 ++++++++++++++++++++++++++++ tools/data.py | 49 ++++++-- tools/factory.py | 92 +++++++++----- tools/partitions-4MB-safeboot.csv | 4 +- tools/partitions-8MB-safeboot.csv | 4 +- tools/upload.py | 130 -------------------- 27 files changed, 420 insertions(+), 270 deletions(-) delete mode 160000 lib/ElegantOTAPro rename src/tasks/{OTA.cpp => SafeBoot.cpp} (63%) create mode 100644 tools/cacerts.py delete mode 100644 tools/upload.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ceb44e4..681f33b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -74,12 +74,6 @@ jobs: ~/.cache/pip ~/.platformio - - name: Cache PlatformIO Dependencies (OSS) - uses: actions/cache@v4 - with: - key: ${{ runner.os }}-pio-oss-${{ matrix.environment }} - path: .pio - - name: Python uses: actions/setup-python@v5 with: diff --git a/.github/workflows/cpplint.yml b/.github/workflows/cpplint.yml index 66b2e63..e0c8998 100644 --- a/.github/workflows/cpplint.yml +++ b/.github/workflows/cpplint.yml @@ -41,7 +41,6 @@ jobs: --recursive \ --filter=-whitespace/line_length,-whitespace/braces,-whitespace/comments,-runtime/indentation_namespace,-whitespace/indent,-readability/braces,-whitespace/newline,-readability/todo,-build/c++11,-runtime/references \ --exclude=lib/DimmableLight \ - --exclude=lib/ElegantOTAPro \ --exclude=lib/ESPDASHPro \ --exclude=lib/MycilaDimmer/doc \ --exclude=lib/WebSerialPro \ diff --git a/.gitmodules b/.gitmodules index 11f8a8b..673ae4a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,7 +1,3 @@ -[submodule "lib/ElegantOTAPro"] - path = lib/ElegantOTAPro - url = git@github.com:mathieucarbou/ElegantOTA-Pro.git - branch = yasolr [submodule "lib/WebSerialPro"] path = lib/WebSerialPro url = git@github.com:mathieucarbou/WebSerial-Pro.git diff --git a/data/config.html b/data/config.html index 3aed272..81e6e70 100644 --- a/data/config.html +++ b/data/config.html @@ -16,6 +16,13 @@

YaSolR Configuration

+ + +
+ +
+ +
@@ -325,6 +332,17 @@

YaSolR Configuration

}); }); + document + .getElementById("safeboot-form") + .addEventListener("submit", function (event) { + event.preventDefault(); + fetch("/api/system/safeboot", { method: "POST" }).then((res) => { + setTimeout(function () { + window.location.reload(); + }, 2000); + }); + }); + document .getElementById("reset-form") .addEventListener("submit", function (event) { diff --git a/include/YaSolR.h b/include/YaSolR.h index c56ddb9..5f1ffe5 100644 --- a/include/YaSolR.h +++ b/include/YaSolR.h @@ -42,11 +42,9 @@ #ifdef APP_MODEL_PRO #include - #include #include #else #include - #include #include #endif @@ -91,7 +89,7 @@ extern Mycila::Task dashboardTask; extern Mycila::Task debugTask; extern Mycila::Task networkConfigTask; extern Mycila::Task networkManagerTask; -extern Mycila::Task otaTask; +extern Mycila::Task safeBootTask; extern Mycila::Task resetTask; extern Mycila::Task restartTask; #ifdef APP_MODEL_TRIAL diff --git a/include/YaSolRWebsite.h b/include/YaSolRWebsite.h index f4146c0..e049da3 100644 --- a/include/YaSolRWebsite.h +++ b/include/YaSolRWebsite.h @@ -184,8 +184,8 @@ namespace YaSolR { Tab _managementTab = Tab(&dashboard, "\u2764 " YASOLR_LBL_078); Card _configBackup = Card(&dashboard, LINK_CARD, YASOLR_LBL_079); Card _configRestore = Card(&dashboard, FILE_UPLOAD_CARD, YASOLR_LBL_080, ".txt"); - Card _otaLink = Card(&dashboard, LINK_CARD, YASOLR_LBL_081); Card _restart = Card(&dashboard, PUSH_BUTTON_CARD, YASOLR_LBL_082); + Card _safeBoot = Card(&dashboard, PUSH_BUTTON_CARD, YASOLR_LBL_081); Card _energyReset = Card(&dashboard, PUSH_BUTTON_CARD, YASOLR_LBL_085); Card _reset = Card(&dashboard, PUSH_BUTTON_CARD, YASOLR_LBL_086); Card _debugMode = Card(&dashboard, BUTTON_CARD, YASOLR_LBL_083); @@ -212,6 +212,7 @@ namespace YaSolR { Card _mqttPwd = Card(&dashboard, PASSWORD_CARD, YASOLR_LBL_099); Card _mqttSecured = Card(&dashboard, BUTTON_CARD, YASOLR_LBL_100); Card _mqttServerCert = Card(&dashboard, FILE_UPLOAD_CARD, YASOLR_LBL_101, ".pem,crt,der"); + Card _mqttServerCertDelete = Card(&dashboard, PUSH_BUTTON_CARD, YASOLR_LBL_049); Card _mqttPublishInterval = Card(&dashboard, SLIDER_CARD, YASOLR_LBL_102, "s", 5, 30, 1); Card _mqttTopic = Card(&dashboard, TEXT_INPUT_CARD, YASOLR_LBL_103); Card _haDiscovery = Card(&dashboard, BUTTON_CARD, YASOLR_LBL_104); diff --git a/include/i18n/en.h b/include/i18n/en.h index 2c95800..db57db4 100644 --- a/include/i18n/en.h +++ b/include/i18n/en.h @@ -49,7 +49,7 @@ #define YASOLR_LBL_046 "Output 1" #define YASOLR_LBL_047 "Status" #define YASOLR_LBL_050 "Dimmer" -#define YASOLR_LBL_049 +#define YASOLR_LBL_049 "Remove Server Certificate" #define YASOLR_LBL_051 "Bypass" #define YASOLR_LBL_052 "Power" #define YASOLR_LBL_053 "Apparent Power" @@ -80,7 +80,7 @@ #define YASOLR_LBL_078 "Management" #define YASOLR_LBL_079 "Configuration Backup" #define YASOLR_LBL_080 "Configuration Restore" -#define YASOLR_LBL_081 "OTA Firmware Update" +#define YASOLR_LBL_081 "Restart in SafeBoot mode" #define YASOLR_LBL_082 "Restart" #define YASOLR_LBL_083 "Debug" #define YASOLR_LBL_084 "Console" diff --git a/include/i18n/fr.h b/include/i18n/fr.h index 239cc3b..77a7b90 100644 --- a/include/i18n/fr.h +++ b/include/i18n/fr.h @@ -52,7 +52,7 @@ #define YASOLR_LBL_046 "Sortie 1" #define YASOLR_LBL_047 "État" #define YASOLR_LBL_048 "Température" -#define YASOLR_LBL_049 +#define YASOLR_LBL_049 "Supprimer le certificat serveur" #define YASOLR_LBL_050 "variateur" #define YASOLR_LBL_051 "Marche forcée" #define YASOLR_LBL_052 "Puissance" @@ -84,7 +84,7 @@ #define YASOLR_LBL_078 "Gestion" #define YASOLR_LBL_079 "Backup de la configuration" #define YASOLR_LBL_080 "Restauration de la configuration" -#define YASOLR_LBL_081 "Mise à jour du micrologiciel OTA" +#define YASOLR_LBL_081 "Redémarrer en mode SafeBoot" #define YASOLR_LBL_082 "Redémarrer" #define YASOLR_LBL_083 "Débogage" #define YASOLR_LBL_084 "Console" diff --git a/lib/ElegantOTAPro b/lib/ElegantOTAPro deleted file mode 160000 index a5f3a62..0000000 --- a/lib/ElegantOTAPro +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a5f3a620e500064fcf29ba9d16a4cc66c2366821 diff --git a/platformio.ini b/platformio.ini index 110d794..c73158e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -14,24 +14,33 @@ extra_configs = platformio_override.ini default_envs = pro-esp32, pro-esp32s3, pro-lilygo_eth_lite_s3, pro-wt32_eth01 [env] +build_type = release framework = arduino -; platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.04/platform-espressif32.zip -platform = espressif32@6.8.1 -platform_packages= - platformio/framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.4 - platformio/framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.0.4/esp32-arduino-libs-3.0.4.zip -board = esp32dev +platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.04/platform-espressif32.zip + monitor_filters = esp32_exception_decoder, log2file monitor_speed = 115200 upload_protocol = esptool -; upload_speed = 921600 -; upload_protocol = custom -; upload_url = http://192.168.125.119 + extra_scripts = pre:tools/data.py + pre:tools/cacerts.py pre:tools/version.py post:tools/factory.py - ; tools/upload.py + +custom_cacert_url = https://curl.se/ca/cacert.pem +; custom_cacert_url = https://raw.githubusercontent.com/adafruit/certificates/main/data/roots.pem + +board_build.partitions = tools/partitions-4MB-safeboot.csv +board_build.app_partition_name = app +board_build.filesystem = littlefs + +board_build.embed_files = + .pio/data/cacerts.bin + .pio/data/logo.png.gz + .pio/data/logo-icon.png.gz + .pio/data/config.html.gz + lib_compat_mode = strict lib_ldf_mode = chain ; https://github.com/espressif/arduino-esp32/issues/9782 @@ -39,7 +48,7 @@ lib_ldf_mode = chain lib_deps = DNSServer ESP32 Async UDP - ; ESPmDNS + ESPmDNS FS Networking LittleFS @@ -58,16 +67,17 @@ lib_deps = mathieucarbou/MycilaHADiscovery @ 2.2.2 mathieucarbou/MycilaJSY @ 9.1.6 mathieucarbou/MycilaLogger @ 3.1.2 - mathieucarbou/MycilaMQTT @ 4.2.0 + mathieucarbou/MycilaMQTT @ 4.2.1 mathieucarbou/MycilaNTP @ 4.0.0 mathieucarbou/MycilaPulseAnalyzer @ 1.0.2 mathieucarbou/MycilaPZEM004Tv3 @ 4.0.7 mathieucarbou/MycilaRelay @ 4.0.1 - mathieucarbou/MycilaSystem @ 2.0.6 + mathieucarbou/MycilaSystem @ 2.0.8 mathieucarbou/MycilaTaskManager @ 3.1.2 mathieucarbou/MycilaTaskMonitor @ 3.0.1 mathieucarbou/MycilaTrafficLight @ 1.0.0 mathieucarbou/MycilaUtilities @ 1.3.7 + build_flags = ; Stack sizes -D ARDUINO_LOOP_STACK_SIZE=4096 @@ -94,7 +104,7 @@ build_flags = -D MYCILA_JSON_SUPPORT -D MYCILA_LOGGER_SUPPORT ; ESPConnect - -D ESPCONNECT_NO_MDNS + ; -D ESPCONNECT_NO_MDNS ; MQTT -D CONFIG_MQTT_TASK_CORE_SELECTION=1 -D MQTT_REPORT_DELETED_MESSAGES=1 @@ -108,8 +118,7 @@ build_flags = -D DASH_DEFAULT_CARD_SIZE_XL=6 -D DASH_DEFAULT_CARD_SIZE_XS=12 -D DASH_DEFAULT_CARD_SIZE_XXL=3 - ; ElegantOTA - -D ELEGANTOTA_USE_ASYNC_WEBSERVER=1 + ; -D DASH_JSON_SIZE=4096 ; WebSerial -D WSL_HIGH_PERF ; YaSolR @@ -129,13 +138,6 @@ build_flags = -D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_INFO build_unflags = -std=gnu++11 -build_type = release -board_build.partitions = tools/partitions-4MB.csv -board_build.filesystem = littlefs -board_build.embed_files = - .pio/data/logo.png.gz - .pio/data/logo-icon.png.gz - .pio/data/config.html.gz ; -------------------------------------------------------------------- ; MODELS @@ -145,10 +147,8 @@ board_build.embed_files = build_flags = -D APP_MODEL_OSS lib_deps = mathieucarbou/MycilaWebSerial @ 6.3.0 - ayushsharma82/ElegantOTA @ 3.1.4 https://github.com/mathieucarbou/ayushsharma82-ESP-DASH#dev lib_ignore = - ElegantOTAPro ESPDASHPro WebSerialPro @@ -200,6 +200,8 @@ build_flags = ; esp32dev (defaul) [env:oss-esp32] +board = esp32dev +custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/latest/safeboot-esp32dev.bin lib_deps = ${env.lib_deps} ${oss.lib_deps} @@ -211,6 +213,8 @@ build_flags = ${oss.build_flags} [env:pro-esp32] +board = esp32dev +custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/latest/safeboot-esp32dev.bin lib_deps = ${env.lib_deps} ${pro.lib_deps} @@ -222,6 +226,8 @@ build_flags = ${pro.build_flags} [env:trial-esp32] +board = esp32dev +custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/latest/safeboot-esp32dev.bin lib_deps = ${env.lib_deps} ${trial.lib_deps} @@ -245,6 +251,7 @@ build_flags = [env:oss-esp32s3] extends = env:oss-esp32 board = esp32-s3-devkitc-1 +custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/latest/safeboot-esp32-s3-devkitc-1.bin build_flags = ${env.build_flags} ${oss.build_flags} @@ -253,6 +260,7 @@ build_flags = [env:pro-esp32s3] extends = env:pro-esp32 board = esp32-s3-devkitc-1 +custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/latest/safeboot-esp32-s3-devkitc-1.bin build_flags = ${env.build_flags} ${pro.build_flags} @@ -261,6 +269,7 @@ build_flags = [env:trial-esp32s3] extends = env:trial-esp32 board = esp32-s3-devkitc-1 +custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/latest/safeboot-esp32-s3-devkitc-1.bin build_flags = ${env.build_flags} ${trial.build_flags} @@ -275,6 +284,7 @@ build_flags = ; [env:oss-esp32_poe] ; extends = env:oss-esp32 ; board = esp32-poe +; custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/latest/safeboot-esp32-poe.bin ; build_flags = ; ${env.build_flags} ; ${oss.build_flags} @@ -283,6 +293,7 @@ build_flags = ; [env:pro-esp32_poe] ; extends = env:pro-esp32 ; board = esp32-poe +; custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/latest/safeboot-esp32-poe.bin ; build_flags = ; ${env.build_flags} ; ${pro.build_flags} @@ -291,6 +302,7 @@ build_flags = ; [env:trial-esp32_poe] ; extends = env:trial-esp32 ; board = esp32-poe +; custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/latest/safeboot-esp32-poe.bin ; build_flags = ; ${env.build_flags} ; ${trial.build_flags} @@ -323,6 +335,7 @@ build_flags = [env:oss-wt32_eth01] extends = env:oss-esp32 board = wt32-eth01 +custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/latest/safeboot-wt32-eth01.bin upload_speed = 460800 build_flags = ${env.build_flags} @@ -332,6 +345,7 @@ build_flags = [env:pro-wt32_eth01] extends = env:pro-esp32 board = wt32-eth01 +custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/latest/safeboot-wt32-eth01.bin upload_speed = 460800 build_flags = ${env.build_flags} @@ -341,6 +355,7 @@ build_flags = [env:trial-wt32_eth01] extends = env:trial-esp32 board = wt32-eth01 +custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/latest/safeboot-wt32-eth01.bin upload_speed = 460800 build_flags = ${env.build_flags} @@ -382,6 +397,7 @@ build_flags = [env:oss-lilygo_eth_lite_s3] extends = env:oss-esp32 board = esp32s3box +custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/latest/safeboot-esp32s3box.bin upload_speed = 115200 build_flags = ${env.build_flags} @@ -391,6 +407,7 @@ build_flags = [env:pro-lilygo_eth_lite_s3] extends = env:pro-esp32 board = esp32s3box +custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/latest/safeboot-esp32s3box.bin upload_speed = 115200 build_flags = ${env.build_flags} @@ -400,6 +417,7 @@ build_flags = [env:trial-lilygo_eth_lite_s3] extends = env:trial-esp32 board = esp32s3box +custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/latest/safeboot-esp32s3box.bin upload_speed = 115200 build_flags = ${env.build_flags} diff --git a/src/Website.cpp b/src/Website.cpp index 40556f9..f01902c 100644 --- a/src/Website.cpp +++ b/src/Website.cpp @@ -187,7 +187,7 @@ void YaSolR::WebsiteClass::initLayout() { _consoleLink.setTab(&_managementTab); _debugInfo.setTab(&_managementTab); _debugMode.setTab(&_managementTab); - _otaLink.setTab(&_managementTab); + _safeBoot.setTab(&_managementTab); _reset.setTab(&_managementTab); _restart.setTab(&_managementTab); _energyReset.setTab(&_managementTab); @@ -201,6 +201,7 @@ void YaSolR::WebsiteClass::initLayout() { }); _reset.attachCallback([]() { resetTask.resume(); }); _restart.attachCallback([]() { restartTask.resume(); }); + _safeBoot.attachCallback([]() { safeBootTask.resume(); }); // network (config) @@ -246,6 +247,7 @@ void YaSolR::WebsiteClass::initLayout() { _mqttSecured.setTab(&_mqttConfigTab); _mqttServer.setTab(&_mqttConfigTab); _mqttServerCert.setTab(&_mqttConfigTab); + _mqttServerCertDelete.setTab(&_mqttConfigTab); _mqttTopic.setTab(&_mqttConfigTab); _mqttUser.setTab(&_mqttConfigTab); _mqttTempO1.setTab(&_mqttConfigTab); @@ -265,6 +267,14 @@ void YaSolR::WebsiteClass::initLayout() { _textConfig(_mqttTopic, KEY_MQTT_TOPIC); _textConfig(_mqttUser, KEY_MQTT_USERNAME); + _mqttServerCertDelete.attachCallback([this]() { + if (LittleFS.exists("/mqtt-server.crt") && LittleFS.remove("/mqtt-server.crt")) { + logger.warn(TAG, "MQTT server certificate deleted successfully!"); + initCards(); + dashboardTask.requestEarlyRun(); + } + }); + // GPIO (configuration) _pinDimmerO1.setTab(&_pinConfigTab); @@ -570,7 +580,6 @@ void YaSolR::WebsiteClass::initCards() { _consoleLink.update("/console"); _debugInfo.update("/api/debug"); _debugMode.update(config.getBool(KEY_ENABLE_DEBUG)); - _otaLink.update("/update"); _energyReset.setDisplay(jsyEnabled || pzem1Enabled || pzem2Enabled); _debugInfo.setDisplay(config.getBool(KEY_ENABLE_DEBUG)); @@ -603,7 +612,11 @@ void YaSolR::WebsiteClass::initCards() { _mqttServerCert.update("/api/config/mqttServerCertificate"); _mqttTopic.update(config.get(KEY_MQTT_TOPIC)); _mqttUser.update(config.get(KEY_MQTT_USERNAME)); + + const bool serverCertExists = LittleFS.exists("/mqtt-server.crt"); _mqttConfigTab.setDisplay(config.getBool(KEY_ENABLE_MQTT)); + _mqttServerCert.setDisplay(!serverCertExists); + _mqttServerCertDelete.setDisplay(serverCertExists); // GPIO diff --git a/src/init/Boot.cpp b/src/init/Boot.cpp index c117581..287707b 100644 --- a/src/init/Boot.cpp +++ b/src/init/Boot.cpp @@ -19,7 +19,7 @@ Mycila::Task bootTask("Boot", [](void* params) { logger.warn(TAG, "Booting %s", Mycila::AppInfo.nameModelVersion.c_str()); // system - Mycila::System.begin(); + Mycila::System.begin(true, "fs"); // trial #ifdef APP_MODEL_TRIAL @@ -67,7 +67,7 @@ Mycila::Task bootTask("Boot", [](void* params) { config.configure(KEY_MQTT_SECURED, YASOLR_FALSE); config.configure(KEY_MQTT_SERVER); config.configure(KEY_MQTT_TOPIC, Mycila::AppInfo.defaultMqttClientId); - config.configure(KEY_MQTT_USERNAME, "homeassistant"); + config.configure(KEY_MQTT_USERNAME); config.configure(KEY_NET_DNS); config.configure(KEY_NET_GATEWAY); config.configure(KEY_NET_IP); diff --git a/src/init/Config.cpp b/src/init/Config.cpp index 11799e7..cd6bf6a 100644 --- a/src/init/Config.cpp +++ b/src/init/Config.cpp @@ -15,7 +15,7 @@ Mycila::Task initConfigTask("Init Config", [](void* params) { dashboardTask.setEnabledWhen([]() { return espConnect.isConnected() && !dashboard.isAsyncAccessInProgress(); }); dashboardTask.setInterval(1000 * Mycila::TaskDuration::MILLISECONDS); debugTask.setEnabledWhen([]() { return config.getBool(KEY_ENABLE_DEBUG); }); - debugTask.setInterval(30 * Mycila::TaskDuration::SECONDS); + debugTask.setInterval(20 * Mycila::TaskDuration::SECONDS); displayTask.setEnabledWhen([]() { return display.isEnabled(); }); displayTask.setInterval(500 * Mycila::TaskDuration::MILLISECONDS); lightsTask.setInterval(200 * Mycila::TaskDuration::MILLISECONDS); @@ -171,17 +171,6 @@ Mycila::Task initConfigTask("Init Config", [](void* params) { WebSerial.begin(&webServer, "/console"); logger.forwardTo(&WebSerial); - // ElegantOTA -#ifdef APP_MODEL_PRO - ElegantOTA.setID(Mycila::AppInfo.firmware.c_str()); - ElegantOTA.setTitle((Mycila::AppInfo.name + " OTA Updater").c_str()); - ElegantOTA.setFWVersion(Mycila::AppInfo.version.c_str()); - ElegantOTA.setFirmwareMode(true); - ElegantOTA.setFilesystemMode(false); -#endif - ElegantOTA.setAutoReboot(false); - ElegantOTA.begin(&webServer, YASOLR_ADMIN_USERNAME, config.get(KEY_ADMIN_PASSWORD).c_str()); - // Dashboard dashboard.setAuthentication(YASOLR_ADMIN_USERNAME, config.get(KEY_ADMIN_PASSWORD).c_str()); #ifdef APP_MODEL_PRO diff --git a/src/init/Core.cpp b/src/init/Core.cpp index 31b3055..16df0d7 100644 --- a/src/init/Core.cpp +++ b/src/init/Core.cpp @@ -19,7 +19,7 @@ Mycila::Task initCoreTask("Init Core", [](void* params) { dimmer2Task.setManager(coreTaskManager); displayTask.setManager(coreTaskManager); lightsTask.setManager(coreTaskManager); - otaTask.setManager(coreTaskManager); + safeBootTask.setManager(coreTaskManager); relayTask.setManager(coreTaskManager); resetTask.setManager(coreTaskManager); restartTask.setManager(coreTaskManager); diff --git a/src/init/Debug.cpp b/src/init/Debug.cpp index d457ca3..a7d6bed 100644 --- a/src/init/Debug.cpp +++ b/src/init/Debug.cpp @@ -50,7 +50,7 @@ Mycila::Task initLoggingTask("Init Logging", [](void* params) { mqttPublishConfigTask.setCallback(debug ? LOG_EXEC_TIME : nullptr); mqttPublishStaticTask.setCallback(debug ? LOG_EXEC_TIME : nullptr); mqttPublishTask.setCallback(debug ? LOG_EXEC_TIME : nullptr); - otaTask.setCallback(debug ? LOG_EXEC_TIME : nullptr); + safeBootTask.setCallback(debug ? LOG_EXEC_TIME : nullptr); debugTask.setCallback(debug ? LOG_EXEC_TIME : nullptr); pzemO1PairingTask.setCallback(debug ? LOG_EXEC_TIME : nullptr); pzemO2PairingTask.setCallback(debug ? LOG_EXEC_TIME : nullptr); diff --git a/src/init/Events.cpp b/src/init/Events.cpp index 631ceda..ad7bb2a 100644 --- a/src/init/Events.cpp +++ b/src/init/Events.cpp @@ -283,16 +283,6 @@ Mycila::Task initEventsTask("Init Events", [](void* params) { } }); - ElegantOTA.onStart([]() { otaTask.resume(); }); - ElegantOTA.onEnd([](bool success) { - if (success) { - logger.info(TAG, "OTA Update Success! Restarting"); - } else { - logger.error(TAG, "OTA Failed! Restarting"); - } - restartTask.resume(); - }); - bypassRelayO1.listen([](bool state) { logger.info(TAG, "Output 1 Relay changed to %s", state ? "ON" : "OFF"); mqttPublishTask.requestEarlyRun(); diff --git a/src/init/REST.cpp b/src/init/REST.cpp index 3d4c61f..6196aac 100644 --- a/src/init/REST.cpp +++ b/src/init/REST.cpp @@ -5,6 +5,8 @@ #include #include +#include + #include static void systemInfoToJson(JsonObject& root) { @@ -193,6 +195,8 @@ Mycila::Task initRestApiTask("Init REST API", [](void* params) { File serverCertFile = LittleFS.open("/mqtt-server.crt", "r"); logger.info(TAG, "Uploaded MQTT server certificate:\n%s", serverCertFile.readString().c_str()); serverCertFile.close(); + YaSolR::Website.initCards(); + dashboardTask.requestEarlyRun(); request->send(response); }, [](AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final) { @@ -238,6 +242,13 @@ Mycila::Task initRestApiTask("Init REST API", [](void* params) { }) .setAuthentication(YASOLR_ADMIN_USERNAME, config.get(KEY_ADMIN_PASSWORD)); + webServer + .on("/api/system/safeboot", HTTP_ANY, [](AsyncWebServerRequest* request) { + safeBootTask.resume(); + request->send(200); + }) + .setAuthentication(YASOLR_ADMIN_USERNAME, config.get(KEY_ADMIN_PASSWORD)); + webServer .on("/api/system/reset", HTTP_ANY, [](AsyncWebServerRequest* request) { resetTask.resume(); diff --git a/src/tasks/Debug.cpp b/src/tasks/Debug.cpp index 12712bc..b84c18e 100644 --- a/src/tasks/Debug.cpp +++ b/src/tasks/Debug.cpp @@ -5,6 +5,7 @@ #include Mycila::Task debugTask("Debug", [](void* params) { + logger.info(TAG, "Free Heap: %" PRIu32, ESP.getFreeHeap()); Mycila::TaskMonitor.log(); coreTaskManager.log(); pioTaskManager.log(); diff --git a/src/tasks/Lights.cpp b/src/tasks/Lights.cpp index 57c887f..fd0c6bd 100644 --- a/src/tasks/Lights.cpp +++ b/src/tasks/Lights.cpp @@ -5,7 +5,7 @@ #include Mycila::Task lightsTask("Lights", [](void* params) { - if (!otaTask.isPaused()) { + if (!safeBootTask.isPaused()) { lights.setAllOn(); return; } diff --git a/src/tasks/MQTT.cpp b/src/tasks/MQTT.cpp index c000101..70001c5 100644 --- a/src/tasks/MQTT.cpp +++ b/src/tasks/MQTT.cpp @@ -4,33 +4,43 @@ */ #include +extern const uint8_t ca_certs_bundle_start[] asm("_binary__pio_data_cacerts_bin_start"); +extern const uint8_t ca_certs_bundle_end[] asm("_binary__pio_data_cacerts_bin_end"); + Mycila::Task mqttConfigTask("MQTT Config", Mycila::TaskType::ONCE, [](void* params) { mqtt.end(); if (!config.getBool(KEY_ENABLE_AP_MODE) && config.getBool(KEY_ENABLE_MQTT)) { - logger.info(TAG, "Enable MQTT"); - bool secured = config.getBool(KEY_MQTT_SECURED); - String serverCert = ""; - - if (secured && LittleFS.exists("/mqtt-server.crt")) { - logger.debug(TAG, "Loading MQTT server certificate"); - File serverCertFile = LittleFS.open("/mqtt-server.crt", "r"); - serverCert = serverCertFile.readString(); - serverCertFile.close(); - logger.debug(TAG, "Loaded MQTT server certificate:\n%s", serverCert.c_str()); + + Mycila::MQTT::Config mqttConfig; + mqttConfig.server = config.get(KEY_MQTT_SERVER); + mqttConfig.port = static_cast(config.get(KEY_MQTT_PORT).toInt()); + mqttConfig.secured = secured; + mqttConfig.username = config.get(KEY_MQTT_USERNAME); + mqttConfig.password = config.get(KEY_MQTT_PASSWORD); + mqttConfig.clientId = Mycila::AppInfo.defaultMqttClientId; + mqttConfig.willTopic = config.get(KEY_MQTT_TOPIC) + YASOLR_MQTT_WILL_TOPIC; + mqttConfig.keepAlive = YASOLR_MQTT_KEEPALIVE; + + if (secured) { + // if a server certificate has been used, set it + if (LittleFS.exists("/mqtt-server.crt")) { + logger.debug(TAG, "Loading MQTT server certificate"); + File serverCertFile = LittleFS.open("/mqtt-server.crt", "r"); + mqttConfig.serverCert = serverCertFile.readString(); + serverCertFile.close(); + logger.debug(TAG, "Loaded MQTT server certificate:\n%s", mqttConfig.serverCert.c_str()); + } else { + logger.debug(TAG, "Using cacert bundle for MQTT"); + // if no server certificate has been used, set default CA certs bundle + mqttConfig.certBundle = ca_certs_bundle_start; + mqttConfig.certBundleSize = static_cast(ca_certs_bundle_end - ca_certs_bundle_start); + } } - mqtt.begin({ - .server = config.get(KEY_MQTT_SERVER), - .port = static_cast(config.get(KEY_MQTT_PORT).toInt()), - .secured = secured, - .serverCert = serverCert, - .username = config.get(KEY_MQTT_USERNAME), - .password = config.get(KEY_MQTT_PASSWORD), - .clientId = Mycila::AppInfo.defaultMqttClientId, - .willTopic = config.get(KEY_MQTT_TOPIC) + YASOLR_MQTT_WILL_TOPIC, - .keepAlive = YASOLR_MQTT_KEEPALIVE, - }); + logger.info(TAG, "Enable MQTT (server: ://%s:%" PRIu16 ")", (secured ? "mqtts" : "mqtt"), mqttConfig.server.c_str(), mqttConfig.port); + mqtt.setAsync(false); + mqtt.begin(mqttConfig); } }); diff --git a/src/tasks/OTA.cpp b/src/tasks/SafeBoot.cpp similarity index 63% rename from src/tasks/OTA.cpp rename to src/tasks/SafeBoot.cpp index c696974..dfe4f32 100644 --- a/src/tasks/OTA.cpp +++ b/src/tasks/SafeBoot.cpp @@ -4,8 +4,8 @@ */ #include -Mycila::Task otaTask("OTA", Mycila::TaskType::ONCE, [](void* params) { - logger.info(TAG, "Preparing OTA update"); +Mycila::Task safeBootTask("SafeBoot", Mycila::TaskType::ONCE, [](void* params) { + logger.info(TAG, "Restarting %s in SafeBoot mode...", Mycila::AppInfo.nameModelVersion.c_str()); // stop electricity dimmerO1.endDimmer(); dimmerO2.endDimmer(); @@ -20,4 +20,5 @@ Mycila::Task otaTask("OTA", Mycila::TaskType::ONCE, [](void* params) { #ifdef APP_MODEL_TRIAL Mycila::Trial.end(); #endif + Mycila::System.restartFactory("safeboot"); }); diff --git a/tools/cacerts.py b/tools/cacerts.py new file mode 100644 index 0000000..1fd1a4e --- /dev/null +++ b/tools/cacerts.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python +# +# modified ESP32 x509 certificate bundle generation utility to run with platformio +# +# Converts PEM and DER certificates to a custom bundle format which stores just the +# subject name and public key to reduce space +# +# The bundle will have the format: number of certificates; crt 1 subject name length; crt 1 public key length; +# crt 1 subject name; crt 1 public key; crt 2... +# +# SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +######################################################################################################## +# Certificate Bundle Generation from PsychicMqttClient (https://github.com/theelims/PsychicMqttClient) # +######################################################################################################## + +from __future__ import with_statement + +from pathlib import Path +import os +import struct +import sys +import requests +from io import open + +Import("env") + +try: + from cryptography import x509 + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import serialization +except ImportError: + env.Execute("$PYTHONEXE -m pip install cryptography") + +output_dir = ".pio/data" +quiet = False + +def download_cacert_file(): + cacert_url = env.GetProjectOption("custom_cacert_url", "") + if cacert_url == "": + raise Exception("Please specify the URL of the CA certificate bundle with the 'custom_cacert_url' option in platformio.ini") + + response = requests.get(cacert_url) + if response.status_code == 200: + with open(os.path.join(output_dir, "cacerts.pem"), "w", encoding="utf-8") as f: + f.write(response.text) + status('Certificate bundle downloaded to: %s' % os.path.join(output_dir, "cacerts.pem")) + else: + status('Failed to fetch the certificate bundle.') + +def status(msg): + """ Print status message to stderr """ + if not quiet: + critical(msg) + +def critical(msg): + """ Print critical message to stderr """ + sys.stderr.write('cacerts.py: ') + sys.stderr.write(msg) + sys.stderr.write('\n') + +class CertificateBundle: + def __init__(self): + self.certificates = [] + self.compressed_crts = [] + + def add_from_path(self, crts_path): + + found = False + for file_path in os.listdir(crts_path): + found |= self.add_from_file(os.path.join(crts_path, file_path)) + + if found is False: + raise InputError('No valid x509 certificates found in %s' % crts_path) + + def add_from_file(self, file_path): + try: + if file_path.endswith('.pem'): + status('Parsing certificates from %s' % file_path) + with open(file_path, 'r', encoding='utf-8') as f: + crt_str = f.read() + self.add_from_pem(crt_str) + return True + + elif file_path.endswith('.der'): + status('Parsing certificates from %s' % file_path) + with open(file_path, 'rb') as f: + crt_str = f.read() + self.add_from_der(crt_str) + return True + + except ValueError: + critical('Invalid certificate in %s' % file_path) + raise InputError('Invalid certificate') + + return False + + def add_from_pem(self, crt_str): + """ A single PEM file may have multiple certificates """ + + crt = '' + count = 0 + start = False + + for strg in crt_str.splitlines(True): + if strg == '-----BEGIN CERTIFICATE-----\n' and start is False: + crt = '' + start = True + elif strg == '-----END CERTIFICATE-----\n' and start is True: + crt += strg + '\n' + start = False + self.certificates.append(x509.load_pem_x509_certificate(crt.encode(), default_backend())) + count += 1 + if start is True: + crt += strg + + if count == 0: + raise InputError('No certificate found') + + status('Successfully added %d certificates' % count) + + def add_from_der(self, crt_str): + self.certificates.append(x509.load_der_x509_certificate(crt_str, default_backend())) + status('Successfully added 1 certificate') + + def create_bundle(self): + # Sort certificates in order to do binary search when looking up certificates + self.certificates = sorted(self.certificates, key=lambda cert: cert.subject.public_bytes(default_backend())) + + bundle = struct.pack('>H', len(self.certificates)) + + for crt in self.certificates: + """ Read the public key as DER format """ + pub_key = crt.public_key() + pub_key_der = pub_key.public_bytes(serialization.Encoding.DER, serialization.PublicFormat.SubjectPublicKeyInfo) + + """ Read the subject name as DER format """ + sub_name_der = crt.subject.public_bytes(default_backend()) + + name_len = len(sub_name_der) + key_len = len(pub_key_der) + len_data = struct.pack('>HH', name_len, key_len) + + bundle += len_data + bundle += sub_name_der + bundle += pub_key_der + + return bundle + +class InputError(RuntimeError): + def __init__(self, e): + super(InputError, self).__init__(e) + + +def main(): + os.makedirs(output_dir, exist_ok=True) + + output = os.path.join(output_dir, "cacerts.bin") + if os.path.exists(output): + status('Certificate bundle already exists') + return + + source = os.path.join(output_dir, "cacerts.pem") + if not os.path.exists(source): + download_cacert_file() + + bundle = CertificateBundle() + bundle.add_from_file(source) + + with open(output, 'wb') as f: + f.write(bundle.create_bundle()) + + status('cacert bundle created: %s' % output) +try: + main() +except InputError as e: + print(e) + sys.exit(2) diff --git a/tools/data.py b/tools/data.py index 4beb4bb..78617a9 100644 --- a/tools/data.py +++ b/tools/data.py @@ -1,23 +1,50 @@ import gzip import os +import sys -os.makedirs('.pio/data', exist_ok=True) +quiet = False -for filename in ['config.html', 'logo-icon.png', 'logo.png']: + +def status(msg): + """Print status message to stderr""" + if not quiet: + critical(msg) + + +def critical(msg): + """Print critical message to stderr""" + sys.stderr.write("factory.py: ") + sys.stderr.write(msg) + sys.stderr.write("\n") + + +os.makedirs(".pio/data", exist_ok=True) + +for filename in ["config.html", "logo-icon.png", "logo.png"]: skip = False - if os.path.isfile('.pio/data/' + filename + '.timestamp'): - with open('.pio/data/' + filename + '.timestamp', 'r', -1, 'utf-8') as timestampFile: - if os.path.getmtime('data/' + filename) == float(timestampFile.readline()): + if os.path.isfile(".pio/data/" + filename + ".timestamp"): + with open( + ".pio/data/" + filename + ".timestamp", "r", -1, "utf-8" + ) as timestampFile: + if os.path.getmtime("data/" + filename) == float(timestampFile.readline()): skip = True if skip: - print('[data.py] ' + filename + ' up to date') + status("[data.py] " + filename + " up to date") continue - with open('data/' + filename, 'rb') as inputFile: - with gzip.open('.pio/data/' + filename + '.gz', 'wb') as outputFile: - print('[data.py] gzip \'data/' + filename + '\' to \'.pio/data/' + filename + '.gz\'') + with open("data/" + filename, "rb") as inputFile: + with gzip.open(".pio/data/" + filename + ".gz", "wb") as outputFile: + status( + "[data.py] gzip 'data/" + + filename + + "' to '.pio/data/" + + filename + + ".gz'" + ) outputFile.writelines(inputFile) - with open('.pio/data/' + filename + '.timestamp', 'w', -1, 'utf-8') as timestampFile: - timestampFile.write(str(os.path.getmtime('data/' + filename))) + with open( + ".pio/data/" + filename + ".timestamp", "w", -1, "utf-8" + ) as timestampFile: + timestampFile.write(str(os.path.getmtime("data/" + filename))) diff --git a/tools/factory.py b/tools/factory.py index 20a5223..d5ad390 100644 --- a/tools/factory.py +++ b/tools/factory.py @@ -2,44 +2,80 @@ # # Copyright (C) 2023-2024 Mathieu Carbou # -Import("env", "projenv") +Import("env") import sys import os +import requests from os.path import join, getsize sys.path.append(join(env.PioPlatform().get_package_dir("tool-esptoolpy"))) import esptool # print(env.Dump()) -# print(projenv.Dump()) +quiet = False -def esp32_create_combined_bin(source, target, env): - print("Generating factory image for serial flashing") - # print("Building Safeboot image for board: %s" % env.get("BOARD")) - # env.Execute("SAFEBOOT_BOARD=%s pio run -d ../../tools/Safeboot" % env.get("BOARD")) +def status(msg): + """Print status message to stderr""" + if not quiet: + critical(msg) - # print("Building File System image") - # env.Execute('pio run -t buildfs -e %s' % env['PIOENV']) - safeboot_offset = 0x10000 - safeboot_image = "../../tools/Safeboot/.pio/build/safeboot/safeboot.bin" +def critical(msg): + """Print critical message to stderr""" + sys.stderr.write("factory.py: ") + sys.stderr.write(msg) + sys.stderr.write("\n") + - app_offset = 0x110000 +def generateFactooryImage(source, target, env): + status("Generating factory image for serial flashing") + + app_offset = 0xB0000 app_image = env.subst("$BUILD_DIR/${PROGNAME}.bin") + # Set fs_offset = 0 to disable LittleFS image generation + # Set fs_offset to the correct offset from the partition to generate a LittleFS image fs_offset = 0 fs_image = env.subst("$BUILD_DIR/littlefs.bin") - if env.get("PARTITIONS_TABLE_CSV").endswith("partitions-4MB.csv"): - fs_offset = 0x3F0000 - if env.get("PARTITIONS_TABLE_CSV").endswith("partitions-8MB.csv"): - fs_offset = 0x7E0000 - if fs_offset == 0: - print(env.get("PARTITIONS_TABLE_CSV")) - raise Exception("Unknown partition file, cannot determine FS offset") + safeboot_offset = 0x10000 + safeboot_image = "" + + safeboot_project = env.GetProjectOption("custom_safeboot_dir", "") + if safeboot_project != "": + status( + "Building SafeBoot image for board %s from %s" + % (env.get("BOARD"), safeboot_project) + ) + if not os.path.isdir(safeboot_project): + raise Exception("SafeBoot project not found: %s" % safeboot_project) + env.Execute( + "SAFEBOOT_BOARD=%s pio run -d %s" % (env.get("BOARD"), safeboot_project) + ) + safeboot_image = join(safeboot_project, ".pio/build/safeboot/safeboot.bin") + if not os.path.isfile(safeboot_image): + raise Exception("SafeBoot image not found: %s" % safeboot_image) + + safeboot_url = env.GetProjectOption("custom_safeboot_url", "") + if safeboot_url != "": + safeboot_image = env.subst("$BUILD_DIR/safeboot.bin") + if not os.path.isfile(safeboot_image): + status( + "Downloading SafeBoot image from %s to %s" + % (safeboot_url, safeboot_image) + ) + response = requests.get(safeboot_url) + if response.status_code != 200: + raise Exception("Download error: %d" % response.status_code) + with open(safeboot_image, "wb") as file: + file.write(response.content) + + if fs_offset != 0: + status("Building File System image") + env.Execute("pio run -t buildfs -e %s" % env["PIOENV"]) factory_image = env.subst("$BUILD_DIR/${PROGNAME}.factory.bin") @@ -79,28 +115,28 @@ def esp32_create_combined_bin(source, target, env): if fw_size > max_size: raise Exception("Firmware binary too large: %d > %d" % (fw_size, max_size)) - print(" Offset | File") + status(" Offset | File") for section in sections: sect_adr, sect_file = section.split(" ", 1) - print(f" - {sect_adr} | {sect_file}") + status(f" - {sect_adr} | {sect_file}") cmd += [sect_adr, sect_file] - if os.path.isfile(safeboot_image): - print(f" - {hex(safeboot_offset)} | {safeboot_image}") + if safeboot_image != "" and os.path.isfile(safeboot_image): + status(f" - {hex(safeboot_offset)} | {safeboot_image}") cmd += [hex(safeboot_offset), safeboot_image] - print(f" - {hex(app_offset)} | {app_image}") + status(f" - {hex(app_offset)} | {app_image}") cmd += [hex(app_offset), app_image] - if os.path.isfile(fs_image): - print(f" - {hex(fs_offset)} | {fs_image}") + if fs_image != 0 and os.path.isfile(fs_image): + status(f" - {hex(fs_offset)} | {fs_image}") cmd += [hex(fs_offset), fs_image] - print("Using esptool.py arguments: %s" % " ".join(cmd)) + status("Using esptool.py arguments: %s" % " ".join(cmd)) esptool.main(cmd) - print("Factory image generated: %s" % factory_image) + status("Factory image generated! You can flash it with:\n> esptool.py write_flash 0x0 %s" % factory_image) -env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin) +env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", generateFactooryImage) diff --git a/tools/partitions-4MB-safeboot.csv b/tools/partitions-4MB-safeboot.csv index 3be03a5..a28f1b2 100644 --- a/tools/partitions-4MB-safeboot.csv +++ b/tools/partitions-4MB-safeboot.csv @@ -1,7 +1,7 @@ # Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 0x9000, 0x5000, otadata, data, ota, 0xE000, 0x2000, -safeboot, app, factory, 0x10000, 0x100000, -app, app, ota_0, 0x110000, 0x2D0000, +safeboot, app, factory, 0x10000, 0xA0000, +app, app, ota_0, 0xB0000, 0x330000, fs, data, spiffs, 0x3E0000, 0x10000, coredump, data, coredump, 0x3F0000, 0x10000, diff --git a/tools/partitions-8MB-safeboot.csv b/tools/partitions-8MB-safeboot.csv index 0b3b919..77ccc7f 100644 --- a/tools/partitions-8MB-safeboot.csv +++ b/tools/partitions-8MB-safeboot.csv @@ -1,7 +1,7 @@ # Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 0x9000, 0x5000, otadata, data, ota, 0xE000, 0x2000, -safeboot, app, factory, 0x10000, 0x100000, -app, app, ota_0, 0x110000, 0x6D0000, +safeboot, app, factory, 0x10000, 0xA0000, +app, app, ota_0, 0xB0000, 0x730000, fs, data, spiffs, 0x7E0000, 0x10000, coredump, data, coredump, 0x7F0000, 0x10000, diff --git a/tools/upload.py b/tools/upload.py deleted file mode 100644 index d8c1bfc..0000000 --- a/tools/upload.py +++ /dev/null @@ -1,130 +0,0 @@ -# Allows PlatformIO to upload directly to ElegantOTA -# -# To use: -# - copy this script into the same folder as your platformio.ini -# - set the following for your project in platformio.ini: -# -# extra_scripts = platformio_upload.py -# upload_protocol = custom -# upload_url = -# -# An example of an upload URL: -# upload_url = http://192.168.1.123/update -# also possible: upload_url = http://domainname/update - -import requests -import hashlib -from urllib.parse import urlparse -import time -from requests.auth import HTTPDigestAuth -Import("env") - -try: - from requests_toolbelt import MultipartEncoder, MultipartEncoderMonitor - from tqdm import tqdm -except ImportError: - env.Execute("$PYTHONEXE -m pip install requests_toolbelt") - env.Execute("$PYTHONEXE -m pip install tqdm") - from requests_toolbelt import MultipartEncoder, MultipartEncoderMonitor - from tqdm import tqdm - -def on_upload(source, target, env): - firmware_path = str(source[0]) - - auth = None - upload_url = env.GetProjectOption('upload_url') - - with open(firmware_path, 'rb') as firmware: - md5 = hashlib.md5(firmware.read()).hexdigest() - - parsed_url = urlparse(upload_url) - host_ip = parsed_url.netloc - - # Führe die GET-Anfrage aus - start_url = f"{upload_url}/ota/start?mode=fr&hash={md5}" - - start_headers = { - 'Host': host_ip, - 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/118.0', - 'Accept': '*/*', - 'Accept-Language': 'de,en-US;q=0.7,en;q=0.3', - 'Accept-Encoding': 'gzip, deflate', - 'Referer': f'{upload_url}/update', - 'Connection': 'keep-alive' - } - - checkAuthResponse = requests.get(f"{upload_url}/update") - - if checkAuthResponse.status_code == 401: - try: - username = env.GetProjectOption('custom_username') - password = env.GetProjectOption('custom_password') - except: - username = None - password = None - print("No authentication values specified.") - print('Please, add some Options in your .ini file like: \n\ncustom_username=username\ncustom_password=password\n') - if username is None or password is None: - print("Authentication required, but no credentials provided.") - return - print("Serverconfiguration: authentication needed.") - auth = HTTPDigestAuth(username, password) - doUpdateAuth = requests.get(start_url, headers=start_headers, auth=auth) - - if doUpdateAuth.status_code != 200: - print("authentication faild " + str(doUpdateAuth.status_code)) - return - print("Authentication successfull") - else: - auth = None - print("Serverconfiguration: autentication not needed.") - doUpdate = requests.get(start_url, headers=start_headers) - - if doUpdate.status_code != 200: - print("start-request faild " + str(doUpdate.status_code)) - return - - firmware.seek(0) - encoder = MultipartEncoder(fields={ - 'MD5': md5, - 'firmware': ('firmware', firmware, 'application/octet-stream')} - ) - - bar = tqdm(desc='Upload Progress', - total=encoder.len, - dynamic_ncols=True, - unit='B', - unit_scale=True, - unit_divisor=1024 - ) - - monitor = MultipartEncoderMonitor(encoder, lambda monitor: bar.update(monitor.bytes_read - bar.n)) - - post_headers = { - 'Host': host_ip, - 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/118.0', - 'Accept': '*/*', - 'Accept-Language': 'de,en-US;q=0.7,en;q=0.3', - 'Accept-Encoding': 'gzip, deflate', - 'Referer': f'{upload_url}/update', - 'Connection': 'keep-alive', - 'Content-Type': monitor.content_type, - 'Content-Length': str(monitor.len), - 'Origin': f'{upload_url}' - } - - - response = requests.post(f"{upload_url}/ota/upload", data=monitor, headers=post_headers, auth=auth) - - bar.close() - time.sleep(0.1) - - if response.status_code != 200: - message = "\nUpload faild.\nServer response: " + response.text - tqdm.write(message) - else: - message = "\nUpload successful.\nServer response: " + response.text - tqdm.write(message) - - -env.Replace(UPLOADCMD=on_upload) \ No newline at end of file From 8d4dffb06bc4867148e15e93524d63874175bdd9 Mon Sep 17 00:00:00 2001 From: Mathieu Carbou Date: Tue, 13 Aug 2024 15:23:24 +0200 Subject: [PATCH 2/2] SafeBoot v1 --- .github/workflows/build.yml | 1 + platformio.ini | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 681f33b..72b6928 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -83,6 +83,7 @@ jobs: run: | python -m pip install --upgrade pip pip install --upgrade platformio + pip install --upgrade cryptography ref="${{ github.ref_name }}" ref="${ref//\//}" ref="${ref//-/}" diff --git a/platformio.ini b/platformio.ini index c73158e..8b6b779 100644 --- a/platformio.ini +++ b/platformio.ini @@ -201,7 +201,7 @@ build_flags = [env:oss-esp32] board = esp32dev -custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/latest/safeboot-esp32dev.bin +custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/v1.0.0/safeboot-esp32dev.bin lib_deps = ${env.lib_deps} ${oss.lib_deps} @@ -214,7 +214,7 @@ build_flags = [env:pro-esp32] board = esp32dev -custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/latest/safeboot-esp32dev.bin +custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/v1.0.0/safeboot-esp32dev.bin lib_deps = ${env.lib_deps} ${pro.lib_deps} @@ -227,7 +227,7 @@ build_flags = [env:trial-esp32] board = esp32dev -custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/latest/safeboot-esp32dev.bin +custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/v1.0.0/safeboot-esp32dev.bin lib_deps = ${env.lib_deps} ${trial.lib_deps} @@ -251,7 +251,7 @@ build_flags = [env:oss-esp32s3] extends = env:oss-esp32 board = esp32-s3-devkitc-1 -custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/latest/safeboot-esp32-s3-devkitc-1.bin +custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/v1.0.0/safeboot-esp32-s3-devkitc-1.bin build_flags = ${env.build_flags} ${oss.build_flags} @@ -260,7 +260,7 @@ build_flags = [env:pro-esp32s3] extends = env:pro-esp32 board = esp32-s3-devkitc-1 -custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/latest/safeboot-esp32-s3-devkitc-1.bin +custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/v1.0.0/safeboot-esp32-s3-devkitc-1.bin build_flags = ${env.build_flags} ${pro.build_flags} @@ -269,7 +269,7 @@ build_flags = [env:trial-esp32s3] extends = env:trial-esp32 board = esp32-s3-devkitc-1 -custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/latest/safeboot-esp32-s3-devkitc-1.bin +custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/v1.0.0/safeboot-esp32-s3-devkitc-1.bin build_flags = ${env.build_flags} ${trial.build_flags} @@ -284,7 +284,7 @@ build_flags = ; [env:oss-esp32_poe] ; extends = env:oss-esp32 ; board = esp32-poe -; custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/latest/safeboot-esp32-poe.bin +; custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/v1.0.0/safeboot-esp32-poe.bin ; build_flags = ; ${env.build_flags} ; ${oss.build_flags} @@ -293,7 +293,7 @@ build_flags = ; [env:pro-esp32_poe] ; extends = env:pro-esp32 ; board = esp32-poe -; custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/latest/safeboot-esp32-poe.bin +; custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/v1.0.0/safeboot-esp32-poe.bin ; build_flags = ; ${env.build_flags} ; ${pro.build_flags} @@ -302,7 +302,7 @@ build_flags = ; [env:trial-esp32_poe] ; extends = env:trial-esp32 ; board = esp32-poe -; custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/latest/safeboot-esp32-poe.bin +; custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/v1.0.0/safeboot-esp32-poe.bin ; build_flags = ; ${env.build_flags} ; ${trial.build_flags} @@ -335,7 +335,7 @@ build_flags = [env:oss-wt32_eth01] extends = env:oss-esp32 board = wt32-eth01 -custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/latest/safeboot-wt32-eth01.bin +custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/v1.0.0/safeboot-wt32-eth01.bin upload_speed = 460800 build_flags = ${env.build_flags} @@ -345,7 +345,7 @@ build_flags = [env:pro-wt32_eth01] extends = env:pro-esp32 board = wt32-eth01 -custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/latest/safeboot-wt32-eth01.bin +custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/v1.0.0/safeboot-wt32-eth01.bin upload_speed = 460800 build_flags = ${env.build_flags} @@ -355,7 +355,7 @@ build_flags = [env:trial-wt32_eth01] extends = env:trial-esp32 board = wt32-eth01 -custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/latest/safeboot-wt32-eth01.bin +custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/v1.0.0/safeboot-wt32-eth01.bin upload_speed = 460800 build_flags = ${env.build_flags} @@ -397,7 +397,7 @@ build_flags = [env:oss-lilygo_eth_lite_s3] extends = env:oss-esp32 board = esp32s3box -custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/latest/safeboot-esp32s3box.bin +custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/v1.0.0/safeboot-esp32s3box.bin upload_speed = 115200 build_flags = ${env.build_flags} @@ -407,7 +407,7 @@ build_flags = [env:pro-lilygo_eth_lite_s3] extends = env:pro-esp32 board = esp32s3box -custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/latest/safeboot-esp32s3box.bin +custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/v1.0.0/safeboot-esp32s3box.bin upload_speed = 115200 build_flags = ${env.build_flags} @@ -417,7 +417,7 @@ build_flags = [env:trial-lilygo_eth_lite_s3] extends = env:trial-esp32 board = esp32s3box -custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/latest/safeboot-esp32s3box.bin +custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/v1.0.0/safeboot-esp32s3box.bin upload_speed = 115200 build_flags = ${env.build_flags}