Merge develop into main (#377)

- Extract web server from `DevelopmentService` into a separate class: `HttpServer`
- Export more functions in `tt_init.cpp`
This commit is contained in:
Ken Van Hoeylandt 2025-10-16 18:59:23 +02:00 committed by GitHub
parent d8346998ce
commit 9c5a427a34
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 191 additions and 160 deletions

View File

@ -0,0 +1,71 @@
#pragma once
#ifdef ESP_PLATFORM
#include <esp_http_server.h>
#include <Tactility/Mutex.h>
#include <Tactility/kernel/SystemEvents.h>
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<httpd_uri_t> 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<httpd_uri_t> 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

View File

@ -7,49 +7,44 @@
#include <esp_event.h> #include <esp_event.h>
#include <esp_http_server.h> #include <esp_http_server.h>
#include <Tactility/kernel/SystemEvents.h> #include <Tactility/network/HttpServer.h>
namespace tt::service::development { namespace tt::service::development {
class DevelopmentService final : public Service { class DevelopmentService final : public Service {
Mutex mutex = Mutex(Mutex::Type::Recursive); 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; std::string deviceResponse;
network::HttpServer httpServer = network::HttpServer(
httpd_uri_t handleGetInfoEndpoint = { 6666,
.uri = "/info", "0.0.0.0",
.method = HTTP_GET, std::vector<httpd_uri_t>{
.handler = handleGetInfo, {
.user_ctx = this .uri = "/info",
}; .method = HTTP_GET,
.handler = handleGetInfo,
httpd_uri_t appRunEndpoint = { .user_ctx = this
.uri = "/app/run", },
.method = HTTP_POST, {
.handler = handleAppRun, .uri = "/app/run",
.user_ctx = this .method = HTTP_POST,
}; .handler = handleAppRun,
.user_ctx = this
httpd_uri_t appInstallEndpoint = { },
.uri = "/app/install", {
.method = HTTP_PUT, .uri = "/app/install",
.handler = handleAppInstall, .method = HTTP_PUT,
.user_ctx = this .handler = handleAppInstall,
}; .user_ctx = this
},
httpd_uri_t appUninstallEndpoint = { {
.uri = "/app/uninstall", .uri = "/app/uninstall",
.method = HTTP_PUT, .method = HTTP_PUT,
.handler = handleAppUninstall, .handler = handleAppUninstall,
.user_ctx = this .user_ctx = this
}; }
}
void onNetworkConnected(); );
void onNetworkDisconnected();
void startServer(); void startServer();
void stopServer(); void stopServer();
@ -64,6 +59,7 @@ public:
// region Overrides // region Overrides
bool onStart(ServiceContext& service) override; bool onStart(ServiceContext& service) override;
void onStop(ServiceContext& service) override; void onStop(ServiceContext& service) override;
// endregion Overrides // endregion Overrides
@ -81,10 +77,6 @@ public:
* @param[in] enabled * @param[in] enabled
*/ */
void setEnabled(bool enabled); void setEnabled(bool enabled);
bool isStarted() const;
// region Internal API
}; };
std::shared_ptr<DevelopmentService> findService(); std::shared_ptr<DevelopmentService> findService();

View File

@ -65,14 +65,14 @@ class DevelopmentApp final : public App {
void updateViewState() { void updateViewState() {
if (!service->isEnabled()) { if (!service->isEnabled()) {
lv_label_set_text(statusLabel, "Service disabled"); 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..."); lv_label_set_text(statusLabel, "Waiting for connection...");
} else { // enabled and started } else { // enabled and connected to wifi
auto ip = service::wifi::getIp(); auto ip = service::wifi::getIp();
if (ip.empty()) { if (ip.empty()) {
lv_label_set_text(statusLabel, "Waiting for IP..."); lv_label_set_text(statusLabel, "Waiting for IP...");
} else { } 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()); lv_label_set_text(statusLabel, status.c_str());
} }
} }

View File

@ -0,0 +1,58 @@
#ifdef ESP_PLATFORM
#include <Tactility/network/HttpServer.h>
#include <Tactility/service/wifi/Wifi.h>
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<httpd_uri_t>::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

View File

@ -9,17 +9,11 @@
#include <Tactility/network/Url.h> #include <Tactility/network/Url.h>
#include <Tactility/Paths.h> #include <Tactility/Paths.h>
#include <Tactility/service/development/DevelopmentSettings.h> #include <Tactility/service/development/DevelopmentSettings.h>
#include <Tactility/service/ServiceManifest.h>
#include <Tactility/service/ServiceRegistration.h> #include <Tactility/service/ServiceRegistration.h>
#include <Tactility/service/wifi/Wifi.h>
#include <Tactility/StringUtils.h> #include <Tactility/StringUtils.h>
#include <cstring>
#include <esp_wifi.h>
#include <ranges> #include <ranges>
#include <sstream> #include <sstream>
#include <Tactility/Tactility.h>
#include <Tactility/file/FileLock.h>
namespace tt::service::development { namespace tt::service::development {
@ -28,70 +22,6 @@ extern const ServiceManifest manifest;
constexpr const char* TAG = "DevService"; constexpr const char* TAG = "DevService";
bool DevelopmentService::onStart(ServiceContext& service) { 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; std::stringstream stream;
stream << "{"; stream << "{";
stream << "\"cpuFamily\":\"" << CONFIG_IDF_TARGET << "\", "; stream << "\"cpuFamily\":\"" << CONFIG_IDF_TARGET << "\", ";
@ -100,61 +30,36 @@ void DevelopmentService::startServer() {
stream << "}"; stream << "}";
deviceResponse = stream.str(); deviceResponse = stream.str();
httpd_config_t config = HTTPD_DEFAULT_CONFIG(); setEnabled(shouldEnableOnBoot());
config.stack_size = 5120;
config.server_port = 6666; return true;
config.uri_match_fn = httpd_uri_match_wildcard; }
if (httpd_start(&server, &config) == ESP_OK) { void DevelopmentService::onStop(ServiceContext& service) {
httpd_register_uri_handler(server, &handleGetInfoEndpoint); setEnabled(false);
httpd_register_uri_handler(server, &appRunEndpoint); }
httpd_register_uri_handler(server, &appInstallEndpoint);
httpd_register_uri_handler(server, &appUninstallEndpoint); // region Enable/disable
TT_LOG_I(TAG, "Started on port %d", config.server_port);
void DevelopmentService::setEnabled(bool enabled) {
auto lock = mutex.asScopedLock();
lock.lock();
if (enabled) {
if (!httpServer.isStarted()) {
httpServer.start();
}
} else { } else {
TT_LOG_E(TAG, "Failed to start"); if (httpServer.isStarted()) {
httpServer.stop();
}
} }
} }
void DevelopmentService::stopServer() { bool DevelopmentService::isEnabled() const {
auto lock = mutex.asScopedLock(); auto lock = mutex.asScopedLock();
lock.lock(); lock.lock();
return httpServer.isStarted();
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();
}
});
} }
// region endpoints // region endpoints

View File

@ -379,6 +379,11 @@ const esp_elfsym main_symbols[] {
ESP_ELFSYM_EXPORT(lv_obj_set_flex_align), ESP_ELFSYM_EXPORT(lv_obj_set_flex_align),
ESP_ELFSYM_EXPORT(lv_obj_set_flex_flow), ESP_ELFSYM_EXPORT(lv_obj_set_flex_flow),
ESP_ELFSYM_EXPORT(lv_obj_set_flex_grow), 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_scroll_dir),
ESP_ELFSYM_EXPORT(lv_obj_set_style_radius), ESP_ELFSYM_EXPORT(lv_obj_set_style_radius),
ESP_ELFSYM_EXPORT(lv_obj_set_style_border_width), ESP_ELFSYM_EXPORT(lv_obj_set_style_border_width),