From d2487d9c7dc87d54f37d423e19ae333ec5d285dd Mon Sep 17 00:00:00 2001 From: 0xFEEDC0DE64 Date: Sun, 21 Nov 2021 00:56:28 +0100 Subject: [PATCH] fixes webserver --- main/webserver.cpp | 65 +++++ main/webserver.h | 64 +---- main/webserver_displaycontrol.cpp | 391 ++++++++++++++++++++++++++++++ main/webserver_displaycontrol.h | 389 ----------------------------- main/webserver_dumpnvs.cpp | 171 +++++++++++++ main/webserver_dumpnvs.h | 172 ------------- main/webserver_lock.cpp | 3 + main/webserver_lock.h | 6 +- main/webserver_ota.cpp | 258 ++++++++++++++++++++ main/webserver_ota.h | 251 ------------------- main/webserver_settings.cpp | 301 +++++++++++++++++++++++ main/webserver_settings.h | 298 ----------------------- main/webserver_stringsettings.cpp | 169 +++++++++++++ main/webserver_stringsettings.h | 166 ------------- 14 files changed, 1360 insertions(+), 1344 deletions(-) diff --git a/main/webserver.cpp b/main/webserver.cpp index e69de29..df21c57 100644 --- a/main/webserver.cpp +++ b/main/webserver.cpp @@ -0,0 +1,65 @@ +#include "webserver.h" + +#ifdef FEATURE_WEBSERVER +namespace { +constexpr const char * const TAG = "BOBBYWEB"; +} // namespace + +httpd_handle_t httpdHandle; + +void initWebserver() +{ + webserver_lock.construct(); + webserver_lock->take(portMAX_DELAY); + + { + httpd_config_t httpConfig HTTPD_DEFAULT_CONFIG(); + httpConfig.core_id = 1; + httpConfig.max_uri_handlers = 14; + + const auto result = httpd_start(&httpdHandle, &httpConfig); + ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_INFO : ESP_LOG_ERROR), TAG, "httpd_start(): %s", esp_err_to_name(result)); + if (result != ESP_OK) + return; + } + + for (const httpd_uri_t &uri : { + httpd_uri_t { .uri = "/", .method = HTTP_GET, .handler = webserver_root_handler, .user_ctx = NULL }, + httpd_uri_t { .uri = "/triggerButton", .method = HTTP_GET, .handler = webserver_triggerButton_handler, .user_ctx = NULL }, + httpd_uri_t { .uri = "/triggerItem", .method = HTTP_GET, .handler = webserver_triggerItem_handler, .user_ctx = NULL }, + httpd_uri_t { .uri = "/setValue", .method = HTTP_GET, .handler = webserver_setValue_handler, .user_ctx = NULL }, + httpd_uri_t { .uri = "/reboot", .method = HTTP_GET, .handler = webserver_reboot_handler, .user_ctx = NULL }, +#ifdef FEATURE_OTA + httpd_uri_t { .uri = "/ota", .method = HTTP_GET, .handler = webserver_ota_handler, .user_ctx = NULL }, + httpd_uri_t { .uri = "/triggerOta", .method = HTTP_GET, .handler = webserver_trigger_ota_handler, .user_ctx = NULL }, +#endif + httpd_uri_t { .uri = "/settings", .method = HTTP_GET, .handler = webserver_settings_handler, .user_ctx = NULL }, + httpd_uri_t { .uri = "/saveSettings", .method = HTTP_GET, .handler = webserver_saveSettings_handler, .user_ctx = NULL }, + httpd_uri_t { .uri = "/stringSettings", .method = HTTP_GET, .handler = webserver_stringSettings_handler, .user_ctx = NULL }, + httpd_uri_t { .uri = "/saveStringSettings", .method = HTTP_GET, .handler = webserver_saveStringSettings_handler, .user_ctx = NULL }, +#ifdef OLD_NVS + httpd_uri_t { .uri = "/dumpnvs", .method = HTTP_GET, .handler = webserver_dump_nvs_handler, .user_ctx = NULL }, +#endif + }) + { + const auto result = httpd_register_uri_handler(httpdHandle, &uri); + ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_INFO : ESP_LOG_ERROR), TAG, "httpd_register_uri_handler() for %s: %s", uri.uri, esp_err_to_name(result)); + //if (result != ESP_OK) + // return result; + } +} + +void handleWebserver() +{ + webserver_lock->give(); + webserver_lock->take(portMAX_DELAY); +} + +esp_err_t webserver_reboot_handler(httpd_req_t *req) +{ + esp_restart(); + + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::Ok, "text/plain", "REBOOT called...") +} + +#endif diff --git a/main/webserver.h b/main/webserver.h index 954f187..c312174 100644 --- a/main/webserver.h +++ b/main/webserver.h @@ -24,74 +24,12 @@ #include "webserver_stringsettings.h" #ifdef OLD_NVS #include "webserver_dumpnvs.h" -using namespace dump_nvs_handler; #endif #ifdef FEATURE_WEBSERVER -namespace { -httpd_handle_t httpdHandle; +extern httpd_handle_t httpdHandle; void initWebserver(); void handleWebserver(); esp_err_t webserver_reboot_handler(httpd_req_t *req); - -} - -namespace { -void initWebserver() -{ - webserver_lock.construct(); - webserver_lock->take(portMAX_DELAY); - - { - httpd_config_t httpConfig HTTPD_DEFAULT_CONFIG(); - httpConfig.core_id = 1; - httpConfig.max_uri_handlers = 14; - - const auto result = httpd_start(&httpdHandle, &httpConfig); - ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_INFO : ESP_LOG_ERROR), TAG, "httpd_start(): %s", esp_err_to_name(result)); - if (result != ESP_OK) - return; - } - - for (const httpd_uri_t &uri : { - httpd_uri_t { .uri = "/", .method = HTTP_GET, .handler = webserver_root_handler, .user_ctx = NULL }, - httpd_uri_t { .uri = "/triggerButton", .method = HTTP_GET, .handler = webserver_triggerButton_handler, .user_ctx = NULL }, - httpd_uri_t { .uri = "/triggerItem", .method = HTTP_GET, .handler = webserver_triggerItem_handler, .user_ctx = NULL }, - httpd_uri_t { .uri = "/setValue", .method = HTTP_GET, .handler = webserver_setValue_handler, .user_ctx = NULL }, - httpd_uri_t { .uri = "/reboot", .method = HTTP_GET, .handler = webserver_reboot_handler, .user_ctx = NULL }, -#ifdef FEATURE_OTA - httpd_uri_t { .uri = "/ota", .method = HTTP_GET, .handler = webserver_ota_handler, .user_ctx = NULL }, - httpd_uri_t { .uri = "/triggerOta", .method = HTTP_GET, .handler = webserver_trigger_ota_handler, .user_ctx = NULL }, -#endif - httpd_uri_t { .uri = "/settings", .method = HTTP_GET, .handler = webserver_settings_handler, .user_ctx = NULL }, - httpd_uri_t { .uri = "/saveSettings", .method = HTTP_GET, .handler = webserver_saveSettings_handler, .user_ctx = NULL }, - httpd_uri_t { .uri = "/stringSettings", .method = HTTP_GET, .handler = webserver_stringSettings_handler, .user_ctx = NULL }, - httpd_uri_t { .uri = "/saveStringSettings", .method = HTTP_GET, .handler = webserver_saveStringSettings_handler, .user_ctx = NULL }, -#ifdef OLD_NVS - httpd_uri_t { .uri = "/dumpnvs", .method = HTTP_GET, .handler = webserver_dump_nvs_handler, .user_ctx = NULL }, -#endif - }) - { - const auto result = httpd_register_uri_handler(httpdHandle, &uri); - ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_INFO : ESP_LOG_ERROR), TAG, "httpd_register_uri_handler() for %s: %s", uri.uri, esp_err_to_name(result)); - //if (result != ESP_OK) - // return result; - } -} - -void handleWebserver() -{ - webserver_lock->give(); - webserver_lock->take(portMAX_DELAY); -} - -esp_err_t webserver_reboot_handler(httpd_req_t *req) -{ - esp_restart(); - - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::Ok, "text/plain", "REBOOT called...") -} - -} #endif diff --git a/main/webserver_displaycontrol.cpp b/main/webserver_displaycontrol.cpp index e69de29..8d215db 100644 --- a/main/webserver_displaycontrol.cpp +++ b/main/webserver_displaycontrol.cpp @@ -0,0 +1,391 @@ +#include "webserver_displaycontrol.h" + +#ifdef FEATURE_WEBSERVER +using esphttpdutils::HtmlTag; + +namespace { +constexpr const char * const TAG = "BOBBYWEB"; +} // namespace + +esp_err_t webserver_root_handler(httpd_req_t *req) +{ + espcpputils::LockHelper helper{webserver_lock->handle, std::chrono::ceil(5s).count()}; + if (!helper.locked()) + { + constexpr const std::string_view msg = "could not lock webserver_lock"; + ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); + } + + std::string body; + + { + HtmlTag htmlTag{"html", body}; + + { + HtmlTag headTag{"head", body}; + + { + HtmlTag titleTag{"title", body}; + body += "Display control"; + } + + body += ""; + } + + { + HtmlTag bodyTag{"body", body}; + + { + HtmlTag h1Tag{"h1", body}; + body += "Display control"; + } + + { + HtmlTag pTag{"p", body}; + body += "Display control - " +#ifdef FEATURE_OTA + "Update - " +#endif + + "Settings - " + "String Settings - " + "Dump NVS"; + } + + { + HtmlTag pTag{"p", body}; + body += "Up " + "Down " + "Confirm " + "Back " + "Profile0 " + "Profile1 " + "Profile2 " + "Profile3 "; + } + + if (auto currentDisplay = static_cast(espgui::currentDisplay.get())) + { + if (const auto *textInterface = currentDisplay->asTextInterface()) + { + HtmlTag h2Tag{"h2", body}; + body += esphttpdutils::htmlentities(textInterface->text()); + } + + if (const auto *menuDisplay = currentDisplay->asMenuDisplay()) + { + HtmlTag ulTag{"ul", body}; + + int i{0}; + menuDisplay->runForEveryMenuItem([&,selectedIndex=menuDisplay->selectedIndex()](const espgui::MenuItem &menuItem){ + HtmlTag liTag = i == selectedIndex ? + HtmlTag{"li", "style=\"border: 1px solid black;\"", body} : + HtmlTag{"li", body}; + + body += fmt::format("{}", i, esphttpdutils::htmlentities(menuItem.text())); + i++; + }); + } + else if (const auto *changeValueDisplay = currentDisplay->asChangeValueDisplayInterface()) + { + HtmlTag formTag{"form", "action=\"/setValue\" method=\"GET\"", body}; + body += fmt::format("", changeValueDisplay->shownValue()); + body += ""; + } + else + { + body += "No web control implemented for current display."; + } + } + else + { + body += "Currently no screen instantiated."; + } + } + } + + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::Ok, "text/html", body) +} + +esp_err_t webserver_triggerButton_handler(httpd_req_t *req) +{ + espcpputils::LockHelper helper{webserver_lock->handle, std::chrono::ceil(5s).count()}; + if (!helper.locked()) + { + constexpr const std::string_view msg = "could not lock webserver_lock"; + ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); + } + + std::string query; + if (auto result = esphttpdutils::webserver_get_query(req)) + query = *result; + else + { + ESP_LOGE(TAG, "%.*s", result.error().size(), result.error().data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", result.error()); + } + + std::string button; + constexpr const std::string_view buttonParamName{"button"}; + + { + char valueBufEncoded[256]; + if (const auto result = httpd_query_key_value(query.data(), buttonParamName.data(), valueBufEncoded, 256); result != ESP_OK) + { + if (result == ESP_ERR_NOT_FOUND) + { + const auto msg = fmt::format("{} not set", buttonParamName); + ESP_LOGW(TAG, "%.*s", msg.size(), msg.data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); + } + else + { + const auto msg = fmt::format("httpd_query_key_value() {} failed with {}", buttonParamName, esp_err_to_name(result)); + ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); + } + } + + char valueBuf[257]; + esphttpdutils::urldecode(valueBuf, valueBufEncoded); + + button = valueBuf; + } + + if (button == "up") + { + InputDispatcher::rotate(-1); + + CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Location", "/") + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::TemporaryRedirect, "text/html", "Ok, continue at /") + } + else if (button == "down") + { + InputDispatcher::rotate(1); + + CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Location", "/") + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::TemporaryRedirect, "text/html", "Ok, continue at /") + } + else if (button == "confirm") + { + InputDispatcher::confirmButton(true); + InputDispatcher::confirmButton(false); + + CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Location", "/") + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::TemporaryRedirect, "text/html", "Ok, continue at /") + } + else if (button == "back") + { + InputDispatcher::backButton(true); + InputDispatcher::backButton(false); + + CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Location", "/") + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::TemporaryRedirect, "text/html", "Ok, continue at /") + } + else if (button == "profile0") + { + InputDispatcher::profileButton(0, true); + InputDispatcher::profileButton(0, false); + + CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Location", "/") + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::TemporaryRedirect, "text/html", "Ok, continue at /") + } + else if (button == "profile1") + { + InputDispatcher::profileButton(1, true); + InputDispatcher::profileButton(1, false); + + CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Location", "/") + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::TemporaryRedirect, "text/html", "Ok, continue at /") + } + else if (button == "profile2") + { + InputDispatcher::profileButton(2, true); + InputDispatcher::profileButton(2, false); + + CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Location", "/") + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::TemporaryRedirect, "text/html", "Ok, continue at /") + } + else if (button == "profile3") + { + InputDispatcher::profileButton(3, true); + InputDispatcher::profileButton(3, false); + + CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Location", "/") + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::TemporaryRedirect, "text/html", "Ok, continue at /") + } + else + { + const auto msg = fmt::format("invalid {} {}", buttonParamName, button); + ESP_LOGW(TAG, "%.*s", msg.size(), msg.data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); + } +} + +esp_err_t webserver_triggerItem_handler(httpd_req_t *req) +{ + espcpputils::LockHelper helper{webserver_lock->handle, std::chrono::ceil(5s).count()}; + if (!helper.locked()) + { + constexpr const std::string_view msg = "could not lock webserver_lock"; + ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); + } + + std::string query; + if (auto result = esphttpdutils::webserver_get_query(req)) + query = *result; + else + { + ESP_LOGE(TAG, "%.*s", result.error().size(), result.error().data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", result.error()); + } + + std::size_t index; + + constexpr const std::string_view indexParamName{"index"}; + + { + char valueBufEncoded[256]; + if (const auto result = httpd_query_key_value(query.data(), indexParamName.data(), valueBufEncoded, 256); result != ESP_OK) + { + if (result == ESP_ERR_NOT_FOUND) + { + const auto msg = fmt::format("{} not set", indexParamName); + ESP_LOGW(TAG, "%.*s", msg.size(), msg.data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); + } + else + { + const auto msg = fmt::format("httpd_query_key_value() {} failed with {}", indexParamName, esp_err_to_name(result)); + ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); + } + } + + char valueBuf[257]; + esphttpdutils::urldecode(valueBuf, valueBufEncoded); + + std::string_view value{valueBuf}; + + if (auto parsed = cpputils::fromString(value)) + { + index = *parsed; + } + else + { + const auto msg = fmt::format("could not parse {} {}", indexParamName, value); + ESP_LOGW(TAG, "%.*s", msg.size(), msg.data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); + } + } + + if (!espgui::currentDisplay) + { + constexpr const std::string_view msg = "espgui::currentDisplay is null"; + ESP_LOGW(TAG, "%.*s", msg.size(), msg.data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); + } + + auto *menuDisplay = espgui::currentDisplay->asMenuDisplay(); + if (!menuDisplay) + { + constexpr const std::string_view msg = "espgui::currentDisplay is not a menu display"; + ESP_LOGW(TAG, "%.*s", msg.size(), msg.data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); + } + + if (/*index < 0 ||*/ index >= menuDisplay->menuItemCount()) + { + const auto msg = fmt::format("{} {} out of range (must be smaller than {})", indexParamName, index, menuDisplay->menuItemCount()); + ESP_LOGW(TAG, "%.*s", msg.size(), msg.data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); + } + + menuDisplay->getMenuItem(index).triggered(); + + CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Location", "/") + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::TemporaryRedirect, "text/html", "Ok, continue at /") +} + +esp_err_t webserver_setValue_handler(httpd_req_t *req) +{ + espcpputils::LockHelper helper{webserver_lock->handle, std::chrono::ceil(5s).count()}; + if (!helper.locked()) + { + constexpr const std::string_view msg = "could not lock webserver_lock"; + ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); + } + + std::string query; + if (auto result = esphttpdutils::webserver_get_query(req)) + query = *result; + else + { + ESP_LOGE(TAG, "%.*s", result.error().size(), result.error().data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", result.error()); + } + + int newValue; + + constexpr const std::string_view valueParamName{"value"}; + + { + char valueBufEncoded[256]; + if (const auto result = httpd_query_key_value(query.data(), valueParamName.data(), valueBufEncoded, 256); result != ESP_OK) + { + if (result == ESP_ERR_NOT_FOUND) + { + const auto msg = fmt::format("{} not set", valueParamName); + ESP_LOGW(TAG, "%.*s", msg.size(), msg.data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); + } + else + { + const auto msg = fmt::format("httpd_query_key_value() {} failed with {}", valueParamName, esp_err_to_name(result)); + ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); + } + } + + char valueBuf[257]; + esphttpdutils::urldecode(valueBuf, valueBufEncoded); + + std::string_view value{valueBuf}; + + if (auto parsed = cpputils::fromString(value)) + { + newValue = *parsed; + } + else + { + const auto msg = fmt::format("could not parse {} {}", valueParamName, value); + ESP_LOGW(TAG, "%.*s", msg.size(), msg.data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); + } + } + + if (!espgui::currentDisplay) + { + constexpr const std::string_view msg = "espgui::currentDisplay is null"; + ESP_LOGW(TAG, "%.*s", msg.size(), msg.data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); + } + + auto *changeValueDisplay = espgui::currentDisplay->asChangeValueDisplayInterface(); + if (!changeValueDisplay) + { + constexpr const std::string_view msg = "espgui::currentDisplay is not a change value display"; + ESP_LOGW(TAG, "%.*s", msg.size(), msg.data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); + } + + changeValueDisplay->setShownValue(newValue); + + CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Location", "/") + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::TemporaryRedirect, "text/html", "Ok, continue at /") +} +#endif diff --git a/main/webserver_displaycontrol.h b/main/webserver_displaycontrol.h index 3721d7d..2fd9d72 100644 --- a/main/webserver_displaycontrol.h +++ b/main/webserver_displaycontrol.h @@ -25,397 +25,8 @@ #include "webserver_lock.h" #ifdef FEATURE_WEBSERVER -namespace { esp_err_t webserver_root_handler(httpd_req_t *req); esp_err_t webserver_triggerButton_handler(httpd_req_t *req); esp_err_t webserver_triggerItem_handler(httpd_req_t *req); esp_err_t webserver_setValue_handler(httpd_req_t *req); -} // namespace - -using esphttpdutils::HtmlTag; - -namespace { -esp_err_t webserver_root_handler(httpd_req_t *req) -{ - espcpputils::LockHelper helper{webserver_lock->handle, std::chrono::ceil(5s).count()}; - if (!helper.locked()) - { - constexpr const std::string_view msg = "could not lock webserver_lock"; - ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); - } - - std::string body; - - { - HtmlTag htmlTag{"html", body}; - - { - HtmlTag headTag{"head", body}; - - { - HtmlTag titleTag{"title", body}; - body += "Display control"; - } - - body += ""; - } - - { - HtmlTag bodyTag{"body", body}; - - { - HtmlTag h1Tag{"h1", body}; - body += "Display control"; - } - - { - HtmlTag pTag{"p", body}; - body += "Display control - " -#ifdef FEATURE_OTA - "Update - " -#endif - - "Settings - " - "String Settings - " - "Dump NVS"; - } - - { - HtmlTag pTag{"p", body}; - body += "Up " - "Down " - "Confirm " - "Back " - "Profile0 " - "Profile1 " - "Profile2 " - "Profile3 "; - } - - if (auto currentDisplay = static_cast(espgui::currentDisplay.get())) - { - if (const auto *textInterface = currentDisplay->asTextInterface()) - { - HtmlTag h2Tag{"h2", body}; - body += esphttpdutils::htmlentities(textInterface->text()); - } - - if (const auto *menuDisplay = currentDisplay->asMenuDisplay()) - { - HtmlTag ulTag{"ul", body}; - - int i{0}; - menuDisplay->runForEveryMenuItem([&,selectedIndex=menuDisplay->selectedIndex()](const espgui::MenuItem &menuItem){ - HtmlTag liTag = i == selectedIndex ? - HtmlTag{"li", "style=\"border: 1px solid black;\"", body} : - HtmlTag{"li", body}; - - body += fmt::format("{}", i, esphttpdutils::htmlentities(menuItem.text())); - i++; - }); - } - else if (const auto *changeValueDisplay = currentDisplay->asChangeValueDisplayInterface()) - { - HtmlTag formTag{"form", "action=\"/setValue\" method=\"GET\"", body}; - body += fmt::format("", changeValueDisplay->shownValue()); - body += ""; - } - else - { - body += "No web control implemented for current display."; - } - } - else - { - body += "Currently no screen instantiated."; - } - } - } - - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::Ok, "text/html", body) -} - -esp_err_t webserver_triggerButton_handler(httpd_req_t *req) -{ - espcpputils::LockHelper helper{webserver_lock->handle, std::chrono::ceil(5s).count()}; - if (!helper.locked()) - { - constexpr const std::string_view msg = "could not lock webserver_lock"; - ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); - } - - std::string query; - if (auto result = esphttpdutils::webserver_get_query(req)) - query = *result; - else - { - ESP_LOGE(TAG, "%.*s", result.error().size(), result.error().data()); - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", result.error()); - } - - std::string button; - constexpr const std::string_view buttonParamName{"button"}; - - { - char valueBufEncoded[256]; - if (const auto result = httpd_query_key_value(query.data(), buttonParamName.data(), valueBufEncoded, 256); result != ESP_OK) - { - if (result == ESP_ERR_NOT_FOUND) - { - const auto msg = fmt::format("{} not set", buttonParamName); - ESP_LOGW(TAG, "%.*s", msg.size(), msg.data()); - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); - } - else - { - const auto msg = fmt::format("httpd_query_key_value() {} failed with {}", buttonParamName, esp_err_to_name(result)); - ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); - } - } - - char valueBuf[257]; - esphttpdutils::urldecode(valueBuf, valueBufEncoded); - - button = valueBuf; - } - - if (button == "up") - { - InputDispatcher::rotate(-1); - - CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Location", "/") - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::TemporaryRedirect, "text/html", "Ok, continue at /") - } - else if (button == "down") - { - InputDispatcher::rotate(1); - - CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Location", "/") - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::TemporaryRedirect, "text/html", "Ok, continue at /") - } - else if (button == "confirm") - { - InputDispatcher::confirmButton(true); - InputDispatcher::confirmButton(false); - - CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Location", "/") - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::TemporaryRedirect, "text/html", "Ok, continue at /") - } - else if (button == "back") - { - InputDispatcher::backButton(true); - InputDispatcher::backButton(false); - - CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Location", "/") - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::TemporaryRedirect, "text/html", "Ok, continue at /") - } - else if (button == "profile0") - { - InputDispatcher::profileButton(0, true); - InputDispatcher::profileButton(0, false); - - CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Location", "/") - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::TemporaryRedirect, "text/html", "Ok, continue at /") - } - else if (button == "profile1") - { - InputDispatcher::profileButton(1, true); - InputDispatcher::profileButton(1, false); - - CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Location", "/") - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::TemporaryRedirect, "text/html", "Ok, continue at /") - } - else if (button == "profile2") - { - InputDispatcher::profileButton(2, true); - InputDispatcher::profileButton(2, false); - - CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Location", "/") - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::TemporaryRedirect, "text/html", "Ok, continue at /") - } - else if (button == "profile3") - { - InputDispatcher::profileButton(3, true); - InputDispatcher::profileButton(3, false); - - CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Location", "/") - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::TemporaryRedirect, "text/html", "Ok, continue at /") - } - else - { - const auto msg = fmt::format("invalid {} {}", buttonParamName, button); - ESP_LOGW(TAG, "%.*s", msg.size(), msg.data()); - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); - } -} - -esp_err_t webserver_triggerItem_handler(httpd_req_t *req) -{ - espcpputils::LockHelper helper{webserver_lock->handle, std::chrono::ceil(5s).count()}; - if (!helper.locked()) - { - constexpr const std::string_view msg = "could not lock webserver_lock"; - ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); - } - - std::string query; - if (auto result = esphttpdutils::webserver_get_query(req)) - query = *result; - else - { - ESP_LOGE(TAG, "%.*s", result.error().size(), result.error().data()); - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", result.error()); - } - - std::size_t index; - - constexpr const std::string_view indexParamName{"index"}; - - { - char valueBufEncoded[256]; - if (const auto result = httpd_query_key_value(query.data(), indexParamName.data(), valueBufEncoded, 256); result != ESP_OK) - { - if (result == ESP_ERR_NOT_FOUND) - { - const auto msg = fmt::format("{} not set", indexParamName); - ESP_LOGW(TAG, "%.*s", msg.size(), msg.data()); - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); - } - else - { - const auto msg = fmt::format("httpd_query_key_value() {} failed with {}", indexParamName, esp_err_to_name(result)); - ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); - } - } - - char valueBuf[257]; - esphttpdutils::urldecode(valueBuf, valueBufEncoded); - - std::string_view value{valueBuf}; - - if (auto parsed = cpputils::fromString(value)) - { - index = *parsed; - } - else - { - const auto msg = fmt::format("could not parse {} {}", indexParamName, value); - ESP_LOGW(TAG, "%.*s", msg.size(), msg.data()); - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); - } - } - - if (!espgui::currentDisplay) - { - constexpr const std::string_view msg = "espgui::currentDisplay is null"; - ESP_LOGW(TAG, "%.*s", msg.size(), msg.data()); - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); - } - - auto *menuDisplay = espgui::currentDisplay->asMenuDisplay(); - if (!menuDisplay) - { - constexpr const std::string_view msg = "espgui::currentDisplay is not a menu display"; - ESP_LOGW(TAG, "%.*s", msg.size(), msg.data()); - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); - } - - if (/*index < 0 ||*/ index >= menuDisplay->menuItemCount()) - { - const auto msg = fmt::format("{} {} out of range (must be smaller than {})", indexParamName, index, menuDisplay->menuItemCount()); - ESP_LOGW(TAG, "%.*s", msg.size(), msg.data()); - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); - } - - menuDisplay->getMenuItem(index).triggered(); - - CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Location", "/") - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::TemporaryRedirect, "text/html", "Ok, continue at /") -} - -esp_err_t webserver_setValue_handler(httpd_req_t *req) -{ - espcpputils::LockHelper helper{webserver_lock->handle, std::chrono::ceil(5s).count()}; - if (!helper.locked()) - { - constexpr const std::string_view msg = "could not lock webserver_lock"; - ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); - } - - std::string query; - if (auto result = esphttpdutils::webserver_get_query(req)) - query = *result; - else - { - ESP_LOGE(TAG, "%.*s", result.error().size(), result.error().data()); - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", result.error()); - } - - int newValue; - - constexpr const std::string_view valueParamName{"value"}; - - { - char valueBufEncoded[256]; - if (const auto result = httpd_query_key_value(query.data(), valueParamName.data(), valueBufEncoded, 256); result != ESP_OK) - { - if (result == ESP_ERR_NOT_FOUND) - { - const auto msg = fmt::format("{} not set", valueParamName); - ESP_LOGW(TAG, "%.*s", msg.size(), msg.data()); - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); - } - else - { - const auto msg = fmt::format("httpd_query_key_value() {} failed with {}", valueParamName, esp_err_to_name(result)); - ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); - } - } - - char valueBuf[257]; - esphttpdutils::urldecode(valueBuf, valueBufEncoded); - - std::string_view value{valueBuf}; - - if (auto parsed = cpputils::fromString(value)) - { - newValue = *parsed; - } - else - { - const auto msg = fmt::format("could not parse {} {}", valueParamName, value); - ESP_LOGW(TAG, "%.*s", msg.size(), msg.data()); - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); - } - } - - if (!espgui::currentDisplay) - { - constexpr const std::string_view msg = "espgui::currentDisplay is null"; - ESP_LOGW(TAG, "%.*s", msg.size(), msg.data()); - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); - } - - auto *changeValueDisplay = espgui::currentDisplay->asChangeValueDisplayInterface(); - if (!changeValueDisplay) - { - constexpr const std::string_view msg = "espgui::currentDisplay is not a change value display"; - ESP_LOGW(TAG, "%.*s", msg.size(), msg.data()); - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); - } - - changeValueDisplay->setShownValue(newValue); - - CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Location", "/") - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::TemporaryRedirect, "text/html", "Ok, continue at /") -} -} // namespace - #endif diff --git a/main/webserver_dumpnvs.cpp b/main/webserver_dumpnvs.cpp index e69de29..1fe4afb 100644 --- a/main/webserver_dumpnvs.cpp +++ b/main/webserver_dumpnvs.cpp @@ -0,0 +1,171 @@ +#include "webserver_dumpnvs.h" + +using esphttpdutils::HtmlTag; +using namespace espchrono; + +namespace { +constexpr const char * const TAG = "BOBBYWEB"; +} // namespace + +template +typename std::enable_if< + !std::is_same::value && + !std::is_integral::value && + !std::is_same>::value && + !std::is_same::value && + !std::is_same::value && + !std::is_same::value && + !std::is_same::value +#if defined(FEATURE_LEDSTRIP) && defined(FEATURE_OTA) + && !std::is_same::value +#endif + , bool>::type +showInputForSetting(std::string_view key, T value, JsonObject &body) +{ + body[key] = nullptr; + return false; +} + +template +typename std::enable_if< + std::is_same::value + , bool>::type +showInputForSetting(std::string_view key, T value, JsonObject &body) +{ + body[key] = value; + return true; +} + +template +typename std::enable_if< + !std::is_same::value && + std::is_integral::value + , bool>::type +showInputForSetting(std::string_view key, T value, JsonObject &body) +{ + body[key] = value; + return true; +} + +template +typename std::enable_if< + std::is_same>::value + , bool>::type +showInputForSetting(std::string_view key, T value, JsonObject &body) +{ + std::string array_str = fmt::format("{}{}{}{}", value[0], value[1], value[2], value[3]); + body[key] = array_str; + return true; +} + +template +typename std::enable_if< + std::is_same::value + , bool>::type +showInputForSetting(std::string_view key, T value, JsonObject &body) +{ + body[key] = value; + return true; +} + +template +typename std::enable_if< + std::is_same::value + , bool>::type +showInputForSetting(std::string_view key, T value, JsonObject &body) +{ + body[key] = value.count(); + return true; +} + +template +typename std::enable_if< + std::is_same::value + , bool>::type +showInputForSetting(std::string_view key, T value, JsonObject &body) +{ + body[key] = toString(espchrono::DayLightSavingMode(value)); + return true; +} + +template +typename std::enable_if< + std::is_same::value + , bool>::type +showInputForSetting(std::string_view key, T value, JsonObject &body) +{ + body[key] = int(value); + return true; +} + +#if defined(FEATURE_LEDSTRIP) && defined(FEATURE_OTA) + +template +typename std::enable_if< + std::is_same::value + , bool>::type +showInputForSetting(std::string_view key, T value, JsonObject &body) +{ + body[key] = int(value); + return true; +} +#endif + +esp_err_t webserver_dump_nvs_handler(httpd_req_t *req) +{ + espcpputils::LockHelper helper{webserver_lock->handle, std::chrono::ceil(5s).count()}; + if (!helper.locked()) + { + constexpr const std::string_view msg = "could not lock webserver_lock"; + ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); + } + + DynamicJsonDocument doc(6144); + const auto profile = settingsPersister.currentlyOpenProfileIndex(); + const auto switchBackProfile = profile ? int(*profile) : 0; + + JsonObject json_settings = doc.createNestedObject("settings"); + settings.executeForEveryCommonSetting([&](std::string_view key, const auto &value){ + showInputForSetting(key, value, json_settings); + }); + + JsonObject json_stringSettings = doc.createNestedObject("stringSettings"); + stringSettings.executeForEveryCommonSetting([&](std::string_view key, const auto &value){ + showInputForSetting(key, value, json_stringSettings); + }); + + JsonObject profiles = doc.createNestedObject("profiles"); + + // Profile settings + for (uint8_t profile_num = 0; profile_num < 4; profile_num++) { + +#ifdef SIMPLIFIED_TRIGGER_TRIGGERONPRESET + if (profile_num == SIMPLIFIED_TRIGGER_TRIGGERONPRESET) { + continue; + } +#endif + switchProfile(profile_num); + + const auto cur_profile = settingsPersister.currentlyOpenProfileIndex(); + const auto profile_str = cur_profile ? std::to_string(*cur_profile) : "-"; + + JsonObject profile = profiles.createNestedObject(profile_str); + JsonObject profile_stringSettings = profile.createNestedObject("stringSettings"); + JsonObject profile_settings = profile.createNestedObject("settings"); + + stringSettings.executeForEveryProfileSetting([&](const char *key, auto &value){ + showInputForSetting(key, value, profile_stringSettings); + }); + + settings.executeForEveryProfileSetting([&](const char *key, auto &value){ + showInputForSetting(key, value, profile_settings); + }); + } + + switchProfile(switchBackProfile); + + std::string body; + serializeJson(doc, body); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::Ok, "application/json", body) +} diff --git a/main/webserver_dumpnvs.h b/main/webserver_dumpnvs.h index 2f00540..c8306aa 100644 --- a/main/webserver_dumpnvs.h +++ b/main/webserver_dumpnvs.h @@ -22,178 +22,6 @@ #include "settingsutils.h" #ifdef FEATURE_WEBSERVER -namespace dump_nvs_handler { esp_err_t webserver_dump_nvs_handler(httpd_req_t *req); -} // namespace - -using esphttpdutils::HtmlTag; -using namespace espchrono; - -namespace dump_nvs_handler { - -template -typename std::enable_if< - !std::is_same::value && - !std::is_integral::value && - !std::is_same>::value && - !std::is_same::value && - !std::is_same::value && - !std::is_same::value && - !std::is_same::value -#if defined(FEATURE_LEDSTRIP) && defined(FEATURE_OTA) - && !std::is_same::value -#endif -, bool>::type -showInputForSetting(std::string_view key, T value, JsonObject &body) -{ - body[key] = nullptr; - return false; -} - -template -typename std::enable_if< - std::is_same::value -, bool>::type -showInputForSetting(std::string_view key, T value, JsonObject &body) -{ - body[key] = value; - return true; -} - -template -typename std::enable_if< - !std::is_same::value && - std::is_integral::value -, bool>::type -showInputForSetting(std::string_view key, T value, JsonObject &body) -{ - body[key] = value; - return true; -} - -template -typename std::enable_if< - std::is_same>::value -, bool>::type -showInputForSetting(std::string_view key, T value, JsonObject &body) -{ - std::string array_str = fmt::format("{}{}{}{}", value[0], value[1], value[2], value[3]); - body[key] = array_str; - return true; -} - -template -typename std::enable_if< - std::is_same::value -, bool>::type -showInputForSetting(std::string_view key, T value, JsonObject &body) -{ - body[key] = value; - return true; -} - -template -typename std::enable_if< - std::is_same::value -, bool>::type -showInputForSetting(std::string_view key, T value, JsonObject &body) -{ - body[key] = value.count(); - return true; -} - -template -typename std::enable_if< - std::is_same::value -, bool>::type -showInputForSetting(std::string_view key, T value, JsonObject &body) -{ - body[key] = toString(espchrono::DayLightSavingMode(value)); - return true; -} - -template -typename std::enable_if< - std::is_same::value -, bool>::type -showInputForSetting(std::string_view key, T value, JsonObject &body) -{ - body[key] = int(value); - return true; -} - -#if defined(FEATURE_LEDSTRIP) && defined(FEATURE_OTA) - -template -typename std::enable_if< - std::is_same::value -, bool>::type -showInputForSetting(std::string_view key, T value, JsonObject &body) -{ - body[key] = int(value); - return true; -} -#endif - -esp_err_t webserver_dump_nvs_handler(httpd_req_t *req) -{ - espcpputils::LockHelper helper{webserver_lock->handle, std::chrono::ceil(5s).count()}; - if (!helper.locked()) - { - constexpr const std::string_view msg = "could not lock webserver_lock"; - ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); - } - - DynamicJsonDocument doc(6144); - const auto profile = settingsPersister.currentlyOpenProfileIndex(); - const auto switchBackProfile = profile ? int(*profile) : 0; - - JsonObject json_settings = doc.createNestedObject("settings"); - settings.executeForEveryCommonSetting([&](std::string_view key, const auto &value){ - showInputForSetting(key, value, json_settings); - }); - - JsonObject json_stringSettings = doc.createNestedObject("stringSettings"); - stringSettings.executeForEveryCommonSetting([&](std::string_view key, const auto &value){ - showInputForSetting(key, value, json_stringSettings); - }); - - JsonObject profiles = doc.createNestedObject("profiles"); - - // Profile settings - for (uint8_t profile_num = 0; profile_num < 4; profile_num++) { - -#ifdef SIMPLIFIED_TRIGGER_TRIGGERONPRESET - if (profile_num == SIMPLIFIED_TRIGGER_TRIGGERONPRESET) { - continue; - } -#endif - switchProfile(profile_num); - - const auto cur_profile = settingsPersister.currentlyOpenProfileIndex(); - const auto profile_str = cur_profile ? std::to_string(*cur_profile) : "-"; - - JsonObject profile = profiles.createNestedObject(profile_str); - JsonObject profile_stringSettings = profile.createNestedObject("stringSettings"); - JsonObject profile_settings = profile.createNestedObject("settings"); - - stringSettings.executeForEveryProfileSetting([&](const char *key, auto &value){ - showInputForSetting(key, value, profile_stringSettings); - }); - - settings.executeForEveryProfileSetting([&](const char *key, auto &value){ - showInputForSetting(key, value, profile_settings); - }); - } - - switchProfile(switchBackProfile); - - std::string body; - serializeJson(doc, body); - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::Ok, "application/json", body) -} -} // namespace - #endif diff --git a/main/webserver_lock.cpp b/main/webserver_lock.cpp index e69de29..ae0dbe1 100644 --- a/main/webserver_lock.cpp +++ b/main/webserver_lock.cpp @@ -0,0 +1,3 @@ +#include "webserver_lock.h" + +cpputils::DelayedConstruction webserver_lock; diff --git a/main/webserver_lock.h b/main/webserver_lock.h index fa5a494..ddb94b5 100644 --- a/main/webserver_lock.h +++ b/main/webserver_lock.h @@ -4,8 +4,4 @@ #include #include -namespace { - -cpputils::DelayedConstruction webserver_lock; - -} // namespace +extern cpputils::DelayedConstruction webserver_lock; diff --git a/main/webserver_ota.cpp b/main/webserver_ota.cpp index e69de29..5e1f5d7 100644 --- a/main/webserver_ota.cpp +++ b/main/webserver_ota.cpp @@ -0,0 +1,258 @@ +#include "webserver_ota.h" + +// local includes +#include "globals.h" + +#if defined(FEATURE_WEBSERVER) && defined(FEATURE_OTA) +using namespace std::chrono_literals; +using esphttpdutils::HtmlTag; + +namespace { +constexpr const char * const TAG = "BOBBYWEB"; +} // namespace + +esp_err_t webserver_ota_handler(httpd_req_t *req) +{ + espcpputils::LockHelper helper{webserver_lock->handle, std::chrono::ceil(5s).count()}; + if (!helper.locked()) + { + constexpr const std::string_view msg = "could not lock webserver_lock"; + ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); + } + + std::string body; + + { + HtmlTag htmlTag{"html", body}; + + { + HtmlTag headTag{"head", body}; + + { + HtmlTag titleTag{"title", body}; + body += "Update"; + } + + body += ""; + } + + { + HtmlTag bodyTag{"body", body}; + + { + HtmlTag h1Tag{"h1", body}; + body += "Update"; + } + + { + HtmlTag pTag{"p", body}; + body += "Display control - " + "Update - " + "Settings - " + "String Settings - " + "Dump NVS"; + } + + if (const esp_app_desc_t *app_desc = esp_ota_get_app_description()) + { + HtmlTag tableTag{"table", "border=\"1\"", body}; + + { + HtmlTag trTag{"tr", body}; + { HtmlTag tdTag{"td", body}; body += "Current project_name"; } + { HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(app_desc->project_name); } + } + + { + HtmlTag trTag{"tr", body}; + { HtmlTag tdTag{"td", body}; body += "Current version"; } + { HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(app_desc->version); } + } + + { + HtmlTag trTag{"tr", body}; + { HtmlTag tdTag{"td", body}; body += "Current secure_version"; } + { HtmlTag tdTag{"td", body}; body += std::to_string(app_desc->secure_version); } + } + + { + HtmlTag trTag{"tr", body}; + { HtmlTag tdTag{"td", body}; body += "Current timestamp"; } + { HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(fmt::format("{} {}", app_desc->date, app_desc->time)); } + } + + { + HtmlTag trTag{"tr", body}; + { HtmlTag tdTag{"td", body}; body += "Current idf_ver"; } + { HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(app_desc->idf_ver); } + } + + { + HtmlTag trTag{"tr", body}; + { HtmlTag tdTag{"td", body}; body += "Current sha256"; } + { HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(espcpputils::toHexString({app_desc->app_elf_sha256, 8})); } + } + } + else + { + constexpr const std::string_view msg = "esp_ota_get_app_description() failed"; + ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); + HtmlTag pTag{"p", "style=\"color: red;\"", body}; + body += esphttpdutils::htmlentities(msg); + } + + if (asyncOta) + { + HtmlTag tableTag{"table", "border=\"1\"", body}; + + { + HtmlTag trTag{"tr", body}; + { HtmlTag tdTag{"td", body}; body += "Update status"; } + { HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(toString(asyncOta->status())); } + } + + { + HtmlTag trTag{"tr", body}; + { HtmlTag tdTag{"td", body}; body += "Update progress"; } + { + HtmlTag tdTag{"td", body}; + const auto progress = asyncOta->progress(); + const auto totalSize = asyncOta->totalSize(); + body += fmt::format("{} / {}{}", + progress, + totalSize ? std::to_string(*totalSize) : "?", + (totalSize && *totalSize > 0) ? fmt::format(" ({:.02f}%)", float(progress) / *totalSize * 100) : ""); + } + } + + { + HtmlTag trTag{"tr", body}; + { HtmlTag tdTag{"td", body}; body += "Update message"; } + { HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(asyncOta->message()); } + } + + if (const auto &appDesc = asyncOta->appDesc()) + { + { + HtmlTag trTag{"tr", body}; + { HtmlTag tdTag{"td", body}; body += "New project_name"; } + { HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(appDesc->project_name); } + } + + { + HtmlTag trTag{"tr", body}; + { HtmlTag tdTag{"td", body}; body += "New version"; } + { HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(appDesc->version); } + } + + { + HtmlTag trTag{"tr", body}; + { HtmlTag tdTag{"td", body}; body += "New secure_version"; } + { HtmlTag tdTag{"td", body}; body += std::to_string(appDesc->secure_version); } + } + + { + HtmlTag trTag{"tr", body}; + { HtmlTag tdTag{"td", body}; body += "New timestamp"; } + { HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(fmt::format("{} {}", appDesc->date, appDesc->time)); } + } + + { + HtmlTag trTag{"tr", body}; + { HtmlTag tdTag{"td", body}; body += "New idf_ver"; } + { HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(appDesc->idf_ver); } + } + + { + HtmlTag trTag{"tr", body}; + { HtmlTag tdTag{"td", body}; body += "New sha256"; } + { HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(espcpputils::toHexString({appDesc->app_elf_sha256, 8})); } + } + } + } + else + { + HtmlTag pTag{"p", body}; + body += "Updater is sleeping (not constructed)"; + } + + { + HtmlTag formTag{"form", "action=\"/triggerOta\" method=\"GET\"", body}; + HtmlTag fieldsetTag{"fieldset", body}; + { + HtmlTag legendTag{"legend", body}; + body += "Trigger Update"; + } + + body += fmt::format("", esphttpdutils::htmlentities(stringSettings.otaUrl)); + + { + HtmlTag buttonTag{"button", "type=\"submit\"", body}; + body += "Go"; + } + + body += "url is only used temporarely and not persisted in flash"; + } + } + } + + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::Ok, "text/html", body) +} + +esp_err_t webserver_trigger_ota_handler(httpd_req_t *req) +{ + espcpputils::LockHelper helper{webserver_lock->handle, std::chrono::ceil(5s).count()}; + if (!helper.locked()) + { + constexpr const std::string_view msg = "could not lock webserver_lock"; + ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); + } + + std::string query; + if (auto result = esphttpdutils::webserver_get_query(req)) + query = *result; + else + { + ESP_LOGE(TAG, "%.*s", result.error().size(), result.error().data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", result.error()); + } + + std::string url; + constexpr const std::string_view urlParamName{"url"}; + + { + char valueBufEncoded[256]; + if (const auto result = httpd_query_key_value(query.data(), urlParamName.data(), valueBufEncoded, 256); result != ESP_OK) + { + if (result == ESP_ERR_NOT_FOUND) + { + const auto msg = fmt::format("{} not set", urlParamName); + ESP_LOGW(TAG, "%.*s", msg.size(), msg.data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); + } + else + { + const auto msg = fmt::format("httpd_query_key_value() {} failed with {}", urlParamName, esp_err_to_name(result)); + ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); + } + } + + char valueBuf[257]; + esphttpdutils::urldecode(valueBuf, valueBufEncoded); + + url = valueBuf; + } + + if (const auto result = triggerOta(url); !result) + { + ESP_LOGE(TAG, "%.*s", result.error().size(), result.error().data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", result.error()); + } + + CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Location", "/ota") + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::TemporaryRedirect, "text/html", "Ok, continue at /") +} +#endif diff --git a/main/webserver_ota.h b/main/webserver_ota.h index 5ea061e..cef270b 100644 --- a/main/webserver_ota.h +++ b/main/webserver_ota.h @@ -23,257 +23,6 @@ #include "webserver_lock.h" #if defined(FEATURE_WEBSERVER) && defined(FEATURE_OTA) -namespace { esp_err_t webserver_ota_handler(httpd_req_t *req); esp_err_t webserver_trigger_ota_handler(httpd_req_t *req); -} // namespace - -using esphttpdutils::HtmlTag; - -namespace { -esp_err_t webserver_ota_handler(httpd_req_t *req) -{ - espcpputils::LockHelper helper{webserver_lock->handle, std::chrono::ceil(5s).count()}; - if (!helper.locked()) - { - constexpr const std::string_view msg = "could not lock webserver_lock"; - ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); - } - - std::string body; - - { - HtmlTag htmlTag{"html", body}; - - { - HtmlTag headTag{"head", body}; - - { - HtmlTag titleTag{"title", body}; - body += "Update"; - } - - body += ""; - } - - { - HtmlTag bodyTag{"body", body}; - - { - HtmlTag h1Tag{"h1", body}; - body += "Update"; - } - - { - HtmlTag pTag{"p", body}; - body += "Display control - " - "Update - " - "Settings - " - "String Settings - " - "Dump NVS"; - } - - if (const esp_app_desc_t *app_desc = esp_ota_get_app_description()) - { - HtmlTag tableTag{"table", "border=\"1\"", body}; - - { - HtmlTag trTag{"tr", body}; - { HtmlTag tdTag{"td", body}; body += "Current project_name"; } - { HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(app_desc->project_name); } - } - - { - HtmlTag trTag{"tr", body}; - { HtmlTag tdTag{"td", body}; body += "Current version"; } - { HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(app_desc->version); } - } - - { - HtmlTag trTag{"tr", body}; - { HtmlTag tdTag{"td", body}; body += "Current secure_version"; } - { HtmlTag tdTag{"td", body}; body += std::to_string(app_desc->secure_version); } - } - - { - HtmlTag trTag{"tr", body}; - { HtmlTag tdTag{"td", body}; body += "Current timestamp"; } - { HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(fmt::format("{} {}", app_desc->date, app_desc->time)); } - } - - { - HtmlTag trTag{"tr", body}; - { HtmlTag tdTag{"td", body}; body += "Current idf_ver"; } - { HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(app_desc->idf_ver); } - } - - { - HtmlTag trTag{"tr", body}; - { HtmlTag tdTag{"td", body}; body += "Current sha256"; } - { HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(espcpputils::toHexString({app_desc->app_elf_sha256, 8})); } - } - } - else - { - constexpr const std::string_view msg = "esp_ota_get_app_description() failed"; - ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); - HtmlTag pTag{"p", "style=\"color: red;\"", body}; - body += esphttpdutils::htmlentities(msg); - } - - if (asyncOta) - { - HtmlTag tableTag{"table", "border=\"1\"", body}; - - { - HtmlTag trTag{"tr", body}; - { HtmlTag tdTag{"td", body}; body += "Update status"; } - { HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(toString(asyncOta->status())); } - } - - { - HtmlTag trTag{"tr", body}; - { HtmlTag tdTag{"td", body}; body += "Update progress"; } - { - HtmlTag tdTag{"td", body}; - const auto progress = asyncOta->progress(); - const auto totalSize = asyncOta->totalSize(); - body += fmt::format("{} / {}{}", - progress, - totalSize ? std::to_string(*totalSize) : "?", - (totalSize && *totalSize > 0) ? fmt::format(" ({:.02f}%)", float(progress) / *totalSize * 100) : ""); - } - } - - { - HtmlTag trTag{"tr", body}; - { HtmlTag tdTag{"td", body}; body += "Update message"; } - { HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(asyncOta->message()); } - } - - if (const auto &appDesc = asyncOta->appDesc()) - { - { - HtmlTag trTag{"tr", body}; - { HtmlTag tdTag{"td", body}; body += "New project_name"; } - { HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(appDesc->project_name); } - } - - { - HtmlTag trTag{"tr", body}; - { HtmlTag tdTag{"td", body}; body += "New version"; } - { HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(appDesc->version); } - } - - { - HtmlTag trTag{"tr", body}; - { HtmlTag tdTag{"td", body}; body += "New secure_version"; } - { HtmlTag tdTag{"td", body}; body += std::to_string(appDesc->secure_version); } - } - - { - HtmlTag trTag{"tr", body}; - { HtmlTag tdTag{"td", body}; body += "New timestamp"; } - { HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(fmt::format("{} {}", appDesc->date, appDesc->time)); } - } - - { - HtmlTag trTag{"tr", body}; - { HtmlTag tdTag{"td", body}; body += "New idf_ver"; } - { HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(appDesc->idf_ver); } - } - - { - HtmlTag trTag{"tr", body}; - { HtmlTag tdTag{"td", body}; body += "New sha256"; } - { HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(espcpputils::toHexString({appDesc->app_elf_sha256, 8})); } - } - } - } - else - { - HtmlTag pTag{"p", body}; - body += "Updater is sleeping (not constructed)"; - } - - { - HtmlTag formTag{"form", "action=\"/triggerOta\" method=\"GET\"", body}; - HtmlTag fieldsetTag{"fieldset", body}; - { - HtmlTag legendTag{"legend", body}; - body += "Trigger Update"; - } - - body += fmt::format("", esphttpdutils::htmlentities(stringSettings.otaUrl)); - - { - HtmlTag buttonTag{"button", "type=\"submit\"", body}; - body += "Go"; - } - - body += "url is only used temporarely and not persisted in flash"; - } - } - } - - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::Ok, "text/html", body) -} - -esp_err_t webserver_trigger_ota_handler(httpd_req_t *req) -{ - espcpputils::LockHelper helper{webserver_lock->handle, std::chrono::ceil(5s).count()}; - if (!helper.locked()) - { - constexpr const std::string_view msg = "could not lock webserver_lock"; - ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); - } - - std::string query; - if (auto result = esphttpdutils::webserver_get_query(req)) - query = *result; - else - { - ESP_LOGE(TAG, "%.*s", result.error().size(), result.error().data()); - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", result.error()); - } - - std::string url; - constexpr const std::string_view urlParamName{"url"}; - - { - char valueBufEncoded[256]; - if (const auto result = httpd_query_key_value(query.data(), urlParamName.data(), valueBufEncoded, 256); result != ESP_OK) - { - if (result == ESP_ERR_NOT_FOUND) - { - const auto msg = fmt::format("{} not set", urlParamName); - ESP_LOGW(TAG, "%.*s", msg.size(), msg.data()); - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); - } - else - { - const auto msg = fmt::format("httpd_query_key_value() {} failed with {}", urlParamName, esp_err_to_name(result)); - ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); - } - } - - char valueBuf[257]; - esphttpdutils::urldecode(valueBuf, valueBufEncoded); - - url = valueBuf; - } - - if (const auto result = triggerOta(url); !result) - { - ESP_LOGE(TAG, "%.*s", result.error().size(), result.error().data()); - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", result.error()); - } - - CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Location", "/ota") - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::TemporaryRedirect, "text/html", "Ok, continue at /") -} -} #endif diff --git a/main/webserver_settings.cpp b/main/webserver_settings.cpp index e69de29..09d6668 100644 --- a/main/webserver_settings.cpp +++ b/main/webserver_settings.cpp @@ -0,0 +1,301 @@ +#include "webserver_settings.h" + +#ifdef FEATURE_WEBSERVER +using namespace std::chrono_literals; +using esphttpdutils::HtmlTag; + +namespace { +constexpr const char * const TAG = "BOBBYWEB"; +} // namespace + +template +typename std::enable_if< + !std::is_same::value && + !std::is_integral::value && + !std::is_same>::value + , bool>::type +showInputForSetting(std::string_view key, T value, std::string &body) +{ + HtmlTag spanTag{"span", "style=\"color: red;\"", body}; + body += "Unsupported config type"; + return false; +} + +template +typename std::enable_if< + std::is_same::value + , bool>::type +showInputForSetting(std::string_view key, T value, std::string &body) +{ + body += fmt::format("" + "", + esphttpdutils::htmlentities(key), + value ? "checked " : "", + esphttpdutils::htmlentities(key)); + return true; +} + +template +typename std::enable_if< + !std::is_same::value && + std::is_integral::value + , bool>::type +showInputForSetting(std::string_view key, T value, std::string &body) +{ + body += fmt::format("", + esphttpdutils::htmlentities(key), + value, + std::numeric_limits::min(), + std::numeric_limits::max()); + return true; +} + +template +typename std::enable_if< + std::is_same>::value + , bool>::type +showInputForSetting(std::string_view key, T value, std::string &body) +{ + body += fmt::format("", + esphttpdutils::htmlentities(key), + value[0], + value[1], + value[2], + value[3]); + return true; +} + +esp_err_t webserver_settings_handler(httpd_req_t *req) +{ + espcpputils::LockHelper helper{webserver_lock->handle, std::chrono::ceil(5s).count()}; + if (!helper.locked()) + { + constexpr const std::string_view msg = "could not lock webserver_lock"; + ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); + } + + std::string body; + + { + HtmlTag htmlTag{"html", body}; + + { + HtmlTag headTag{"head", body}; + + { + HtmlTag titleTag{"title", body}; + body += "Settings"; + } + + body += ""; + + HtmlTag styleTag{"style", "type=\"text/css\"", body}; + body += + ".form-table {" + "display: table;" + "border-collapse: separate;" + "border-spacing: 10px 0;" + "}" + + ".form-table .form-table-row {" + "display: table-row;" + "}" + + ".form-table .form-table-row .form-table-cell {" + "display: table-cell;" + "}"; + } + + { + HtmlTag bodyTag{"body", body}; + + { + HtmlTag h1Tag{"h1", body}; + body += "Settings"; + } + + { + HtmlTag pTag{"p", body}; + body += "Display control - " +#ifdef FEATURE_OTA + "Update - " +#endif + "Settings - " + "String Settings - " + "Dump NVS"; + } + + HtmlTag divTag{"div", "class=\"form-table\"", body}; + settings.executeForEveryCommonSetting([&](std::string_view key, const auto &value){ + HtmlTag formTag{"form", "class=\"form-table-row\" action=\"/saveSettings\" method=\"GET\"", body}; + + { + HtmlTag divTag{"div", "class=\"form-table\"", body}; + HtmlTag bTag{"b", body}; + body += esphttpdutils::htmlentities(key); + } + + { + HtmlTag divTag{"div", "class=\"form-table-cell\"", body}; + showInputForSetting(key, value, body); + } + + { + HtmlTag divTag{"div", "class=\"form-table-cell\"", body}; + HtmlTag buttonTag{"button", "type=\"submit\"", body}; + body += "Save"; + } + }); + } + } + + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::Ok, "text/html", body) +} + +template +typename std::enable_if< + !std::is_same::value && + !std::is_integral::value && + !std::is_same>::value + , bool>::type +saveSetting(T &value, std::string_view newValue, std::string &body) +{ + body += "Unsupported config type"; + return false; +} + +template +typename std::enable_if< + std::is_same::value + , bool>::type +saveSetting(T &value, std::string_view newValue, std::string &body) +{ + if (newValue == "true") + { + value = true; + body += "applied"; + return true; + } + else if (newValue == "false") + { + value = false; + body += "applied"; + return true; + } + else + { + body += fmt::format("only true and false allowed, not {}", newValue); + return false; + } +} + +template +typename std::enable_if< + !std::is_same::value && + std::is_integral::value + , bool>::type +saveSetting(T &value, std::string_view newValue, std::string &body) +{ + if (auto parsed = cpputils::fromString(newValue)) + { + value = *parsed; + body += "applied"; + return true; + } + else + { + body += fmt::format("could not parse {}", newValue); + return false; + } +} + +template +typename std::enable_if< + std::is_same>::value + , bool>::type +saveSetting(T &value, std::string_view newValue, std::string &body) +{ + if (std::array parsed; std::sscanf(newValue.data(), "%1hhi%1hhi%1hhi%1hhi", &parsed[0], &parsed[1], &parsed[2], &parsed[3]) == 4) + { + value = parsed; + body += "applied"; + return true; + } + else + { + body += fmt::format("could not parse {}", newValue); + return false; + } +} + +esp_err_t webserver_saveSettings_handler(httpd_req_t *req) +{ + espcpputils::LockHelper helper{webserver_lock->handle, std::chrono::ceil(5s).count()}; + if (!helper.locked()) + { + constexpr const std::string_view msg = "could not lock webserver_lock"; + ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); + } + + std::string query; + if (auto result = esphttpdutils::webserver_get_query(req)) + query = *result; + else + { + ESP_LOGE(TAG, "%.*s", result.error().size(), result.error().data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", result.error()); + } + + std::string body; + bool success{true}; + + settings.executeForEveryCommonSetting([&](std::string_view key, auto &value){ + char valueBufEncoded[256]; + if (const auto result = httpd_query_key_value(query.data(), key.data(), valueBufEncoded, 256); result != ESP_OK) + { + if (result != ESP_ERR_NOT_FOUND) + { + const auto msg = fmt::format("{}: httpd_query_key_value() failed with {}", key, esp_err_to_name(result)); + ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); + body += msg; + body += '\n'; + success = false; + } + return; + } + + char valueBuf[257]; + esphttpdutils::urldecode(valueBuf, valueBufEncoded); + + body += key; + if (!saveSetting(value, valueBuf, body)) + success = false; + body += '\n'; + }); + + if (body.empty()) + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::Ok, "text/plain", "nothing changed?!") + + if (settingsPersister.save(settings)) + body += "settings persisted successfully"; + else + { + body += "error while persisting settings"; + success = false; + } + + if (success) + { + CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Location", "/settings") + body += "\nOk, continue at /settings"; + } + + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, + req, + success ? esphttpdutils::ResponseStatus::TemporaryRedirect : esphttpdutils::ResponseStatus::BadRequest, + "text/plain", + body) +} +#endif diff --git a/main/webserver_settings.h b/main/webserver_settings.h index 1fa9f24..71647ab 100644 --- a/main/webserver_settings.h +++ b/main/webserver_settings.h @@ -23,304 +23,6 @@ #include "webserver_lock.h" #ifdef FEATURE_WEBSERVER -namespace { esp_err_t webserver_settings_handler(httpd_req_t *req); esp_err_t webserver_saveSettings_handler(httpd_req_t *req); -} // namespace - -using esphttpdutils::HtmlTag; - -namespace { -template -typename std::enable_if< - !std::is_same::value && - !std::is_integral::value && - !std::is_same>::value -, bool>::type -showInputForSetting(std::string_view key, T value, std::string &body) -{ - HtmlTag spanTag{"span", "style=\"color: red;\"", body}; - body += "Unsupported config type"; - return false; -} - -template -typename std::enable_if< - std::is_same::value -, bool>::type -showInputForSetting(std::string_view key, T value, std::string &body) -{ - body += fmt::format("" - "", - esphttpdutils::htmlentities(key), - value ? "checked " : "", - esphttpdutils::htmlentities(key)); - return true; -} - -template -typename std::enable_if< - !std::is_same::value && - std::is_integral::value -, bool>::type -showInputForSetting(std::string_view key, T value, std::string &body) -{ - body += fmt::format("", - esphttpdutils::htmlentities(key), - value, - std::numeric_limits::min(), - std::numeric_limits::max()); - return true; -} - -template -typename std::enable_if< - std::is_same>::value -, bool>::type -showInputForSetting(std::string_view key, T value, std::string &body) -{ - body += fmt::format("", - esphttpdutils::htmlentities(key), - value[0], - value[1], - value[2], - value[3]); - return true; -} - -esp_err_t webserver_settings_handler(httpd_req_t *req) -{ - espcpputils::LockHelper helper{webserver_lock->handle, std::chrono::ceil(5s).count()}; - if (!helper.locked()) - { - constexpr const std::string_view msg = "could not lock webserver_lock"; - ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); - } - - std::string body; - - { - HtmlTag htmlTag{"html", body}; - - { - HtmlTag headTag{"head", body}; - - { - HtmlTag titleTag{"title", body}; - body += "Settings"; - } - - body += ""; - - HtmlTag styleTag{"style", "type=\"text/css\"", body}; - body += - ".form-table {" - "display: table;" - "border-collapse: separate;" - "border-spacing: 10px 0;" - "}" - - ".form-table .form-table-row {" - "display: table-row;" - "}" - - ".form-table .form-table-row .form-table-cell {" - "display: table-cell;" - "}"; - } - - { - HtmlTag bodyTag{"body", body}; - - { - HtmlTag h1Tag{"h1", body}; - body += "Settings"; - } - - { - HtmlTag pTag{"p", body}; - body += "Display control - " -#ifdef FEATURE_OTA - "Update - " -#endif - "Settings - " - "String Settings - " - "Dump NVS"; - } - - HtmlTag divTag{"div", "class=\"form-table\"", body}; - settings.executeForEveryCommonSetting([&](std::string_view key, const auto &value){ - HtmlTag formTag{"form", "class=\"form-table-row\" action=\"/saveSettings\" method=\"GET\"", body}; - - { - HtmlTag divTag{"div", "class=\"form-table\"", body}; - HtmlTag bTag{"b", body}; - body += esphttpdutils::htmlentities(key); - } - - { - HtmlTag divTag{"div", "class=\"form-table-cell\"", body}; - showInputForSetting(key, value, body); - } - - { - HtmlTag divTag{"div", "class=\"form-table-cell\"", body}; - HtmlTag buttonTag{"button", "type=\"submit\"", body}; - body += "Save"; - } - }); - } - } - - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::Ok, "text/html", body) -} - -template -typename std::enable_if< - !std::is_same::value && - !std::is_integral::value && - !std::is_same>::value -, bool>::type -saveSetting(T &value, std::string_view newValue, std::string &body) -{ - body += "Unsupported config type"; - return false; -} - -template -typename std::enable_if< - std::is_same::value -, bool>::type -saveSetting(T &value, std::string_view newValue, std::string &body) -{ - if (newValue == "true") - { - value = true; - body += "applied"; - return true; - } - else if (newValue == "false") - { - value = false; - body += "applied"; - return true; - } - else - { - body += fmt::format("only true and false allowed, not {}", newValue); - return false; - } -} - -template -typename std::enable_if< - !std::is_same::value && - std::is_integral::value -, bool>::type -saveSetting(T &value, std::string_view newValue, std::string &body) -{ - if (auto parsed = cpputils::fromString(newValue)) - { - value = *parsed; - body += "applied"; - return true; - } - else - { - body += fmt::format("could not parse {}", newValue); - return false; - } -} - -template -typename std::enable_if< - std::is_same>::value -, bool>::type -saveSetting(T &value, std::string_view newValue, std::string &body) -{ - if (std::array parsed; std::sscanf(newValue.data(), "%1hhi%1hhi%1hhi%1hhi", &parsed[0], &parsed[1], &parsed[2], &parsed[3]) == 4) - { - value = parsed; - body += "applied"; - return true; - } - else - { - body += fmt::format("could not parse {}", newValue); - return false; - } -} - -esp_err_t webserver_saveSettings_handler(httpd_req_t *req) -{ - espcpputils::LockHelper helper{webserver_lock->handle, std::chrono::ceil(5s).count()}; - if (!helper.locked()) - { - constexpr const std::string_view msg = "could not lock webserver_lock"; - ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); - } - - std::string query; - if (auto result = esphttpdutils::webserver_get_query(req)) - query = *result; - else - { - ESP_LOGE(TAG, "%.*s", result.error().size(), result.error().data()); - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", result.error()); - } - - std::string body; - bool success{true}; - - settings.executeForEveryCommonSetting([&](std::string_view key, auto &value){ - char valueBufEncoded[256]; - if (const auto result = httpd_query_key_value(query.data(), key.data(), valueBufEncoded, 256); result != ESP_OK) - { - if (result != ESP_ERR_NOT_FOUND) - { - const auto msg = fmt::format("{}: httpd_query_key_value() failed with {}", key, esp_err_to_name(result)); - ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); - body += msg; - body += '\n'; - success = false; - } - return; - } - - char valueBuf[257]; - esphttpdutils::urldecode(valueBuf, valueBufEncoded); - - body += key; - if (!saveSetting(value, valueBuf, body)) - success = false; - body += '\n'; - }); - - if (body.empty()) - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::Ok, "text/plain", "nothing changed?!") - - if (settingsPersister.save(settings)) - body += "settings persisted successfully"; - else - { - body += "error while persisting settings"; - success = false; - } - - if (success) - { - CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Location", "/settings") - body += "\nOk, continue at /settings"; - } - - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, - req, - success ? esphttpdutils::ResponseStatus::TemporaryRedirect : esphttpdutils::ResponseStatus::BadRequest, - "text/plain", - body) -} -} // namespace - #endif diff --git a/main/webserver_stringsettings.cpp b/main/webserver_stringsettings.cpp index e69de29..c756403 100644 --- a/main/webserver_stringsettings.cpp +++ b/main/webserver_stringsettings.cpp @@ -0,0 +1,169 @@ +#include "webserver_stringsettings.h" + +#ifdef FEATURE_WEBSERVER +using namespace std::chrono_literals; +using esphttpdutils::HtmlTag; + +namespace { +constexpr const char * const TAG = "BOBBYWEB"; +} // namespace + +esp_err_t webserver_stringSettings_handler(httpd_req_t *req) +{ + espcpputils::LockHelper helper{webserver_lock->handle, std::chrono::ceil(5s).count()}; + if (!helper.locked()) + { + constexpr const std::string_view msg = "could not lock webserver_lock"; + ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); + } + + std::string body; + + { + HtmlTag htmlTag{"html", body}; + + { + HtmlTag headTag{"head", body}; + + { + HtmlTag titleTag{"title", body}; + body += "String Settings"; + } + + body += ""; + + HtmlTag styleTag{"style", "type=\"text/css\"", body}; + body += + ".form-table {" + "display: table;" + "border-collapse: separate;" + "border-spacing: 10px 0;" + "}" + + ".form-table .form-table-row {" + "display: table-row;" + "}" + + ".form-table .form-table-row .form-table-cell {" + "display: table-cell;" + "}"; + } + + { + HtmlTag bodyTag{"body", body}; + + { + HtmlTag h1Tag{"h1", body}; + body += "String Settings"; + } + + { + HtmlTag pTag{"p", body}; + body += "Display control - " +#ifdef FEATURE_OTA + "Update - " +#endif + "Settings - " + "String Settings - " + "Dump NVS"; + } + + HtmlTag divTag{"div", "class=\"form-table\"", body}; + stringSettings.executeForEveryCommonSetting([&](std::string_view key, const auto &value){ + HtmlTag formTag{"form", "class=\"form-table-row\" action=\"/saveStringSettings\" method=\"GET\"", body}; + + { + HtmlTag divTag{"div", "class=\"form-table\"", body}; + HtmlTag bTag{"b", body}; + body += esphttpdutils::htmlentities(key); + } + + { + HtmlTag divTag{"div", "class=\"form-table-cell\"", body}; + body += fmt::format("", + esphttpdutils::htmlentities(key), + esphttpdutils::htmlentities(value)); + } + + { + HtmlTag divTag{"div", "class=\"form-table-cell\"", body}; + HtmlTag buttonTag{"button", "type=\"submit\"", body}; + body += "Save"; + } + }); + } + } + + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::Ok, "text/html", body) +} + +esp_err_t webserver_saveStringSettings_handler(httpd_req_t *req) +{ + espcpputils::LockHelper helper{webserver_lock->handle, std::chrono::ceil(5s).count()}; + if (!helper.locked()) + { + constexpr const std::string_view msg = "could not lock webserver_lock"; + ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); + } + + std::string query; + if (auto result = esphttpdutils::webserver_get_query(req)) + query = *result; + else + { + ESP_LOGE(TAG, "%.*s", result.error().size(), result.error().data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", result.error()); + } + + std::string body; + bool success{true}; + + stringSettings.executeForEveryCommonSetting([&](std::string_view key, auto &value){ + char valueBufEncoded[256]; + if (const auto result = httpd_query_key_value(query.data(), key.data(), valueBufEncoded, 256); result != ESP_OK) + { + if (result != ESP_ERR_NOT_FOUND) + { + const auto msg = fmt::format("{}: httpd_query_key_value() failed with {}", key, esp_err_to_name(result)); + ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); + body += msg; + body += '\n'; + success = false; + } + return; + } + + char valueBuf[257]; + esphttpdutils::urldecode(valueBuf, valueBufEncoded); + + value = valueBuf; + body += fmt::format("{}: applied", key); + body += '\n'; + }); + + if (body.empty()) + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::Ok, "text/plain", "nothing changed?!") + + if (settingsPersister.save(stringSettings)) + body += "string settings persisted successfully"; + else + { + body += "error while persisting string settings"; + success = false; + } + + if (success) + { + CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Location", "/stringSettings") + body += "\nOk, continue at /stringSettings"; + } + + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, + req, + success ? esphttpdutils::ResponseStatus::TemporaryRedirect : esphttpdutils::ResponseStatus::BadRequest, + "text/plain", + body) +} +#endif diff --git a/main/webserver_stringsettings.h b/main/webserver_stringsettings.h index 5c45351..0e881f4 100644 --- a/main/webserver_stringsettings.h +++ b/main/webserver_stringsettings.h @@ -19,173 +19,7 @@ #include "webserver_lock.h" #ifdef FEATURE_WEBSERVER -namespace { esp_err_t webserver_stringSettings_handler(httpd_req_t *req); esp_err_t webserver_saveStringSettings_handler(httpd_req_t *req); -} // namespace - -using esphttpdutils::HtmlTag; - -namespace { -esp_err_t webserver_stringSettings_handler(httpd_req_t *req) -{ - espcpputils::LockHelper helper{webserver_lock->handle, std::chrono::ceil(5s).count()}; - if (!helper.locked()) - { - constexpr const std::string_view msg = "could not lock webserver_lock"; - ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); - } - - std::string body; - - { - HtmlTag htmlTag{"html", body}; - - { - HtmlTag headTag{"head", body}; - - { - HtmlTag titleTag{"title", body}; - body += "String Settings"; - } - - body += ""; - - HtmlTag styleTag{"style", "type=\"text/css\"", body}; - body += - ".form-table {" - "display: table;" - "border-collapse: separate;" - "border-spacing: 10px 0;" - "}" - - ".form-table .form-table-row {" - "display: table-row;" - "}" - - ".form-table .form-table-row .form-table-cell {" - "display: table-cell;" - "}"; - } - - { - HtmlTag bodyTag{"body", body}; - - { - HtmlTag h1Tag{"h1", body}; - body += "String Settings"; - } - - { - HtmlTag pTag{"p", body}; - body += "Display control - " -#ifdef FEATURE_OTA - "Update - " -#endif - "Settings - " - "String Settings - " - "Dump NVS"; - } - - HtmlTag divTag{"div", "class=\"form-table\"", body}; - stringSettings.executeForEveryCommonSetting([&](std::string_view key, const auto &value){ - HtmlTag formTag{"form", "class=\"form-table-row\" action=\"/saveStringSettings\" method=\"GET\"", body}; - - { - HtmlTag divTag{"div", "class=\"form-table\"", body}; - HtmlTag bTag{"b", body}; - body += esphttpdutils::htmlentities(key); - } - - { - HtmlTag divTag{"div", "class=\"form-table-cell\"", body}; - body += fmt::format("", - esphttpdutils::htmlentities(key), - esphttpdutils::htmlentities(value)); - } - - { - HtmlTag divTag{"div", "class=\"form-table-cell\"", body}; - HtmlTag buttonTag{"button", "type=\"submit\"", body}; - body += "Save"; - } - }); - } - } - - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::Ok, "text/html", body) -} - -esp_err_t webserver_saveStringSettings_handler(httpd_req_t *req) -{ - espcpputils::LockHelper helper{webserver_lock->handle, std::chrono::ceil(5s).count()}; - if (!helper.locked()) - { - constexpr const std::string_view msg = "could not lock webserver_lock"; - ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); - } - - std::string query; - if (auto result = esphttpdutils::webserver_get_query(req)) - query = *result; - else - { - ESP_LOGE(TAG, "%.*s", result.error().size(), result.error().data()); - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", result.error()); - } - - std::string body; - bool success{true}; - - stringSettings.executeForEveryCommonSetting([&](std::string_view key, auto &value){ - char valueBufEncoded[256]; - if (const auto result = httpd_query_key_value(query.data(), key.data(), valueBufEncoded, 256); result != ESP_OK) - { - if (result != ESP_ERR_NOT_FOUND) - { - const auto msg = fmt::format("{}: httpd_query_key_value() failed with {}", key, esp_err_to_name(result)); - ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); - body += msg; - body += '\n'; - success = false; - } - return; - } - - char valueBuf[257]; - esphttpdutils::urldecode(valueBuf, valueBufEncoded); - - value = valueBuf; - body += fmt::format("{}: applied", key); - body += '\n'; - }); - - if (body.empty()) - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::Ok, "text/plain", "nothing changed?!") - - if (settingsPersister.save(stringSettings)) - body += "string settings persisted successfully"; - else - { - body += "error while persisting string settings"; - success = false; - } - - if (success) - { - CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Location", "/stringSettings") - body += "\nOk, continue at /stringSettings"; - } - - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, - req, - success ? esphttpdutils::ResponseStatus::TemporaryRedirect : esphttpdutils::ResponseStatus::BadRequest, - "text/plain", - body) -} -} // namespace - #endif