From 454a062b3b5ae48b1c5d6cb8d868bada3a175c5d Mon Sep 17 00:00:00 2001 From: Florian Date: Mon, 27 Feb 2023 12:54:30 -0600 Subject: [PATCH] [OTA] Enable 2 clicks OTA update / one simple MQTT command (#1485) * [OTA] Add automatic OTA updates This feature will enable to update the gateway with one button or a simple MQTT command. * [CI] Enable automatic OTA for nightly development builds And integrate the latest_version file creation into the CI Enable to specify a particular version and switch between dev and production builds --- .github/workflows/build_and_docs_to_dev.yml | 6 +- .github/workflows/release.yml | 2 +- ...nMQTTGateway-OTA-Update-Home-Assistant.png | Bin 0 -> 23000 bytes .../img/Openmqttgateway_logo_mini_margins.png | Bin 0 -> 20260 bytes docs/use/gateway.md | 36 +++++-- main/User_config.h | 10 +- main/ZmqttDiscovery.ino | 13 ++- main/config_mqttDiscovery.h | 6 ++ main/main.ino | 96 ++++++++++++++---- platformio.ini | 1 + scripts/gen_wu.py | 35 ++++--- scripts/gen_wu_dev.py | 24 +++-- scripts/latest_version.json | 5 + scripts/latest_version_dev.json | 4 + scripts/prepare_deploy.sh | 2 +- 15 files changed, 186 insertions(+), 54 deletions(-) create mode 100644 docs/img/OpenMQTTGateway-OTA-Update-Home-Assistant.png create mode 100644 docs/img/Openmqttgateway_logo_mini_margins.png create mode 100644 scripts/latest_version.json create mode 100644 scripts/latest_version_dev.json diff --git a/.github/workflows/build_and_docs_to_dev.yml b/.github/workflows/build_and_docs_to_dev.yml index ca1b200..ee51392 100644 --- a/.github/workflows/build_and_docs_to_dev.yml +++ b/.github/workflows/build_and_docs_to_dev.yml @@ -25,11 +25,13 @@ jobs: npm install - name: Set sha tag run: | - sed -i "s/version_tag/${{ steps.short-sha.outputs.sha }}/g" main/User_config.h + sed -i "s/version_tag/${{ steps.short-sha.outputs.sha }}/g" main/User_config.h scripts/latest_version_dev.json sed -i "s/version_tag/DEVELOPMENT SHA:${{ steps.short-sha.outputs.sha }} TEST ONLY/g" docs/.vuepress/config.js sed -i "s|base: '/'|base: '/dev/'|g" docs/.vuepress/config.js - name: Run PlatformIO - run: platformio run + run: | + export PLATFORMIO_BUILD_FLAGS="'-DDEVELOPMENTOTA=true'" + platformio run - name: Prepare Release Assets run: | sudo apt install rename diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8c85b46..098d5b6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,7 @@ jobs: python -m pip install --upgrade pip pip install platformio - name: Set version tag from git - run: sed -i "s/version_tag/${GITHUB_REF#refs/tags/}/g" main/User_config.h + run: sed -i "s/version_tag/${GITHUB_REF#refs/tags/}/g" main/User_config.h scripts/latest_version.json - name: Run PlatformIO run: platformio run - name: Prepare Release Assets diff --git a/docs/img/OpenMQTTGateway-OTA-Update-Home-Assistant.png b/docs/img/OpenMQTTGateway-OTA-Update-Home-Assistant.png new file mode 100644 index 0000000000000000000000000000000000000000..dbf77a00ed5d7ad9405fc746deb2cedfaf010f41 GIT binary patch literal 23000 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 literal 0 HcmV?d00001 diff --git a/docs/img/Openmqttgateway_logo_mini_margins.png b/docs/img/Openmqttgateway_logo_mini_margins.png new file mode 100644 index 0000000000000000000000000000000000000000..6b8a7618b4ad9ea892c2b23d9df4feeb9ac764f5 GIT binary patch literal 20260 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 M)78&qol`;+0PPr9+W-In literal 0 HcmV?d00001 diff --git a/docs/use/gateway.md b/docs/use/gateway.md index 76f2e5f..e93e07b 100644 --- a/docs/use/gateway.md +++ b/docs/use/gateway.md @@ -104,20 +104,29 @@ The `mqtt_cert_index` value corresponds to the 0 to X index of the `certs_array` # Firmware update from MQTT (ESP only) -The gateway can be updated through an MQTT message by providing a JSON formatted message with a version number, OTA password (optional, see below), and URL to fetch the update from. +When the gateway used is from a standard ESP32 environment [listed and defined here](https://github.com/1technophile/OpenMQTTGateway/blob/development/environments.ini), it can be updated through a simple MQTT command: +``` +mosquitto_pub -t "home/OpenMQTTGateway_ESP32_BLE/commands/MQTTtoSYS/firmware_update" -m '{ + "version": "latest" +}' +``` +This would download the latest version firmware binary from Github and install it. -To enable this functionality, `MQTT_HTTPS_FW_UPDATE` will need to be defined or the line that defines in in user_config.h will need to be uncommented. +Note that this update option is also autodiscovered through Home Assistant convention, you can update directly from the device page with 2 clicks. -::: tip -If using an unsecure MQTT broker it is **highly recommended** to disable the password checking by setting the macro `MQTT_HTTPS_FW_UPDATE_USE_PASSWORD` to 0 (default is 1 (enabled)), otherwise a clear text password may be sent over the network. +![](../img/OpenMQTTGateway-OTA-Update-Home-Assistant.png) -The `server_cert` parameter is optional. If the update server has changed or certificate updated or not set in `user_config.h` then you can provide the certificate here. -::: +You can also indicate the target version to update: +``` +mosquitto_pub -t "home/OpenMQTTGateway_ESP32_BLE/commands/MQTTtoSYS/firmware_update" -m '{ + "version": "v1.2.0" +}' +``` +Alternatively if you want to choose the update URL you can use the command below (ESP32 and ESP8266): -### Example firmware update message: Without certificate, in this case we will use the root certificate defined in User_config.h ``` -mosquitto_pub -t "home/OpenMQTTGateway_ESP32_BLE/commands/firmware_update" -m '{ +mosquitto_pub -t "home/OpenMQTTGateway_ESP32_BLE/commands/MQTTtoSYS/firmware_update" -m '{ "version": "test", "password": "OTAPASSWORD", "url": "https://github.com/1technophile/OpenMQTTGateway/releases/download/v0.9.12/esp32dev-ble-firmware.bin" @@ -126,7 +135,7 @@ mosquitto_pub -t "home/OpenMQTTGateway_ESP32_BLE/commands/firmware_update" -m '{ With certificate: ``` -mosquitto_pub -t "home/OpenMQTTGateway_ESP32_BLE/commands/firmware_update" -m '{ +mosquitto_pub -t "home/OpenMQTTGateway_ESP32_BLE/commands/MQTTtoSYS/firmware_update" -m '{ "version": "test", "password": "OTAPASSWORD", "url": "https://github.com/1technophile/OpenMQTTGateway/releases/download/v0.9.12/esp32dev-ble-firmware.bin", @@ -156,6 +165,15 @@ CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= A bash script is available [here](ota_command_cert.zip) to simplify the use of the `server_cert` parameter. + +To enable this functionality, `MQTT_HTTPS_FW_UPDATE` will need to be defined or the line that defines in in user_config.h will need to be uncommented. + +::: tip +If using an unsecure MQTT broker it is **highly recommended** to disable the password checking by setting the macro `MQTT_HTTPS_FW_UPDATE_USE_PASSWORD` to 0 (default is 1 (enabled)), otherwise a clear text password may be sent over the network. + +The `server_cert` parameter is optional. If the update server has changed or certificate updated or not set in `user_config.h` then you can provide the certificate here. +::: + ::: warning The pre-built binaries for **rfbridge** and **avatto-bakeey-ir** have the above WiFi and MQTT broker credentials and the Firmware update via MQTT options disabled. This is due to the restricted available flash, so as to still be able to use OTA firmware updates for these boards. ::: diff --git a/main/User_config.h b/main/User_config.h index e2ffa0b..f5b2020 100644 --- a/main/User_config.h +++ b/main/User_config.h @@ -238,6 +238,14 @@ CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= # ifndef MQTT_HTTPS_FW_UPDATE_USE_PASSWORD # define MQTT_HTTPS_FW_UPDATE_USE_PASSWORD 1 // Set this to 0 if not using TLS connection to MQTT broker to prevent clear text passwords being sent. # endif +# if DEVELOPMENTOTA +# define OTA_JSON_URL "https://github.com/1technophile/OpenMQTTGateway/raw/gh-pages/dev/firmware_build/latest_version_dev.json" //OTA url used to discover new versions of the firmware from development nightly builds +# else +# define OTA_JSON_URL "https://github.com/1technophile/OpenMQTTGateway/raw/gh-pages/firmware_build/latest_version.json" //OTA url used to discover new versions of the firmware +# endif +# define ENTITY_PICTURE "https://github.com/1technophile/OpenMQTTGateway/raw/development/docs/img/Openmqttgateway_logo_mini_margins.png" +# define RELEASE_LINK_DEV "https://github.com/1technophile/OpenMQTTGateway/raw/gh-pages/dev/firmware_build/" +# define RELEASE_LINK "https://github.com/1technophile/OpenMQTTGateway/releases/download/" # endif # ifndef MQTT_SECURE_SELF_SIGNED @@ -558,7 +566,6 @@ CRGB leds2[FASTLED_IND_NUM_LEDS]; #define subjectMQTTtoX "/commands/#" #define subjectMultiGTWKey "toMQTT" #define subjectGTWSendKey "MQTTto" -#define subjectFWUpdate "firmware_update" // key used for launching commands to the gateway #define restartCmd "restart" @@ -599,6 +606,7 @@ CRGB leds2[FASTLED_IND_NUM_LEDS]; #define InitialMQTTConnectionTimeout 10 // time estimated (s) before the board is connected to MQTT #define subjectSYStoMQTT "/SYStoMQTT" #define subjectMQTTtoSYSset "/commands/MQTTtoSYS/config" +#define subjectMQTTtoSYSupdate "/commands/MQTTtoSYS/firmware_update" #define TimeToResetAtStart 5000 // Time we allow the user at start for the reset command by button press /*-------------------DEFINE LOG LEVEL----------------------*/ #ifndef LOG_LEVEL diff --git a/main/ZmqttDiscovery.ino b/main/ZmqttDiscovery.ino index 52801b4..da513e8 100644 --- a/main/ZmqttDiscovery.ino +++ b/main/ZmqttDiscovery.ino @@ -287,8 +287,10 @@ void createDiscovery(const char* sensor_type, sensor["pl_prs"] = payload_on; // payload_press for a button press } else if (strcmp(sensor_type, "number") == 0) { sensor["cmd_tpl"] = payload_on; // payload_on for a switch + } else if (strcmp(sensor_type, "update") == 0) { + sensor["payload_install"] = payload_on; // payload_install for update } else { - sensor["pl_on"] = payload_on; // payload_on for a switch + sensor["pl_on"] = payload_on; // payload_on for the rest } } if (payload_off[0]) @@ -530,6 +532,15 @@ void pubMqttDiscovery() { "", "", "", "", false, // device name, device manufacturer, device model, device ID, retain stateClassNone //State Class ); + createDiscovery("update", //set Type + subjectSYStoMQTT, "SYS: Firmware Update", (char*)getUniqueId("update", "").c_str(), //set state_topic,name,uniqueId + will_Topic, "firmware", "", //set availability_topic,device_class,value_template, + LATEST_OR_DEV, "", "", //set,payload_on,payload_off,unit_of_meas, + 0, //set off_delay + Gateway_AnnouncementMsg, will_Message, true, subjectMQTTtoSYSupdate, //set,payload_available,payload_not available ,is a gateway entity, command topic + "", "", "", "", false, // device name, device manufacturer, device model, device ID, retain + stateClassNone //State Class + ); # ifdef ZsensorBME280 # define BMEparametersCount 5 Log.trace(F("bme280Discovery" CR)); diff --git a/main/config_mqttDiscovery.h b/main/config_mqttDiscovery.h index 5a3ed1c..597745f 100644 --- a/main/config_mqttDiscovery.h +++ b/main/config_mqttDiscovery.h @@ -268,4 +268,10 @@ const char* availableHASSUnits[] = {"W", "mm/h", "cm"}; +// Define the command used to update through OTA depending if we want to update from dev nightly or latest release +#if DEVELOPMENTOTA +# define LATEST_OR_DEV "{\"version\":\"dev\"}" +#else +# define LATEST_OR_DEV "{\"version\":\"latest\"}" +#endif #endif diff --git a/main/main.ino b/main/main.ino index 7602d03..e091175 100644 --- a/main/main.ino +++ b/main/main.ino @@ -596,9 +596,6 @@ void connectMQTT() { #endif #ifdef ZgatewayIR client.subscribe(subjectMultiGTWIR); // subject on which other OMG will publish, this OMG will store these msg and by the way don't republish them if they have been already published -#endif -#ifdef MQTT_HTTPS_FW_UPDATE - client.subscribe(subjectFWUpdate); #endif Log.trace(F("Subscription OK to the subjects %s" CR), topic2); } @@ -653,7 +650,7 @@ void callback(char* topic, byte* payload, unsigned int length) { //launch the function to treat received data if this data concern OpenMQTTGateway if ((strstr(topic, subjectMultiGTWKey) != NULL) || (strstr(topic, subjectGTWSendKey) != NULL) || - (strstr(topic, subjectFWUpdate) != NULL)) + (strstr(topic, subjectMQTTtoSYSupdate) != NULL)) receivingMQTT(topic, (char*)p); // Free the memory @@ -1509,6 +1506,10 @@ void loop() { connected = true; #if defined(ESP8266) || defined(ESP32) || defined(__AVR_ATmega2560__) || defined(__AVR_ATmega1280__) if (now > (timer_sys_measures + (TimeBetweenReadingSYS * 1000)) || !timer_sys_measures) { +# if defined(ESP32) && defined(MQTT_HTTPS_FW_UPDATE) + if (!timer_sys_measures) // Only check for updates at start for ESP32 + checkForUpdates(); +# endif timer_sys_measures = millis(); stateMeasures(); # ifdef ZgatewayBT @@ -1691,6 +1692,7 @@ void stateMeasures() { SYSdata["uptime"] = uptime(); SYSdata["version"] = OMG_VERSION; # if defined(ESP8266) || defined(ESP32) + SYSdata["env"] = ENV_NAME; uint32_t freeMem; freeMem = ESP.getFreeHeap(); SYSdata["freemem"] = freeMem; @@ -1940,39 +1942,97 @@ void receivingMQTT(char* topicOri, char* datacallback) { } #ifdef MQTT_HTTPS_FW_UPDATE +String latestVersion; # ifdef ESP32 +# include + # include "zzHTTPUpdate.h" + +/** + * Check on a server the latest version information to build a releaseLink + * The release link will be used when the user trigger an OTA update command + * Only available for ESP32 + */ +bool checkForUpdates() { + HTTPClient http; + http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); + http.begin(OTA_JSON_URL, OTAserver_cert); + int httpCode = http.GET(); + StaticJsonDocument jsonBuffer; + JsonObject jsondata = jsonBuffer.to(); + + if (httpCode > 0) { //Check for the returning code + String payload = http.getString(); + auto error = deserializeJson(jsonBuffer, payload); + if (error) { + Log.error(F("Deserialize MQTT data failed: %s" CR), error.c_str()); + } + Log.trace(F("HttpCode %d" CR), httpCode); + Log.trace(F("Payload %s" CR), payload.c_str()); + } else { + Log.error(F("Error on HTTP request")); + } + http.end(); //Free the resources + if (jsondata.containsKey("latest_version")) { + jsondata["installed_version"] = OMG_VERSION; + jsondata["entity_picture"] = ENTITY_PICTURE; + latestVersion = jsondata["latest_version"].as(); + pub(subjectSYStoMQTT, jsondata); + Log.trace(F("Update file found on server" CR)); + return true; + } else { + Log.trace(F("No update file found on server" CR)); + return false; + } +} # elif ESP8266 # include # endif + void MQTTHttpsFWUpdate(char* topicOri, JsonObject& HttpsFwUpdateData) { - if (strstr(topicOri, subjectFWUpdate) != NULL) { + if (strstr(topicOri, subjectMQTTtoSYSupdate) != NULL) { const char* version = HttpsFwUpdateData["version"]; if (version && ((strlen(version) != strlen(OMG_VERSION)) || strcmp(version, OMG_VERSION) != 0)) { const char* url = HttpsFwUpdateData["url"]; + String systemUrl; if (url) { if (!strstr((url + (strlen(url) - 5)), ".bin")) { Log.error(F("Invalid firmware extension" CR)); return; } +# if MQTT_HTTPS_FW_UPDATE_USE_PASSWORD > 0 + const char* pwd = HttpsFwUpdateData["password"]; + if (pwd) { + if (strcmp(pwd, ota_pass) != 0) { + Log.error(F("Invalid OTA password" CR)); + return; + } + } else { + Log.error(F("No password sent" CR)); + return; + } +# endif +# ifdef ESP32 + } else if (strcmp(version, "latest") == 0) { + if (!checkForUpdates()) + return; + systemUrl = RELEASE_LINK + latestVersion + "/" + ENV_NAME + "-firmware.bin"; + url = systemUrl.c_str(); + Log.notice(F("Using system OTA url with latest version %s" CR), url); + } else if (strcmp(version, "dev") == 0) { + systemUrl = String(RELEASE_LINK_DEV) + ENV_NAME + "-firmware.bin"; + url = systemUrl.c_str(); + Log.notice(F("Using system OTA url with dev version %s" CR), url); + } else if (version[0] == 'v') { + systemUrl = String(RELEASE_LINK) + version + "/" + ENV_NAME + "-firmware.bin"; + url = systemUrl.c_str(); + Log.notice(F("Using system OTA url with defined version %s" CR), url); +# endif } else { Log.error(F("Invalid URL" CR)); return; } -# if MQTT_HTTPS_FW_UPDATE_USE_PASSWORD > 0 - const char* pwd = HttpsFwUpdateData["password"]; - if (pwd) { - if (strcmp(pwd, ota_pass) != 0) { - Log.error(F("Invalid OTA password" CR)); - return; - } - } else { - Log.error(F("No password sent" CR)); - return; - } -# endif - Log.warning(F("Starting firmware update" CR)); SendReceiveIndicatorON(); ErrorIndicatorON(); diff --git a/platformio.ini b/platformio.ini index 7b99171..89a02e2 100644 --- a/platformio.ini +++ b/platformio.ini @@ -177,6 +177,7 @@ lib_deps = ${env.lib_deps} build_flags = ${env.build_flags} + '-DENV_NAME="$PIOENV"' '-DZmqttDiscovery="HADiscovery"' '-DTRACE=1' '-DCONFIG_BT_NIMBLE_ROLE_PERIPHERAL_DISABLED' diff --git a/scripts/gen_wu.py b/scripts/gen_wu.py index 0e082b4..d5e3365 100644 --- a/scripts/gen_wu.py +++ b/scripts/gen_wu.py @@ -3,6 +3,7 @@ import requests import json import string import argparse +import shutil mf_temp32 = string.Template('''{ "name": "OpenMQTTGateway", @@ -86,10 +87,11 @@ repo = args.repo manif_folder = "/firmware_build/" manif_path = 'docs/.vuepress/public/firmware_build/' vue_path = 'docs/.vuepress/components/' -cors_proxy = '' #'https://cors.bridged.cc/' +cors_proxy = '' # 'https://cors.bridged.cc/' esp32_blurl = 'https://github.com/espressif/arduino-esp32/raw/2.0.5/tools/sdk/esp32/bin/bootloader_dio_80m.bin' -esp32_boot = 'https://github.com/espressif/arduino-esp32/raw/2.0.5/tools/partitions/boot_app0.bin' -release = requests.get('https://api.github.com/repos/' + repo + '/releases/latest') +esp32_boot = 'https://github.com/espressif/arduino-esp32/raw/2.0.5/tools/partitions/boot_app0.bin' +release = requests.get('https://api.github.com/repos/' + + repo + '/releases/latest') rel_data = json.loads(release.text) if 'assets' in rel_data: @@ -101,6 +103,9 @@ else: if not os.path.exists(manif_path): os.makedirs(manif_path) +# copy OTA latest version definition +shutil.move("scripts/latest_version.json", manif_path + "latest_version.json") + if not os.path.exists(vue_path): os.makedirs(vue_path) @@ -109,12 +114,12 @@ wu_file.write(wu_temp_p1) bl_bin = requests.get(esp32_blurl) filename = esp32_blurl.split('/')[-1] -with open(manif_path + filename,'wb') as output_file: +with open(manif_path + filename, 'wb') as output_file: output_file.write(bl_bin.content) boot_bin = requests.get(esp32_boot) filename = esp32_boot.split('/')[-1] -with open(manif_path + filename,'wb') as output_file: +with open(manif_path + filename, 'wb') as output_file: output_file.write(boot_bin.content) for item in range(len(assets)): @@ -124,21 +129,23 @@ for item in range(len(assets)): man_file = fw + '.manifest.json' fw_url = assets[item]['browser_download_url'] fwp_url = fw_url.split('-firmware')[0] + '-partitions.bin' - mani_str = mf_temp32.substitute({'cp':cors_proxy, 'part':manif_folder + fwp_url.split('/')[-1], 'bin':manif_folder + fw_url.split('/')[-1], 'bl':manif_folder + esp32_blurl.split('/')[-1], 'boot':manif_folder + esp32_boot.split('/')[-1]}) + mani_str = mf_temp32.substitute({'cp': cors_proxy, 'part': manif_folder + fwp_url.split('/')[-1], 'bin': manif_folder + fw_url.split( + '/')[-1], 'bl': manif_folder + esp32_blurl.split('/')[-1], 'boot': manif_folder + esp32_boot.split('/')[-1]}) with open(manif_path + man_file, 'w') as nf: nf.write(mani_str) - wu_file.write(wu_temp_opt.substitute({'mff':manif_folder + man_file, 'mfn':fw})) + wu_file.write(wu_temp_opt.substitute( + {'mff': manif_folder + man_file, 'mfn': fw})) fw_bin = requests.get(fw_url) filename = fw_url.split('/')[-1] - with open(manif_path + filename,'wb') as output_file: + with open(manif_path + filename, 'wb') as output_file: output_file.write(fw_bin.content) part_bin = requests.get(fwp_url) filename = fwp_url.split('/')[-1] - with open(manif_path + filename,'wb') as output_file: + with open(manif_path + filename, 'wb') as output_file: output_file.write(part_bin.content) print('Created: ' + os.path.abspath(man_file)) @@ -151,20 +158,22 @@ for item in range(len(assets)): fw = name.split('-firmware')[0] man_file = fw + '.manifest.json' fw_url = assets[item]['browser_download_url'] - mani_str = mf_temp8266.substitute({'cp':cors_proxy, 'bin':manif_folder + fw_url.split('/')[-1]}) + mani_str = mf_temp8266.substitute( + {'cp': cors_proxy, 'bin': manif_folder + fw_url.split('/')[-1]}) with open(manif_path + man_file, 'w') as nf: nf.write(mani_str) - wu_file.write(manif_folder + wu_temp_opt.substitute({'mff':manif_folder + man_file, 'mfn':fw})) + wu_file.write( + manif_folder + wu_temp_opt.substitute({'mff': manif_folder + man_file, 'mfn': fw})) fw_bin = requests.get(fw_url) filename = fw_url.split('/')[-1] - with open(manif_path + filename,'wb') as output_file: + with open(manif_path + filename, 'wb') as output_file: output_file.write(fw_bin.content) part_bin = requests.get(fwp_url) filename = fwp_url.split('/')[-1] - with open(manif_path + filename,'wb') as output_file: + with open(manif_path + filename, 'wb') as output_file: output_file.write(part_bin.content) print('Created: ' + os.path.abspath(man_file)) diff --git a/scripts/gen_wu_dev.py b/scripts/gen_wu_dev.py index f85fc91..054d302 100644 --- a/scripts/gen_wu_dev.py +++ b/scripts/gen_wu_dev.py @@ -83,13 +83,17 @@ manif_folder = "/dev/firmware_build/" manif_path = 'docs/.vuepress/public/firmware_build/' vue_path = 'docs/.vuepress/components/' bin_path = 'toDeploy/' -cors_proxy = '' #'https://cors.bridged.cc/' +cors_proxy = '' # 'https://cors.bridged.cc/' esp32_blurl = 'https://github.com/espressif/arduino-esp32/raw/2.0.5/tools/sdk/esp32/bin/bootloader_dio_80m.bin' -esp32_boot = 'https://github.com/espressif/arduino-esp32/raw/2.0.5/tools/partitions/boot_app0.bin' +esp32_boot = 'https://github.com/espressif/arduino-esp32/raw/2.0.5/tools/partitions/boot_app0.bin' if not os.path.exists(manif_path): os.makedirs(manif_path) +# copy OTA latest version definition +shutil.move("scripts/latest_version_dev.json", + manif_path + "latest_version_dev.json") + if not os.path.exists(vue_path): os.makedirs(vue_path) @@ -98,12 +102,12 @@ wu_file.write(wu_temp_p1) bl_bin = requests.get(esp32_blurl) filename = esp32_blurl.split('/')[-1] -with open(manif_path + filename,'wb') as output_file: +with open(manif_path + filename, 'wb') as output_file: output_file.write(bl_bin.content) boot_bin = requests.get(esp32_boot) filename = esp32_boot.split('/')[-1] -with open(manif_path + filename,'wb') as output_file: +with open(manif_path + filename, 'wb') as output_file: output_file.write(boot_bin.content) for name in os.listdir(bin_path): @@ -113,12 +117,14 @@ for name in os.listdir(bin_path): print('Bin name:' + name) part_name = name.split('-firmware')[0] + '-partitions.bin' print('Partition name:' + part_name) - mani_str = mf_temp32.substitute({'cp':cors_proxy, 'part':manif_folder + part_name.split('/')[-1], 'bin':manif_folder + name.split('/')[-1], 'bl':manif_folder + esp32_blurl.split('/')[-1], 'boot':manif_folder + esp32_boot.split('/')[-1]}) + mani_str = mf_temp32.substitute({'cp': cors_proxy, 'part': manif_folder + part_name.split('/')[-1], 'bin': manif_folder + name.split( + '/')[-1], 'bl': manif_folder + esp32_blurl.split('/')[-1], 'boot': manif_folder + esp32_boot.split('/')[-1]}) with open(manif_path + man_file, 'w') as nf: nf.write(mani_str) - wu_file.write(wu_temp_opt.substitute({'mff':manif_folder + man_file, 'mfn':fw})) + wu_file.write(wu_temp_opt.substitute( + {'mff': manif_folder + man_file, 'mfn': fw})) shutil.copyfile(bin_path + name, (manif_path + name)) shutil.copyfile(bin_path + part_name, (manif_path + part_name)) @@ -134,12 +140,14 @@ for name in os.listdir(bin_path): print('Bin name:' + name) part_name = name.split('-firmware')[0] + '-partitions.bin' print('Partition name:' + part_name) - mani_str = mf_temp8266.substitute({'cp':cors_proxy, 'bin':manif_folder + name.split('/')[-1]}) + mani_str = mf_temp8266.substitute( + {'cp': cors_proxy, 'bin': manif_folder + name.split('/')[-1]}) with open(manif_path + man_file, 'w') as nf: nf.write(mani_str) - wu_file.write(manif_folder + wu_temp_opt.substitute({'mff':manif_folder + man_file, 'mfn':fw})) + wu_file.write( + manif_folder + wu_temp_opt.substitute({'mff': manif_folder + man_file, 'mfn': fw})) shutil.copyfile(bin_path + name, (manif_path + name)) diff --git a/scripts/latest_version.json b/scripts/latest_version.json new file mode 100644 index 0000000..a64346f --- /dev/null +++ b/scripts/latest_version.json @@ -0,0 +1,5 @@ +{ + "latest_version": "version_tag", + "title": "OpenMQTTGateway version_tag", + "release_url": "https://github.com/1technophile/OpenMQTTGateway/releases/tag/version_tag", +} \ No newline at end of file diff --git a/scripts/latest_version_dev.json b/scripts/latest_version_dev.json new file mode 100644 index 0000000..b759936 --- /dev/null +++ b/scripts/latest_version_dev.json @@ -0,0 +1,4 @@ +{ + "latest_version": "version_tag", + "title": "OpenMQTTGateway Nightly (TEST ONLY) version_tag", +} \ No newline at end of file diff --git a/scripts/prepare_deploy.sh b/scripts/prepare_deploy.sh index 520dab0..2dda51b 100755 --- a/scripts/prepare_deploy.sh +++ b/scripts/prepare_deploy.sh @@ -1,6 +1,6 @@ #!/bin/bash set -e -echo "renaming bin files with the board name" +echo "renaming bin files with the environment name" rename -v 's:/:-:g' .pio/build/*/*.bin mkdir toDeploy rename 's/.pio-build-//' .*.bin