diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index d2aa6f4..3723ec6 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -125,6 +125,8 @@ set(headers displays/menus/mosfetsmenu.h displays/menus/motorfeedbackdebugmenu.h displays/menus/motorstatedebugmenu.h + displays/menus/otamenu.h + displays/menus/selectotabuildmenu.h displays/menus/presetsmenu.h displays/menus/profilesmenu.h displays/menus/selectbatterytypemenu.h diff --git a/main/buildserver.h b/main/buildserver.h index 1b370cc..4a8da09 100644 --- a/main/buildserver.h +++ b/main/buildserver.h @@ -1,5 +1,14 @@ #pragma once +#include +#include +#include + +// 3rdparty lib includes +#include +#include + +// local includes #include "globals.h" #include "esp_log.h" #include "fmt/core.h" @@ -7,39 +16,169 @@ // esp-idf #include "esp_http_client.h" -/* - * ToDo: - * - get_ota_decriptor_json_string => returns std::string, takes std::string url - */ namespace { + void buildMenuFromJson(std::string json); + void buildMenuRequestError(std::string error); + static std::string url_for_hashes = ""; + static std::string url_for_latest = ""; + static std::array availableVersions = {}; + bool request_running = false; + uint16_t request_failed = false; + bool parsing_finished = false; + cpputils::DelayedConstruction request; -std::string ota_descriptor_json = ""; - -std::string get_ota_url_from_index(uint16_t index) -{ - if (index < stringSettings.otaServers.size()) + std::string get_ota_url_from_index(uint16_t index) { - auto otaServer = stringSettings.otaServers[index]; - if (!otaServer.url.empty()) + if (index < stringSettings.otaServers.size()) { - return otaServer.url; + auto otaServer = stringSettings.otaServers[index]; + if (!otaServer.url.empty()) + { + return otaServer.url; + } + else + { + ESP_LOGE("BOBBY", "Cannot get OTA url: otaServer.url is empty"); + return ""; + } } else { - ESP_LOGE("BOBBY", "Cannot get OTA url: otaServer.url is empty"); + ESP_LOGE("BOBBY", "Cannot get OTA url: Invalid Index"); return ""; } } - else + + uint16_t count_available_buildserver() { - ESP_LOGE("BOBBY", "Cannot get OTA url: Invalid Index"); - return ""; + uint16_t count = 0; + for (const auto &otaServer : stringSettings.otaServers) { + if (!otaServer.url.empty()) count++; + } + return count; + } + + std::string get_hash_url(std::string hash) + { + return fmt::format(url_for_hashes, hash); + } + + std::string get_latest_url() + { + return url_for_latest; + } + + std::string fix_url(std::string url) + { + std::string fixed_url = url; + if (fixed_url.find("http") == std::string::npos) + { + fixed_url = fmt::format("http://{}", fixed_url); + } + return fixed_url; + } + + std::string get_descriptor_url(std::string base_url) + { + std::string url = fix_url(base_url); + return fmt::format("{}/otaDescriptor?username={}", url, OTA_USERNAME); + } + + void parse_response_into_variables(std::string response) + { + StaticJsonDocument<512> doc; + + if (const auto error = deserializeJson(doc, response)) + { + ESP_LOGE("BOBBY", "Error parsing server-response => %s", response.c_str()); + return; + } + + JsonObject availableVersionsObject = doc["availableVersions"]; + for (JsonPair kv : availableVersionsObject) + { + static auto index = 0; + auto hash = kv.key().c_str(); + if (index > availableVersions.size()) + { + break; + } + availableVersions.at(index) = hash; + index++; + } + + url_for_latest = doc["latest"].as(); + url_for_hashes = doc["url"].as(); + parsing_finished = true; + } + + void setup_request() + { + if (!request.constructed()) + request.construct("ota-descriptor-request", espcpputils::CoreAffinity::Core0); + } + + void start_descriptor_request(std::string server_base_url) + { + if (!request.constructed()) + { + ESP_LOGW("BOBBY", "request is im oarsch"); + return; + } + + const auto url = get_descriptor_url(server_base_url); + ESP_LOGD("BOBBY", "requesting data..."); + if (const auto result = request->start(url); !result) + { + ESP_LOGW("BOBBY", "request start failed"); + return; + } + request_running = true; + request_failed = false; + url_for_latest.clear(); + url_for_hashes.clear(); + availableVersions = {}; + parsing_finished = false; + } + + void check_descriptor_request() + { + if (!request.constructed()) + { + ESP_LOGW("BOBBY", "request is im oarsch"); + request_running = false; + request_failed = true; + return; + } + + if (!request->finished()) + { + // ESP_LOGW("BOBBY", "Request has not finished yet."); + return; + } + + const auto helper = cpputils::makeCleanupHelper([](){ request->clearFinished(); }); + const std::string content = std::move(request->takeBuffer()); + + if (const auto result = request->result(); !result) + { + ESP_LOGW("BOBBY", "request failed: %.*s", result.error().size(), result.error().data()); + std::string failed_str = result.error().data(); + auto statuscode = failed_str.substr(failed_str.length() - 3); + request_running = false; + request_failed = std::stoi(statuscode); + return; + } + + const auto result = request->result(); + ESP_LOGW("BOBBY", "Request finished: %s", content.c_str()); + parse_response_into_variables(content); + request_running = false; + request_failed = false; + } + + bool get_request_running() + { + return request_running; } } - -void get_ota_descriptor(std::string url) -{ - auto descriptorUrl = fmt::format("{}/otaDescriptor", url); - // Make GET request to descriptorUrl and store json somewhere to be decoded when needed, for example in ota_descriptor_json (buildserver.h:16) -} -} diff --git a/main/displays/menus/mainmenu.h b/main/displays/menus/mainmenu.h index f46fde9..5b76179 100644 --- a/main/displays/menus/mainmenu.h +++ b/main/displays/menus/mainmenu.h @@ -46,7 +46,7 @@ class Lockscreen; class MosfetsMenu; class DemosMenu; class GarageDisplay; -class UpdateDisplay; +class OtaMenu; class PoweroffDisplay; class DebugMenu; class BatteryMenu; @@ -97,7 +97,7 @@ public: if (SHOWITEM) { constructMenuItem, SwitchScreenAction>>(); } #endif #ifdef FEATURE_OTA - if (SHOWITEM) { constructMenuItem, SwitchScreenAction, StaticMenuItemIcon<&bobbyicons::update>>>(); } + if (SHOWITEM) { constructMenuItem, SwitchScreenAction, StaticMenuItemIcon<&bobbyicons::update>>>(); } #endif constructMenuItem, SwitchScreenAction, StaticMenuItemIcon<&bobbyicons::poweroff>>>(); constructMenuItem, RebootAction, StaticMenuItemIcon<&bobbyicons::reboot>>>(); diff --git a/main/displays/menus/otamenu.h b/main/displays/menus/otamenu.h new file mode 100644 index 0000000..85d3a19 --- /dev/null +++ b/main/displays/menus/otamenu.h @@ -0,0 +1,39 @@ +#pragma once + +// local includes +#include "menudisplay.h" +#include "utils.h" +#include "actions/dummyaction.h" +#include "icons/back.h" +#include "icons/update.h" +#include "icons/presets.h" +#include "texts.h" + + +// forward declares +namespace { +class MainMenu; +class UpdateDisplay; +class SelectBuildMenu; +class SelectBuildServerMenu; +} // namespace + +using namespace espgui; + +namespace { + +class OtaMenu : + public MenuDisplay, + public StaticText, + public BackActionInterface> +{ +public: + OtaMenu() + { + constructMenuItem, SwitchScreenAction, StaticMenuItemIcon<&bobbyicons::presets>>>(); + constructMenuItem, SwitchScreenAction, StaticMenuItemIcon<&bobbyicons::update>>>(); + constructMenuItem, SwitchScreenAction>>(); + constructMenuItem, SwitchScreenAction, StaticMenuItemIcon<&espgui::icons::back>>>(); + } +}; +} // namespace diff --git a/main/displays/menus/selectotabuildmenu.h b/main/displays/menus/selectotabuildmenu.h new file mode 100644 index 0000000..dace7a7 --- /dev/null +++ b/main/displays/menus/selectotabuildmenu.h @@ -0,0 +1,147 @@ +#pragma once + +#include +#include "esp_log.h" +#include "fmt/core.h" + +// local includes +#include "menudisplay.h" +#include "utils.h" +#include "actions/dummyaction.h" +#include "icons/back.h" +#include "icons/update.h" +#include "texts.h" + +#include "buildserver.h" +#include "globals.h" + +#define MESSAGE(text) constructMenuItem, DummyAction>>() + +// forward declares +namespace { +class OtaMenu; +} // namespace + +using namespace espgui; + +namespace { + +// ToDo: if (request_failed) => MESSAGE("An error occurred") + +class VersionMenuItem : public MenuItem +{ +public: + std::string text() const override { return m_hash; } + void setHash(std::string &&hash) { m_hash = std::move(hash); } + void setHash(const std::string &hash) { m_hash = hash; } + void setUrl(std::string &&url) { m_url = std::move(url); } + void setUrl(const std::string &url) { m_url = url; } + + void triggered() override + { + stringSettings.otaUrl = m_url; + saveSettings(); + } +private: + std::string m_url; + std::string m_hash; +}; + +class SelectBuildMenu : + public MenuDisplay, + public StaticText, + public BackActionInterface> +{ + using Base = MenuDisplay; +public: + void update() override; + void buildMenuFromJson(); + void buildMenuRequestError(std::string error); + SelectBuildMenu() + { + if (count_available_buildserver() < 1) + { + MESSAGE(TEXT_OTA_NOBUILDSERVERAVAILABLE); + constructMenuItem, SwitchScreenAction, StaticMenuItemIcon<&espgui::icons::back>>>(); + } + else if (stringSettings.otaServerUrl.empty()) + { + MESSAGE(TEXT_OTA_NOBUILDSERVERSELECTED); + constructMenuItem, SwitchScreenAction, StaticMenuItemIcon<&espgui::icons::back>>>(); + } + else + { + const auto staStatus = wifi_stack::get_sta_status(); + if (staStatus != wifi_stack::WiFiStaStatus::CONNECTED) + { + MESSAGE(TEXT_OTA_NOCONNECTION); + constructMenuItem, SwitchScreenAction, StaticMenuItemIcon<&espgui::icons::back>>>(); + } + else + { + std::string serverUrl = stringSettings.otaServerUrl; + if (serverUrl.substr(serverUrl.length() - 4) == ".bin") + { + auto &menuitem = constructMenuItem(); + std::size_t last_slash_index = serverUrl.find_last_of("/"); + auto filename = serverUrl.substr(last_slash_index+1); + auto hash = filename.substr(0, filename.length() - 4); + menuitem.setHash(hash); + menuitem.setUrl(fix_url(serverUrl)); + constructMenuItem, SwitchScreenAction, StaticMenuItemIcon<&espgui::icons::back>>>(); + } + else + { + setup_request(); + start_descriptor_request(serverUrl); + } + } + } + } +}; + +void SelectBuildMenu::update() +{ + if(get_request_running()) + { + check_descriptor_request(); + if (request_failed) + { + this->buildMenuRequestError(fmt::format("Error: {}", request_failed)); + request_failed = false; + } + } + + if (parsing_finished) + { + parsing_finished = false; + if (!availableVersions.empty()) + { + this->buildMenuFromJson(); + } + } + Base::update(); +} + +void SelectBuildMenu::buildMenuFromJson() +{ + auto &latest = constructMenuItem(); + latest.setHash("latest"); + latest.setUrl(url_for_latest); + + for (const std::string &hash : availableVersions) + { + auto &menuitem = constructMenuItem(); + menuitem.setHash(hash); + menuitem.setUrl(fmt::format(url_for_hashes, hash)); + } + constructMenuItem, SwitchScreenAction, StaticMenuItemIcon<&espgui::icons::back>>>(); +} + +void SelectBuildMenu::buildMenuRequestError(std::string error) +{ + auto &item = constructMenuItem>(); + item.setTitle(error); + constructMenuItem, SwitchScreenAction, StaticMenuItemIcon<&espgui::icons::back>>>(); +} +} // namespace diff --git a/main/displays/updatedisplay.h b/main/displays/updatedisplay.h index 378f51f..6e0a60c 100644 --- a/main/displays/updatedisplay.h +++ b/main/displays/updatedisplay.h @@ -23,12 +23,12 @@ #include "ota.h" namespace { -class MainMenu; +class OtaMenu; } namespace { #ifdef FEATURE_OTA -class UpdateDisplay : public Display, public BackActionInterface> +class UpdateDisplay : public Display, public BackActionInterface> { public: void start() override; diff --git a/main/main.cpp b/main/main.cpp index 39f794e..1b5f967 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -106,6 +106,8 @@ using namespace std::chrono_literals; #include "displays/statusdisplay.h" #ifdef FEATURE_OTA #include "displays/updatedisplay.h" +#include "displays/menus/otamenu.h" +#include "displays/menus/selectotabuildmenu.h" #endif #include "screens.h" #include "dpad.h" diff --git a/main/texts.h b/main/texts.h index 6ad8451..3d85cb8 100644 --- a/main/texts.h +++ b/main/texts.h @@ -475,6 +475,14 @@ constexpr char TEXT_CRASH_DIVZERO[] = "42 / 0"; constexpr char TEXT_SELECTBUILDSERVERMENU[] = "Select Buildserver"; constexpr char TEXT_NOBUILDSERVERCONFIGURED[] = "Not configured"; +//Otamenu +constexpr char TEXT_UPDATENOW[] = "Update now"; +constexpr char TEXT_SELECTBUILD[] = "Select build"; +constexpr char TEXT_OTA_NOBUILDSERVERAVAILABLE[] = "E:No server saved."; +constexpr char TEXT_OTA_NOBUILDSERVERSELECTED[] = "E:No server selected."; +constexpr char TEXT_OTA_NOCONNECTION[] = "E:No internet."; +constexpr char TEXT_OTA_WAITFORRESPONSE[] = "Wait for response..."; + #ifdef FEATURE_CAN constexpr char TEXT_POWERSUPPLY[] = "Powersupply"; #endif