From 9c5a427a344d55a3c19ec62f70b92fd500095c1c Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Thu, 16 Oct 2025 18:59:23 +0200 Subject: [PATCH] Merge develop into main (#377) - Extract web server from `DevelopmentService` into a separate class: `HttpServer` - Export more functions in `tt_init.cpp` --- .../Include/Tactility/network/HttpServer.h | 71 +++++++++ .../service/development/DevelopmentService.h | 72 ++++----- .../Source/app/development/Development.cpp | 6 +- Tactility/Source/network/HttpServer.cpp | 58 ++++++++ .../development/DevelopmentService.cpp | 139 +++--------------- TactilityC/Source/tt_init.cpp | 5 + 6 files changed, 191 insertions(+), 160 deletions(-) create mode 100644 Tactility/Include/Tactility/network/HttpServer.h create mode 100644 Tactility/Source/network/HttpServer.cpp diff --git a/Tactility/Include/Tactility/network/HttpServer.h b/Tactility/Include/Tactility/network/HttpServer.h new file mode 100644 index 00000000..4a238b86 --- /dev/null +++ b/Tactility/Include/Tactility/network/HttpServer.h @@ -0,0 +1,71 @@ +#pragma once +#ifdef ESP_PLATFORM + +#include +#include +#include + +namespace tt::network { + +class HttpServer { + +public: + + /** + * @brief Function for URI matching used by server. + * + * @param[in] referenceUri URI/template with respect to which the other URI is matched + * @param[in] uriToCheck URI/template being matched to the reference URI/template + * @param[in] matchUpTo For specifying the actual length of `uri_to_match` up to + * which the matching algorithm is to be applied (The maximum + * value is `strlen(uri_to_match)`, independent of the length + * of `reference_uri`) + * @return true on match + */ + typedef bool (*UriMatchFunction)(const char* referenceUri, const char* uriToCheck, size_t matchUpTo); + +private: + + const uint32_t port; + const std::string address; + const uint32_t stackSize; + const UriMatchFunction matchUri; + + std::vector handlers; + + Mutex mutex = Mutex(Mutex::Type::Recursive); + httpd_handle_t server = nullptr; + + bool startInternal(); + void stopInternal(); + +public: + + HttpServer( + uint32_t port, + const std::string& address, + std::vector handlers, + uint32_t stackSize = 5120, + UriMatchFunction matchUri = httpd_uri_match_wildcard + ) : + port(port), + address(address), + stackSize(stackSize), + matchUri(matchUri), + handlers(handlers) + {} + + void start(); + + void stop(); + + bool isStarted() const { + auto lock = mutex.asScopedLock(); + lock.lock(); + return server != nullptr; + } +}; + +} + +#endif \ No newline at end of file diff --git a/Tactility/Private/Tactility/service/development/DevelopmentService.h b/Tactility/Private/Tactility/service/development/DevelopmentService.h index 5f17706c..2d0bb5fd 100644 --- a/Tactility/Private/Tactility/service/development/DevelopmentService.h +++ b/Tactility/Private/Tactility/service/development/DevelopmentService.h @@ -7,49 +7,44 @@ #include #include -#include +#include namespace tt::service::development { class DevelopmentService final : public Service { Mutex mutex = Mutex(Mutex::Type::Recursive); - httpd_handle_t server = nullptr; - bool enabled = false; - kernel::SystemEventSubscription networkConnectEventSubscription = 0; - kernel::SystemEventSubscription networkDisconnectEventSubscription = 0; std::string deviceResponse; - - httpd_uri_t handleGetInfoEndpoint = { - .uri = "/info", - .method = HTTP_GET, - .handler = handleGetInfo, - .user_ctx = this - }; - - httpd_uri_t appRunEndpoint = { - .uri = "/app/run", - .method = HTTP_POST, - .handler = handleAppRun, - .user_ctx = this - }; - - httpd_uri_t appInstallEndpoint = { - .uri = "/app/install", - .method = HTTP_PUT, - .handler = handleAppInstall, - .user_ctx = this - }; - - httpd_uri_t appUninstallEndpoint = { - .uri = "/app/uninstall", - .method = HTTP_PUT, - .handler = handleAppUninstall, - .user_ctx = this - }; - - void onNetworkConnected(); - void onNetworkDisconnected(); + network::HttpServer httpServer = network::HttpServer( + 6666, + "0.0.0.0", + std::vector{ + { + .uri = "/info", + .method = HTTP_GET, + .handler = handleGetInfo, + .user_ctx = this + }, + { + .uri = "/app/run", + .method = HTTP_POST, + .handler = handleAppRun, + .user_ctx = this + }, + { + .uri = "/app/install", + .method = HTTP_PUT, + .handler = handleAppInstall, + .user_ctx = this + }, + { + .uri = "/app/uninstall", + .method = HTTP_PUT, + .handler = handleAppUninstall, + .user_ctx = this + } + } + ); void startServer(); void stopServer(); @@ -64,6 +59,7 @@ public: // region Overrides bool onStart(ServiceContext& service) override; + void onStop(ServiceContext& service) override; // endregion Overrides @@ -81,10 +77,6 @@ public: * @param[in] enabled */ void setEnabled(bool enabled); - - bool isStarted() const; - - // region Internal API }; std::shared_ptr findService(); diff --git a/Tactility/Source/app/development/Development.cpp b/Tactility/Source/app/development/Development.cpp index adaf81c7..9aca7aea 100644 --- a/Tactility/Source/app/development/Development.cpp +++ b/Tactility/Source/app/development/Development.cpp @@ -65,14 +65,14 @@ class DevelopmentApp final : public App { void updateViewState() { if (!service->isEnabled()) { lv_label_set_text(statusLabel, "Service disabled"); - } else if (!service->isStarted()) { + } else if (service::wifi::getRadioState() != service::wifi::RadioState::ConnectionActive) { lv_label_set_text(statusLabel, "Waiting for connection..."); - } else { // enabled and started + } else { // enabled and connected to wifi auto ip = service::wifi::getIp(); if (ip.empty()) { lv_label_set_text(statusLabel, "Waiting for IP..."); } else { - const std::string status = std::string("Available at ") + ip; + const std::string status = std::format("Available at {}", ip); lv_label_set_text(statusLabel, status.c_str()); } } diff --git a/Tactility/Source/network/HttpServer.cpp b/Tactility/Source/network/HttpServer.cpp new file mode 100644 index 00000000..756aa408 --- /dev/null +++ b/Tactility/Source/network/HttpServer.cpp @@ -0,0 +1,58 @@ +#ifdef ESP_PLATFORM + +#include +#include + +namespace tt::network { + +constexpr auto* TAG = "HttpServer"; + +bool HttpServer::startInternal() { + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + config.stack_size = stackSize; + config.server_port = port; + config.uri_match_fn = matchUri; + + if (httpd_start(&server, &config) != ESP_OK) { + TT_LOG_E(TAG, "Failed to start http server on port %d", port); + return false; + } + + for (std::vector::reference handler : handlers) { + httpd_register_uri_handler(server, &handler); + } + + TT_LOG_I(TAG, "Started on port %d", config.server_port); + + return true; +} + +void HttpServer::stopInternal() { + TT_LOG_I(TAG, "Stopping server"); + if (server != nullptr && httpd_stop(server) != ESP_OK) { + TT_LOG_W(TAG, "Error while stopping"); + server = nullptr; + } +} + +void HttpServer::start() { + auto lock = mutex.asScopedLock(); + lock.lock(); + + startInternal(); +} + +void HttpServer::stop() { + auto lock = mutex.asScopedLock(); + lock.lock(); + + if (!isStarted()) { + TT_LOG_W(TAG, "Not started"); + } + + stopInternal(); +} + +} + +#endif \ No newline at end of file diff --git a/Tactility/Source/service/development/DevelopmentService.cpp b/Tactility/Source/service/development/DevelopmentService.cpp index 8ab32afa..c4c23625 100644 --- a/Tactility/Source/service/development/DevelopmentService.cpp +++ b/Tactility/Source/service/development/DevelopmentService.cpp @@ -9,17 +9,11 @@ #include #include #include -#include #include -#include #include -#include -#include #include #include -#include -#include namespace tt::service::development { @@ -28,70 +22,6 @@ extern const ServiceManifest manifest; constexpr const char* TAG = "DevService"; bool DevelopmentService::onStart(ServiceContext& service) { - auto lock = mutex.asScopedLock(); - lock.lock(); - - networkConnectEventSubscription = kernel::subscribeSystemEvent( - kernel::SystemEvent::NetworkConnected, - [this](kernel::SystemEvent) { onNetworkConnected(); } - ); - networkConnectEventSubscription = kernel::subscribeSystemEvent( - kernel::SystemEvent::NetworkDisconnected, - [this](kernel::SystemEvent) { onNetworkDisconnected(); } - ); - - setEnabled(shouldEnableOnBoot()); - - return true; -} - -void DevelopmentService::onStop(ServiceContext& service) { - auto lock = mutex.asScopedLock(); - lock.lock(); - - kernel::unsubscribeSystemEvent(networkConnectEventSubscription); - kernel::unsubscribeSystemEvent(networkDisconnectEventSubscription); - - if (isEnabled()) { - setEnabled(false); - } -} - -// region Enable/disable - -void DevelopmentService::setEnabled(bool enabled) { - auto lock = mutex.asScopedLock(); - lock.lock(); - this->enabled = enabled; - - // We might already have an IP address, so in case we do, we start the server manually - // Or we started the server while it shouldn't be - if (enabled && !isStarted() && wifi::getRadioState() == wifi::RadioState::ConnectionActive) { - startServer(); - } else if (!enabled && isStarted()) { - stopServer(); - } -} - -bool DevelopmentService::isEnabled() const { - auto lock = mutex.asScopedLock(); - lock.lock(); - return enabled; -} - -// region Enable/disable - -void DevelopmentService::startServer() { - auto lock = mutex.asScopedLock(); - lock.lock(); - - if (isStarted()) { - TT_LOG_W(TAG, "Already started"); - return; - } - - ESP_LOGI(TAG, "Starting server"); - std::stringstream stream; stream << "{"; stream << "\"cpuFamily\":\"" << CONFIG_IDF_TARGET << "\", "; @@ -100,61 +30,36 @@ void DevelopmentService::startServer() { stream << "}"; deviceResponse = stream.str(); - httpd_config_t config = HTTPD_DEFAULT_CONFIG(); - config.stack_size = 5120; + setEnabled(shouldEnableOnBoot()); - config.server_port = 6666; - config.uri_match_fn = httpd_uri_match_wildcard; + return true; +} - if (httpd_start(&server, &config) == ESP_OK) { - httpd_register_uri_handler(server, &handleGetInfoEndpoint); - httpd_register_uri_handler(server, &appRunEndpoint); - httpd_register_uri_handler(server, &appInstallEndpoint); - httpd_register_uri_handler(server, &appUninstallEndpoint); - TT_LOG_I(TAG, "Started on port %d", config.server_port); +void DevelopmentService::onStop(ServiceContext& service) { + setEnabled(false); +} + +// region Enable/disable + +void DevelopmentService::setEnabled(bool enabled) { + auto lock = mutex.asScopedLock(); + lock.lock(); + + if (enabled) { + if (!httpServer.isStarted()) { + httpServer.start(); + } } else { - TT_LOG_E(TAG, "Failed to start"); + if (httpServer.isStarted()) { + httpServer.stop(); + } } } -void DevelopmentService::stopServer() { +bool DevelopmentService::isEnabled() const { auto lock = mutex.asScopedLock(); lock.lock(); - - if (!isStarted()) { - TT_LOG_W(TAG, "Not started"); - return; - } - - TT_LOG_I(TAG, "Stopping server"); - if (httpd_stop(server) != ESP_OK) { - TT_LOG_W(TAG, "Error while stopping"); - } - server = nullptr; -} - -bool DevelopmentService::isStarted() const { - auto lock = mutex.asScopedLock(); - lock.lock(); - return server != nullptr; -} - -void DevelopmentService::onNetworkConnected() { - TT_LOG_I(TAG, "onNetworkConnected"); - mutex.withLock([this] { - if (isEnabled() && !isStarted()) { - startServer(); - } - }); -} - -void DevelopmentService::onNetworkDisconnected() { - TT_LOG_I(TAG, "onNetworkDisconnected"); - mutex.withLock([this] { - if (isStarted()) { - stopServer(); - } - }); + return httpServer.isStarted(); } // region endpoints diff --git a/TactilityC/Source/tt_init.cpp b/TactilityC/Source/tt_init.cpp index 2dc82920..3e301b2a 100644 --- a/TactilityC/Source/tt_init.cpp +++ b/TactilityC/Source/tt_init.cpp @@ -379,6 +379,11 @@ const esp_elfsym main_symbols[] { ESP_ELFSYM_EXPORT(lv_obj_set_flex_align), ESP_ELFSYM_EXPORT(lv_obj_set_flex_flow), ESP_ELFSYM_EXPORT(lv_obj_set_flex_grow), + ESP_ELFSYM_EXPORT(lv_obj_set_layout), + ESP_ELFSYM_EXPORT(lv_obj_is_layout_positioned), + ESP_ELFSYM_EXPORT(lv_obj_mark_layout_as_dirty), + ESP_ELFSYM_EXPORT(lv_obj_get_style_layout), + ESP_ELFSYM_EXPORT(lv_obj_update_layout), ESP_ELFSYM_EXPORT(lv_obj_set_scroll_dir), ESP_ELFSYM_EXPORT(lv_obj_set_style_radius), ESP_ELFSYM_EXPORT(lv_obj_set_style_border_width),