diff --git a/CMakeLists.txt b/CMakeLists.txt index bb11ae8..1537f1f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,7 +40,7 @@ add_definitions( -DDEFAULT_FIELDADVMAX=40 -DDEVICE_PREFIX=bobbyquad -DAP_PASSWORD=Passwort_123 -# -DFEATURE_WEBSERVER + -DFEATURE_WEBSERVER # -DFEATURE_ARDUINOOTA # -DFEATURE_WEBOTA -DFEATURE_DPAD_5WIRESW diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 18701ab..9d1a746 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -178,12 +178,9 @@ set(sources set(dependencies libsodium freertos nvs_flash esp_http_server esp_https_ota mdns app_update esp_system esp_websocket_client driver - arduino-esp32 ArduinoJson -# AsyncTCP + arduino-esp32 ArduinoJson esp-nimble-cpp bobbycar-protocol cpputils cxx-ring-buffer date -# ESPAsyncWebServer espchrono espcpputils espwifistack expected fmt TFT_eSPI - esp-nimble-cpp ) idf_component_register( diff --git a/main/displays/calibratedisplay.h b/main/displays/calibratedisplay.h index 4be0c20..b96be84 100644 --- a/main/displays/calibratedisplay.h +++ b/main/displays/calibratedisplay.h @@ -224,7 +224,13 @@ void CalibrateDisplay::redraw() void CalibrateDisplay::stop() { if (currentMode == &m_mode) + { + // to avoid crash after deconstruction + m_mode.stop(); + lastMode = nullptr; + currentMode = m_oldMode; + } } void CalibrateDisplay::rotate(int offset) diff --git a/main/displays/lockscreen.h b/main/displays/lockscreen.h index 4aa6e36..d4ce8cc 100644 --- a/main/displays/lockscreen.h +++ b/main/displays/lockscreen.h @@ -143,7 +143,13 @@ void Lockscreen::stop() Base::stop(); if (currentMode == &m_mode) + { + // to avoid crash after deconstruction + m_mode.stop(); + lastMode = nullptr; + currentMode = m_oldMode; + } } void Lockscreen::confirm() diff --git a/main/globals.h b/main/globals.h index 74fa05b..13cd279 100644 --- a/main/globals.h +++ b/main/globals.h @@ -86,6 +86,7 @@ BluetoothSerial bluetoothSerial; TFT_eSPI tft = TFT_eSPI(); +ModeInterface *lastMode{}; ModeInterface *currentMode{}; std::unique_ptr currentDisplay; diff --git a/main/main.cpp b/main/main.cpp index 0256b5e..e7e88f8 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -1,3 +1,5 @@ +constexpr const char * const TAG = "BOBBY"; + // system includes #include @@ -100,7 +102,6 @@ using namespace std::chrono_literals; #include "wifi_bobbycar.h" namespace { -ModeInterface *lastMode{}; std::optional lastPotiRead; std::optional lastModeUpdate; std::optional lastStatsUpdate; diff --git a/main/webserver.h b/main/webserver.h index d169fcf..863bc5d 100644 --- a/main/webserver.h +++ b/main/webserver.h @@ -1,9 +1,13 @@ #pragma once -// 3rdparty lib includes +// system includes +#include + +// esp-idf includes #ifdef FEATURE_WEBSERVER -#include +#include #endif +#include // local includes #include "screens.h" @@ -13,237 +17,76 @@ #include "displays/updatedisplay.h" //#include "esputils.h" #include "buttons.h" +#include "espcppmacros.h" namespace { #ifdef FEATURE_WEBSERVER -AsyncWebServer webServer{80}; +httpd_handle_t httpdHandle; -bool shouldReboot; +std::atomic shouldReboot; class HtmlTag { public: - HtmlTag(const char *tagName, AsyncResponseStream *response) : + HtmlTag(const char *tagName, std::string &body) : m_tagName{tagName}, - m_response{response} + m_body{body} { - m_response->printf("<%s>", m_tagName); + m_body += '<'; + m_body += m_tagName; + m_body += '>'; } ~HtmlTag() { - m_response->printf("", m_tagName); + m_body += "'; } private: const char * const m_tagName; - AsyncResponseStream * const m_response; + std::string &m_body; }; +esp_err_t webserver_root_handler(httpd_req_t *req); +esp_err_t webserver_up_handler(httpd_req_t *req); +esp_err_t webserver_down_handler(httpd_req_t *req); +esp_err_t webserver_confirm_handler(httpd_req_t *req); +esp_err_t webserver_back_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); +esp_err_t webserver_reboot_handler(httpd_req_t *req); + void initWebserver() { shouldReboot = false; - DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); + { + httpd_config_t httpConfig HTTPD_DEFAULT_CONFIG(); + httpConfig.core_id = 1; - webServer.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ - AsyncResponseStream *response = request->beginResponseStream("text/html"); - - { - HtmlTag htmlTag{"html", response}; - - { - HtmlTag headTag{"head", response}; - - { - HtmlTag titleTag{"title", response}; - response->print("Bobbycar remote"); - } - - response->print(""); - } - - { - HtmlTag bodyTag{"body", response}; - - { - HtmlTag h1Tag{"h1", response}; - response->print("Bobbycar remote"); - } - - { - HtmlTag pTag{"p", response}; - response->print("Up " - "Down " - "Confirm " - "Back"); - } - - if (auto constCurrentDisplay = static_cast(currentDisplay.get())) - { - if (const auto *textInterface = constCurrentDisplay->asTextInterface()) - { - HtmlTag h2Tag{"h2", response}; - response->print(textInterface->text().c_str()); - } - - if (const auto *menuDisplay = constCurrentDisplay->asMenuDisplay()) - { - HtmlTag ulTag{"ul", response}; - - int i{0}; - menuDisplay->runForEveryMenuItem([&,selectedIndex=menuDisplay->selectedIndex()](const MenuItem &menuItem){ - response->print("print(" style=\"border: 1px solid black;\""); - - response->print(">print(i); - response->print("\">"); - response->print(menuItem.text().c_str()); - response->print(""); - - i++; - }); - } - else if (const auto *changeValueDisplay = constCurrentDisplay->asChangeValueDisplayInterface()) - { - response->print("
"); - response->print(("shownValue()) + "\" />").c_str()); - response->print(""); - response->print("
"); - } - else - { - response->print("No web control implemented for current display."); - } - } - else - { - response->print("Currently no screen instantiated."); - } - } - } - - request->send(response); - }); - - webServer.on("/up", HTTP_GET, [](AsyncWebServerRequest *request){ - InputDispatcher::rotate(-1); - - AsyncWebServerResponse *response = request->beginResponse(302, "text/plain", "ok"); - response->addHeader("Location", "/"); - request->send(response); - }); - - webServer.on("/down", HTTP_GET, [](AsyncWebServerRequest *request){ - InputDispatcher::rotate(1); - - AsyncWebServerResponse *response = request->beginResponse(302, "text/plain", "ok"); - response->addHeader("Location", "/"); - request->send(response); - }); - - webServer.on("/confirm", HTTP_GET, [](AsyncWebServerRequest *request){ - InputDispatcher::confirmButton(true); - InputDispatcher::confirmButton(false); - - AsyncWebServerResponse *response = request->beginResponse(302, "text/plain", "ok"); - response->addHeader("Location", "/"); - request->send(response); - }); - - webServer.on("/back", HTTP_GET, [](AsyncWebServerRequest *request){ - InputDispatcher::backButton(true); - InputDispatcher::backButton(false); - - AsyncWebServerResponse *response = request->beginResponse(302, "text/plain", "ok"); - response->addHeader("Location", "/"); - request->send(response); - }); - - webServer.on("/triggerItem", HTTP_GET, [](AsyncWebServerRequest *request){ - if (!request->hasArg("index")) - { - request->send(400, "text/plain", "index parameter missing"); + 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; - } - - if (!currentDisplay) - { - request->send(400, "text/plain", "currentDisplay is null"); - return; - } - - auto *menuDisplay = currentDisplay->asMenuDisplay(); - if (!menuDisplay) - { - request->send(400, "text/plain", "currentDisplay is not a menu display"); - return; - } - - const auto indexStr = request->arg("index"); - - char *ptr; - const auto index = std::strtol(std::begin(indexStr), &ptr, 10); - - if (ptr != std::end(indexStr)) - { - request->send(400, "text/plain", "index could not be parsed"); - return; - } - - if (index < 0 || index >= menuDisplay->menuItemCount()) - { - request->send(400, "text/plain", "index out of range"); - return; - } - - menuDisplay->getMenuItem(index).triggered(); - - AsyncWebServerResponse *response = request->beginResponse(302, "text/plain", "ok"); - response->addHeader("Location", "/"); - request->send(response); - }); - - webServer.on("/setValue", HTTP_GET, [](AsyncWebServerRequest *request){ - if (!request->hasArg("value")) - { - request->send(400, "text/plain", "value parameter missing"); - return; - } - - if (!currentDisplay) - { - request->send(400, "text/plain", "currentDisplay is null"); - return; - } - - auto *changeValueDisplay = currentDisplay->asChangeValueDisplayInterface(); - if (!changeValueDisplay) - { - request->send(400, "text/plain", "currentDisplay is not a change value display"); - return; - } - - const auto valueStr = request->arg("value"); - - char *ptr; - const auto value = std::strtol(std::begin(valueStr), &ptr, 10); - - if (ptr != std::end(valueStr)) - { - request->send(400, "text/plain", "value could not be parsed"); - return; - } - - changeValueDisplay->setShownValue(value); - - AsyncWebServerResponse *response = request->beginResponse(302, "text/plain", "ok"); - response->addHeader("Location", "/"); - request->send(response); - }); + } + for (const httpd_uri_t &uri : { + httpd_uri_t { .uri = "/", .method = HTTP_GET, .handler = webserver_root_handler, .user_ctx = NULL }, + httpd_uri_t { .uri = "/up", .method = HTTP_GET, .handler = webserver_up_handler, .user_ctx = NULL }, + httpd_uri_t { .uri = "/down", .method = HTTP_GET, .handler = webserver_down_handler, .user_ctx = NULL }, + httpd_uri_t { .uri = "/confirm", .method = HTTP_GET, .handler = webserver_confirm_handler, .user_ctx = NULL }, + httpd_uri_t { .uri = "/back", .method = HTTP_GET, .handler = webserver_back_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 } + }) + { + 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; + } #ifdef FEATURE_WEBOTA webServer.on("/update", HTTP_GET, [](AsyncWebServerRequest *request){ @@ -338,8 +181,6 @@ void initWebserver() webServer.on("/updateCode", HTTP_POST, handleUpdate, createHandleUpdtateUpload((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000, U_FLASH)); webServer.on("/updateData", HTTP_POST, handleUpdate, createHandleUpdtateUpload(UPDATE_SIZE_UNKNOWN, U_SPIFFS)); #endif - - webServer.begin(); } void handleWebserver() @@ -347,8 +188,253 @@ void handleWebserver() if (shouldReboot) { shouldReboot = false; - ESP.restart(); + esp_restart(); } } + +esp_err_t webserver_root_handler(httpd_req_t *req) +{ + std::string body; + + { + HtmlTag htmlTag{"html", body}; + + { + HtmlTag headTag{"head", body}; + + { + HtmlTag titleTag{"title", body}; + body += ("Bobbycar remote"); + } + + body += (""); + } + + { + HtmlTag bodyTag{"body", body}; + + { + HtmlTag h1Tag{"h1", body}; + body += ("Bobbycar remote"); + } + + { + HtmlTag pTag{"p", body}; + body += ("Up " + "Down " + "Confirm " + "Back"); + } + + if (auto constCurrentDisplay = static_cast(currentDisplay.get())) + { + if (const auto *textInterface = constCurrentDisplay->asTextInterface()) + { + HtmlTag h2Tag{"h2", body}; + body += (textInterface->text().c_str()); + } + + if (const auto *menuDisplay = constCurrentDisplay->asMenuDisplay()) + { + HtmlTag ulTag{"ul", body}; + + int i{0}; + menuDisplay->runForEveryMenuItem([&,selectedIndex=menuDisplay->selectedIndex()](const MenuItem &menuItem){ + body += (""); + body += (menuItem.text().c_str()); + body += (""); + + i++; + }); + } + else if (const auto *changeValueDisplay = constCurrentDisplay->asChangeValueDisplayInterface()) + { + body += ("
"); + body += (("shownValue()) + "\" />").c_str()); + body += (""); + body += ("
"); + } + else + { + body += ("No web control implemented for current display."); + } + } + else + { + body += ("Currently no screen instantiated."); + } + } + } + + CALL_AND_EXIT_ON_ERROR(httpd_resp_set_type, req, "text/html") + CALL_AND_EXIT_ON_ERROR(httpd_resp_send, req, body.data(), body.size()) + + return ESP_OK; +} + +esp_err_t webserver_up_handler(httpd_req_t *req) +{ +#ifdef OLD_CODE + InputDispatcher::rotate(-1); + + AsyncWebServerResponse *response = request->beginResponse(302, "text/plain", "ok"); + response->addHeader("Location", "/"); + request->send(response); +#else + return ESP_OK; +#endif +} + +esp_err_t webserver_down_handler(httpd_req_t *req) +{ +#ifdef OLD_CODE + InputDispatcher::rotate(1); + + AsyncWebServerResponse *response = request->beginResponse(302, "text/plain", "ok"); + response->addHeader("Location", "/"); + request->send(response); +#else + return ESP_OK; +#endif +} + +esp_err_t webserver_confirm_handler(httpd_req_t *req) +{ +#ifdef OLD_CODE + InputDispatcher::confirmButton(true); + InputDispatcher::confirmButton(false); + + AsyncWebServerResponse *response = request->beginResponse(302, "text/plain", "ok"); + response->addHeader("Location", "/"); + request->send(response); +#else + return ESP_OK; +#endif +} + +esp_err_t webserver_back_handler(httpd_req_t *req) +{ +#ifdef OLD_CODE + InputDispatcher::backButton(true); + InputDispatcher::backButton(false); + + AsyncWebServerResponse *response = request->beginResponse(302, "text/plain", "ok"); + response->addHeader("Location", "/"); + request->send(response); +#else + return ESP_OK; +#endif +} + +esp_err_t webserver_triggerItem_handler(httpd_req_t *req) +{ +#ifdef OLD_CODE + if (!request->hasArg("index")) + { + request->send(400, "text/plain", "index parameter missing"); + return; + } + + if (!currentDisplay) + { + request->send(400, "text/plain", "currentDisplay is null"); + return; + } + + auto *menuDisplay = currentDisplay->asMenuDisplay(); + if (!menuDisplay) + { + request->send(400, "text/plain", "currentDisplay is not a menu display"); + return; + } + + const auto indexStr = request->arg("index"); + + char *ptr; + const auto index = std::strtol(std::begin(indexStr), &ptr, 10); + + if (ptr != std::end(indexStr)) + { + request->send(400, "text/plain", "index could not be parsed"); + return; + } + + if (index < 0 || index >= menuDisplay->menuItemCount()) + { + request->send(400, "text/plain", "index out of range"); + return; + } + + menuDisplay->getMenuItem(index).triggered(); + + AsyncWebServerResponse *response = request->beginResponse(302, "text/plain", "ok"); + response->addHeader("Location", "/"); + request->send(response); +#else + return ESP_OK; +#endif +} + +esp_err_t webserver_setValue_handler(httpd_req_t *req) +{ +#ifdef OLD_CODE + if (!request->hasArg("value")) + { + request->send(400, "text/plain", "value parameter missing"); + return; + } + + if (!currentDisplay) + { + request->send(400, "text/plain", "currentDisplay is null"); + return; + } + + auto *changeValueDisplay = currentDisplay->asChangeValueDisplayInterface(); + if (!changeValueDisplay) + { + request->send(400, "text/plain", "currentDisplay is not a change value display"); + return; + } + + const auto valueStr = request->arg("value"); + + char *ptr; + const auto value = std::strtol(std::begin(valueStr), &ptr, 10); + + if (ptr != std::end(valueStr)) + { + request->send(400, "text/plain", "value could not be parsed"); + return; + } + + changeValueDisplay->setShownValue(value); + + AsyncWebServerResponse *response = request->beginResponse(302, "text/plain", "ok"); + response->addHeader("Location", "/"); + request->send(response); +#else + return ESP_OK; +#endif +} + +esp_err_t webserver_reboot_handler(httpd_req_t *req) +{ + std::string_view body{"REBOOT called..."}; + CALL_AND_EXIT_ON_ERROR(httpd_resp_set_type, req, "text/html") + CALL_AND_EXIT_ON_ERROR(httpd_resp_send, req, body.data(), body.size()) + + shouldReboot = true; + + return ESP_OK; +} + #endif }