#include "webserver_displaycontrol.h" // system includes #include // esp-idf includes #include #include // 3rdparty lib includes #include #include #include #include #include #include #include #include #include #include #include #include // local includes #include "bobbybuttons.h" #include "globals.h" #include "newsettings.h" #include "webserver_lock.h" using esphttpdutils::HtmlTag; using namespace std::chrono_literals; namespace { constexpr const char * const TAG = "BOBBYWEB"; } // namespace esp_err_t webserver_root_handler(httpd_req_t *req) { std::string body; std::string wants_json_query; if (auto result = esphttpdutils::webserver_get_query(req)) wants_json_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()); } char tmpBuf[256]; const auto key_result = httpd_query_key_value(wants_json_query.data(), "json", tmpBuf, 256); if (key_result == ESP_OK && (configs.webserverPassword.value().empty() || configs.webserverPassword.value() == tmpBuf)) { body += "{"; if (auto currentDisplay = static_cast(espgui::currentDisplay.get())) { body.reserve(4096); if (const auto *textInterface = currentDisplay->asTextInterface()) { body += fmt::format("\"name\":\"{}\",", textInterface->text()); } if (const auto *menuDisplay = currentDisplay->asMenuDisplay()) { body += fmt::format("\"index\":{},\"items\":[", menuDisplay->selectedIndex()); menuDisplay->runForEveryMenuItem([&,selectedIndex=menuDisplay->selectedIndex()](const espgui::MenuItem &menuItem){ body += "{"; const auto itemName = menuItem.text(); std::string color{}; std::string font{}; switch (menuItem.color()) { case TFT_RED: color = "&1"; break; case TFT_GREEN: color = "&2"; break; case TFT_BLUE: color = "&3"; break; case TFT_YELLOW: color = "&4"; break; case TFT_BLACK: color = "&5"; break; case TFT_WHITE: color = "&6"; break; case TFT_GREY: case TFT_DARKGREY: color = "&7"; break; default: color = ""; break; } switch (menuItem.font()) { case 2: font = "&s"; break; case 4: font = "&m"; break; default: font = ""; break; } std::string menuItemName = font + color + itemName; body += fmt::format("\"name\":\"{}\",\"icon\":\"{}\",\"index\":{}", menuItemName, (menuItem.icon()) ? menuItem.icon()->name : "", selectedIndex); body += "},"; }); body += "],"; } else if (const auto *changeValueDisplay = currentDisplay->asChangeValueDisplayInterface()) { body += fmt::format("\"value\":\"{}\",", changeValueDisplay->shownValue()); } else { body += "\"err\":\"Screen not implemented yet.\""; } } else { body += "\"err\":\"Currently no screen instantiated.\""; } body += "}"; size_t lastGesch = body.rfind("},"); if (std::string::npos != lastGesch) body = body.erase(lastGesch+1, 1); size_t lastEckig = body.rfind("],"); if (std::string::npos != lastEckig) body = body.erase(lastEckig+1, 1); } else if (key_result != ESP_ERR_NOT_FOUND && tmpBuf != configs.webserverPassword.value()) { CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::Unauthorized, "text/plain", ""); } else { 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 - " "Update - " "Settings - " "New Settings - " "Dump NVS"; } { HtmlTag pTag{"p", body}; body += "Trigger raw button: " "Button0 " "Button1 " "Button2 " "Button3 " "Button4 " "Button5 " "Button6 " "Button7 " "Button8 " "Button9 " "Button10 " "Button11 " "Button12 " "Button13 " "Button14 " "Button15"; } { HtmlTag pTag{"p", body}; body += fmt::format("Trigger button: " "Left " "Right " "Up " "Down " "Profile0 " "Profile1 " "Profile2 " "Profile3 " "Left2 " "Right2 " "Up2 " "Down2" "Extra1" "Extra2" "Extra3" "Extra4", std::to_underlying(espgui::Button::Left), std::to_underlying(espgui::Button::Right), std::to_underlying(espgui::Button::Up), std::to_underlying(espgui::Button::Down), std::to_underlying(BobbyButton::Profile0), std::to_underlying(BobbyButton::Profile1), std::to_underlying(BobbyButton::Profile2), std::to_underlying(BobbyButton::Profile3), std::to_underlying(BobbyButton::Left2), std::to_underlying(BobbyButton::Right2), std::to_underlying(BobbyButton::Up2), std::to_underlying(BobbyButton::Down2), std::to_underlying(BobbyButton::Extra1), std::to_underlying(BobbyButton::Extra2), std::to_underlying(BobbyButton::Extra3), std::to_underlying(BobbyButton::Extra4)); } 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 if (const auto *changeValueDisplay = currentDisplay->asChangeValueDisplayString()) { 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_ON_ERROR(httpd_resp_set_hdr, req, "Access-Control-Allow-Origin", "http://web.bobbycar.cloud"); CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::Ok, (key_result == ESP_OK) ? "application/json":"text/html", body) } esp_err_t webserver_triggerRawButton_handler(httpd_req_t *req) { 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()); } int8_t 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); std::string_view value{valueBuf}; if (auto parsed = cpputils::fromString(value)) { button = *parsed; } else { const auto msg = fmt::format("could not parse {} {}", buttonParamName, value); ESP_LOGW(TAG, "%.*s", msg.size(), msg.data()); CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); } } rawButtonRequest.store(button); CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Location", "/") CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Access-Control-Allow-Origin", "http://web.bobbycar.cloud"); CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::TemporaryRedirect, "text/html", "Ok, continue at /") } esp_err_t webserver_triggerButton_handler(httpd_req_t *req) { 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()); } int8_t 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); std::string_view value{valueBuf}; if (auto parsed = cpputils::fromString(value)) { button = *parsed; } else { const auto msg = fmt::format("could not parse {} {}", buttonParamName, value); ESP_LOGW(TAG, "%.*s", msg.size(), msg.data()); CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); } } buttonRequest.store(button); CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Location", "/") CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Access-Control-Allow-Origin", "http://web.bobbycar.cloud"); CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::TemporaryRedirect, "text/html", "Ok, continue at /") } esp_err_t webserver_triggerItem_handler(httpd_req_t *req) { CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Access-Control-Allow-Origin", "http://web.bobbycar.cloud"); 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) { CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Access-Control-Allow-Origin", "http://web.bobbycar.cloud"); 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()); } char valueBuf[257]; 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); } } esphttpdutils::urldecode(valueBuf, valueBufEncoded); } std::string_view value{valueBuf}; 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); } if (auto *changeValueDisplay = espgui::currentDisplay->asChangeValueDisplayInterface()) { int newValue; 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); } changeValueDisplay->setShownValue(newValue); } else if (auto *changeValueDisplay = espgui::currentDisplay->asChangeValueDisplayString()) { changeValueDisplay->setShownValue(std::string{value}); } else { 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); } 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 /") }