diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 1e37b56..8762632 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -123,6 +123,7 @@ set(headers displays/poweroffdisplay.h displays/powersupplydisplay.h displays/qrcodedebug.h + displays/qrimportdisplay.h displays/spirodisplay.h displays/starfielddisplay.h displays/statusdisplay.h @@ -179,6 +180,7 @@ set(headers newsettings.h ota.h presets.h + qrimport.h rotary.h screens.h serialhandler.h @@ -333,6 +335,7 @@ set(sources displays/poweroffdisplay.cpp displays/powersupplydisplay.cpp displays/qrcodedebug.cpp + displays/qrimportdisplay.cpp displays/spirodisplay.cpp displays/starfielddisplay.cpp displays/statusdisplay.cpp @@ -390,6 +393,7 @@ set(sources newsettings.cpp ota.cpp presets.cpp + qrimport.cpp rotary.cpp screens.cpp serialhandler.cpp diff --git a/main/actions/qraction.cpp b/main/actions/qraction.cpp index eebbeea..00b7e5e 100644 --- a/main/actions/qraction.cpp +++ b/main/actions/qraction.cpp @@ -4,6 +4,7 @@ #include #include #include "displays/qrdisplay.h" +#include "displays/qrimportdisplay.h" using namespace espgui; @@ -13,3 +14,10 @@ void SwitchQrDisplayAction::triggered() { switchScreen(m_msg, m_ver); } + +SwitchQrImportDisplayAction::SwitchQrImportDisplayAction(std::string nvskey) : m_nvskey{nvskey} {} + +void SwitchQrImportDisplayAction::triggered() +{ + switchScreen(m_nvskey); +} diff --git a/main/actions/qraction.h b/main/actions/qraction.h index ec28ef7..0df9382 100644 --- a/main/actions/qraction.h +++ b/main/actions/qraction.h @@ -7,7 +7,8 @@ namespace qraction { struct QrMenu { - std::string_view message; + std::string message; + std::string text; uint8_t ver; }; } // namespace @@ -19,6 +20,16 @@ public: void triggered() override; private: - std::string_view m_msg; + std::string m_msg; uint8_t m_ver; }; + +class SwitchQrImportDisplayAction : public virtual espgui::ActionInterface +{ +public: + SwitchQrImportDisplayAction(std::string nvskey); + + void triggered() override; +private: + std::string m_nvskey; +}; diff --git a/main/displays/menus/greenpassmenu.cpp b/main/displays/menus/greenpassmenu.cpp index 304ec20..edd8cff 100644 --- a/main/displays/menus/greenpassmenu.cpp +++ b/main/displays/menus/greenpassmenu.cpp @@ -1,17 +1,87 @@ #include "greenpassmenu.h" +// 3rd party includes +#include +#include + // local includes #include "actions/switchscreenaction.h" #include "actions/qraction.h" +#include "actions/dummyaction.h" +#include "actions/toggleboolaction.h" #include "displays/menus/mainmenu.h" #include "displays/qrdisplay.h" +#include "displays/qrimportdisplay.h" #include "icons/back.h" +#include "checkboxicon.h" +#include "qrimport.h" using namespace espgui; +namespace { + +bool deleteMode; +struct DeleteModeAccessor : espgui::RefAccessor { bool &getRef() const override { return deleteMode; } }; + +class ErrorMenuItem : public espgui::MenuItem +{ +public: + std::string text() const override { return m_text; } + void setText(std::string &&text) { m_text = std::move(text); } + void setText(const std::string &text) { m_text = text; } + void triggered() override{} + int color() const override { return TFT_RED; } +private: + std::string m_text; +}; + +class CertMenuItem : public espgui::MenuItem +{ +public: + CertMenuItem(qraction::QrMenu qrmenu) : m_qrmenu{qrmenu} {} + std::string text() const override { return m_qrmenu.text; } + void triggered() override + { + if (deleteMode) + { + qrimport::delete_qr_code(m_qrmenu.text); + switchScreen(); + } + else + { + switchScreen(m_qrmenu.message, m_qrmenu.ver); + } + } +private: + qraction::QrMenu m_qrmenu; +}; + +} // namespace + GreenPassMenu::GreenPassMenu() { - constructMenuItem>>(qraction::QrMenu{.message="CORONA_PASS", .ver=15}); + for (uint8_t index = 0; index < 4; index++) + { + const std::string nvs_key = fmt::format("covidcert-{}", index); + ESP_LOGI("greenpassmenu", "Checking key %s", nvs_key.c_str()); + if (qrimport::has_qr_code(nvs_key)) + { + if (const auto certTxt = qrimport::get_qr_code(nvs_key); certTxt) + { + constructMenuItem(qraction::QrMenu{.message=*certTxt, .text=nvs_key , .ver=15}); + } + else + { + auto &menuitem = constructMenuItem(); + menuitem.setText(esp_err_to_name(certTxt.error())); + } + } + else + { + constructMenuItem>>(nvs_key); + } + } + constructMenuItem, ToggleBoolAction, CheckboxIcon, DeleteModeAccessor>>(); constructMenuItem, SwitchScreenAction, StaticMenuItemIcon<&espgui::icons::back>>>(); } diff --git a/main/displays/menus/greenpassmenu.h b/main/displays/menus/greenpassmenu.h index 76a262d..b05edf7 100644 --- a/main/displays/menus/greenpassmenu.h +++ b/main/displays/menus/greenpassmenu.h @@ -3,6 +3,7 @@ #include // local includes +#include "accessorinterface.h" #include "menudisplay.h" #include "texts.h" diff --git a/main/displays/qrimportdisplay.cpp b/main/displays/qrimportdisplay.cpp new file mode 100644 index 0000000..2a207d3 --- /dev/null +++ b/main/displays/qrimportdisplay.cpp @@ -0,0 +1,85 @@ +constexpr const char * const TAG = "qrimport"; +#include "qrimportdisplay.h" + +// 3rd party includes +#include +#include +#include + +// displays/menus includes +#include "displays/menus/greenpassmenu.h" + +// local includes +#include "qrimport.h" + +// m_statuslabel needs redraw + +using namespace espgui; + +QrImportDisplay::QrImportDisplay(std::string nvs_key) : m_nvs_key{nvs_key} {} + +void QrImportDisplay::start() +{ + m_statuslabel.start(); + qrimport::setup_request(); + m_statuslabel.redraw(fmt::format("Request not running.")); +} + +void QrImportDisplay::back() +{ + if (!qrimport::get_request_running()) + { + switchScreen(); + } +} + +void QrImportDisplay::confirm() +{ + // start request + if (!m_confirmLocked) + { + if (const auto result = qrimport::start_qr_request(); !result) + { + switchScreen(); + } + else + m_confirmLocked = true; + } +} + +void QrImportDisplay::update() +{ + m_expected = qrimport::check_request(); + if (m_expected) + { + ESP_LOGI(TAG, "%s", fmt::format("{} => {}", m_nvs_key, *m_expected).c_str()); + if (const auto result = qrimport::set_qr_code(m_nvs_key, *m_expected); !result) + { + tft.setTextColor(TFT_RED); + m_statuslabel.redraw(esp_err_to_name(result.error())); + tft.setTextColor(TFT_WHITE); + m_confirmLocked = true; + } + else + { + switchScreen(); + } + } +} + +void QrImportDisplay::redraw() +{ + if (qrimport::get_request_running()) + { + if (!m_expected) + { + tft.setTextColor(TFT_RED); + m_statuslabel.redraw(*m_expected); + tft.setTextColor(TFT_WHITE); + } + } + else + { + m_statuslabel.redraw("Request not running"); + } +} diff --git a/main/displays/qrimportdisplay.h b/main/displays/qrimportdisplay.h new file mode 100644 index 0000000..9f080d6 --- /dev/null +++ b/main/displays/qrimportdisplay.h @@ -0,0 +1,24 @@ +#pragma once + +// 3rd party includes +#include +#include +#include +#include + +class QrImportDisplay : public espgui::Display +{ + using Base = espgui::Display; +public: + QrImportDisplay(std::string nvs_key); + void start() override; + void back() override; + void update() override; + void redraw() override; + void confirm() override; +private: + bool m_confirmLocked{false}; + espgui::Label m_statuslabel{5,(espgui::tft.height() / 2)-espgui::tft.fontHeight(4)}; + tl::expected m_expected; + std::string m_nvs_key; +}; diff --git a/main/qrimport.cpp b/main/qrimport.cpp new file mode 100644 index 0000000..f81604c --- /dev/null +++ b/main/qrimport.cpp @@ -0,0 +1,153 @@ +#include "qrimport.h" + +// esp-idf includes +#include +#include + +// 3rd party includes +#include +#include +#include +#include + +// local includes +#include "globals.h" + +namespace qrimport { + +namespace { +constexpr const char * const TAG = "QRIMPORT"; + +cpputils::DelayedConstruction http_request; +} // namespace + +// nvs +bool has_qr_code(std::string_view key) +{ + const auto handle = settingsPersister.getCommonHandle(); + + size_t length; + if (const esp_err_t result = nvs_get_str(handle, key.data(), nullptr, &length); result != ESP_OK) + { + if (result != ESP_ERR_NVS_NOT_FOUND) + ESP_LOGW(TAG, "nvs_get_str() size-only for key %.*s failed with %s", key.size(), key.data(), esp_err_to_name(result)); + return false; + } + + return length; +} + +tl::expected get_qr_code(std::string_view key) +{ + const auto handle = settingsPersister.getCommonHandle(); + + size_t length; + if (const esp_err_t result = nvs_get_str(handle, key.data(), nullptr, &length); result != ESP_OK) + { + if (result != ESP_ERR_NVS_NOT_FOUND) + ESP_LOGW(TAG, "nvs_get_str() size-only for key %.*s failed with %s", key.size(), key.data(), esp_err_to_name(result)); + return tl::make_unexpected(result); + } + + // empty string optimization + if (!length) + return {}; + + std::string buf; + buf.resize(length); + if (const esp_err_t result = nvs_get_str(handle, key.data(), buf.data(), &length); result != ESP_OK) + { + ESP_LOGW(TAG, "nvs_get_str() for key %.*s failed with %s", key.size(), key.data(), esp_err_to_name(result)); + return tl::make_unexpected(result); + } + + if (buf.back() == '\n') + buf.resize(buf.size() - 1); + + return buf; // no std::move needed as return is optimized +} + +tl::expected set_qr_code(std::string_view key, std::string_view qrcode) +{ + const auto handle = settingsPersister.getCommonHandle(); + + if (const esp_err_t result = nvs_set_str(handle, key.data(), qrcode.data()); result != ESP_OK) + { + ESP_LOGW(TAG, "nvs_set_str() for key %.*s failed with %s", key.size(), key.data(), esp_err_to_name(result)); + return tl::make_unexpected(result); + } + + return {}; +} + +tl::expected delete_qr_code(std::string_view key) +{ + const auto handle = settingsPersister.getCommonHandle(); + + if (const esp_err_t result = nvs_erase_key(handle, key.data()); result != ESP_OK) + { + ESP_LOGW(TAG, "nvs_erase_key() for key %.*s failed with %s", key.size(), key.data(), esp_err_to_name(result)); + return tl::make_unexpected(result); + } + + return {}; +} + +// web request +void setup_request() +{ + if (!http_request.constructed()) + { + http_request.construct("qr_request", espcpputils::CoreAffinity::Core0); + } +} + +tl::expected start_qr_request() +{ + if (!http_request.constructed()) + { + return tl::make_unexpected("request im oarsch"); + } + + if (const auto res = http_request->start(fmt::format("http://qr.bobbycar.cloud/{}.qr", OTA_USERNAME)); !res) + { + return res; + } + return{}; +} + +tl::expected check_request() +{ + if (!http_request.constructed()) + { + return tl::make_unexpected("request im oarsch"); + } + + if (!http_request->finished()) + { + return tl::make_unexpected("request has not finished"); + } + + const auto helper = cpputils::makeCleanupHelper([](){ http_request->clearFinished(); }); + + if (const auto result = http_request->result(); !result) + { + return tl::make_unexpected(result.error()); + } + else + { + ESP_LOGI(TAG, "%.*s", http_request->buffer().size(), http_request->buffer().data()); + return http_request->takeBuffer(); + } +} + +bool get_request_running() +{ + if (!http_request.constructed()) + { + return false; + } + + return http_request->finished(); +} +} // namespace diff --git a/main/qrimport.h b/main/qrimport.h new file mode 100644 index 0000000..3220774 --- /dev/null +++ b/main/qrimport.h @@ -0,0 +1,30 @@ +#pragma once +/* + * In this file there will be + * - a web-handler to get qr as plain text from http://qr.bobbycar.cloud/files/[OTA_NAME].qr that can be later triggered via in qrimportdisplay.h + * - getter and setter that use NVS + */ + +// system includes +//#include +#include + +// 3rd party includes +#include +#include + +// local includes + +namespace qrimport { + // nvs + bool has_qr_code(std::string_view key); + tl::expected get_qr_code(std::string_view key); + tl::expected set_qr_code(std::string_view key, std::string_view qrcode); + tl::expected delete_qr_code(std::string_view key); + + // web request + void setup_request(); + tl::expected start_qr_request(); + tl::expected check_request(); + bool get_request_running(); +} // namespace diff --git a/main/settingspersister.h b/main/settingspersister.h index 02dfe92..19f5254 100644 --- a/main/settingspersister.h +++ b/main/settingspersister.h @@ -22,6 +22,8 @@ public: std::optional currentlyOpenProfileIndex() const; + nvs_handle getCommonHandle() { return m_handle; } + private: // for common settings nvs_handle m_handle{}; diff --git a/main/texts.cpp b/main/texts.cpp index 71da3e8..7e54aee 100644 --- a/main/texts.cpp +++ b/main/texts.cpp @@ -543,7 +543,8 @@ char TEXT_QRCODE_DEBUG[] = "QR Debug"; //GreenPassMenu char TEXT_GREENPASS[] = "Green Pass"; -char TEXT_SHOWCERT[] = "Show cert"; +char TEXT_ADDCERT[] = "Add cert"; +char TEXT_DELCERT[] = "Delete cert mode"; //EspNowMenu char TEXT_ESPNOW[] = "ESP-Now"; diff --git a/main/texts.h b/main/texts.h index 42d2bd4..afa48d6 100644 --- a/main/texts.h +++ b/main/texts.h @@ -543,7 +543,8 @@ extern char TEXT_QRCODE_DEBUG[]; //GreenPassMenu extern char TEXT_GREENPASS[]; -extern char TEXT_SHOWCERT[]; +extern char TEXT_ADDCERT[]; +extern char TEXT_DELCERT[]; //EspNowMenu extern char TEXT_ESPNOW[]; diff --git a/sdkconfig_comred b/sdkconfig_comred index fcdd9ef..29b69a5 100644 --- a/sdkconfig_comred +++ b/sdkconfig_comred @@ -842,7 +842,7 @@ CONFIG_LOG_DEFAULT_LEVEL_INFO=y # CONFIG_LOG_DEFAULT_LEVEL_VERBOSE is not set CONFIG_LOG_DEFAULT_LEVEL=3 CONFIG_LOG_COLORS=y -# CONFIG_LOG_LOCATION is not set +CONFIG_LOG_LOCATION=y CONFIG_LOG_TIMESTAMP_SOURCE_RTOS=y # CONFIG_LOG_TIMESTAMP_SOURCE_SYSTEM is not set # end of Log output