diff --git a/Tactility/CMakeLists.txt b/Tactility/CMakeLists.txt index 56ee17ec..db26fe3e 100644 --- a/Tactility/CMakeLists.txt +++ b/Tactility/CMakeLists.txt @@ -6,7 +6,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) if (DEFINED ENV{ESP_IDF_VERSION}) file(GLOB_RECURSE SOURCE_FILES Source/*.c*) - list(APPEND REQUIRES_LIST TactilityCore lvgl driver elf_loader lv_screenshot QRCode esp_lvgl_port minmea esp_wifi nvs_flash spiffs vfs fatfs lwip) + list(APPEND REQUIRES_LIST TactilityCore lvgl driver elf_loader lv_screenshot QRCode esp_lvgl_port minmea esp_wifi nvs_flash spiffs vfs fatfs lwip esp_http_server) if ("${IDF_TARGET}" STREQUAL "esp32s3") list(APPEND REQUIRES_LIST esp_tinyusb) endif () diff --git a/Tactility/Private/Tactility/service/development/Development.h b/Tactility/Private/Tactility/service/development/Development.h new file mode 100644 index 00000000..b33dc424 --- /dev/null +++ b/Tactility/Private/Tactility/service/development/Development.h @@ -0,0 +1,62 @@ +#pragma once +#ifdef ESP_PLATFORM + +#include "Tactility/service/Service.h" + +#include + +#include +#include +#include + +namespace tt::service::development { + +class Development 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 getDeviceEndpoint = { + .uri = "/device", + .method = HTTP_GET, + .handler = getDevice, + .user_ctx = this + }; + + void onNetworkConnected(); + void onNetworkDisconnected(); + + void startServer(); + void stopServer(); + + static esp_err_t getDevice(httpd_req_t* request); + +public: + + // region Overrides + + void onStart(ServiceContext& service) override; + void onStop(ServiceContext& service) override; + + // endregion Overrides + + // region Internal API + + void setEnabled(bool enabled); + + bool isEnabled() const; + + bool isStarted() const; + + // region Internal API +}; + +std::shared_ptr findService(); + +} + +#endif // ESP_PLATFORM diff --git a/Tactility/Source/TactilityHeadless.cpp b/Tactility/Source/TactilityHeadless.cpp index 7b968f6e..237b110c 100644 --- a/Tactility/Source/TactilityHeadless.cpp +++ b/Tactility/Source/TactilityHeadless.cpp @@ -20,6 +20,7 @@ namespace service::gps { extern const ServiceManifest manifest; } namespace service::wifi { extern const ServiceManifest manifest; } namespace service::sdcard { extern const ServiceManifest manifest; } #ifdef ESP_PLATFORM +namespace service::development { extern const ServiceManifest manifest; } namespace service::espnow { extern const ServiceManifest manifest; } #endif @@ -33,6 +34,7 @@ static void registerAndStartSystemServices() { addService(service::sdcard::manifest); addService(service::wifi::manifest); #ifdef ESP_PLATFORM + addService(service::development::manifest); addService(service::espnow::manifest); #endif } diff --git a/Tactility/Source/lvgl/Statusbar.cpp b/Tactility/Source/lvgl/Statusbar.cpp index b485025f..8bf8d8c2 100644 --- a/Tactility/Source/lvgl/Statusbar.cpp +++ b/Tactility/Source/lvgl/Statusbar.cpp @@ -118,7 +118,7 @@ static void statusbar_pubsub_event(TT_UNUSED const void* message, void* obj) { } } -static void onNetworkConnected(TT_UNUSED kernel::SystemEvent event) { +static void onTimeChanged(TT_UNUSED kernel::SystemEvent event) { if (statusbar_data.mutex.lock(100 / portTICK_PERIOD_MS)) { statusbar_data.time_update_timer->stop(); statusbar_data.time_update_timer->start(5); @@ -139,7 +139,7 @@ static void statusbar_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj) statusbar_data.time_update_timer->start(50 / portTICK_PERIOD_MS); statusbar_data.systemEventSubscription = kernel::subscribeSystemEvent( kernel::SystemEvent::Time, - onNetworkConnected + onTimeChanged ); } } diff --git a/Tactility/Source/service/development/Development.cpp b/Tactility/Source/service/development/Development.cpp new file mode 100644 index 00000000..ec888330 --- /dev/null +++ b/Tactility/Source/service/development/Development.cpp @@ -0,0 +1,193 @@ +#ifdef ESP_PLATFORM + +#include "Tactility/service/development/Development.h" + +#include "Tactility/TactilityHeadless.h" +#include "Tactility/service/ServiceManifest.h" +#include "Tactility/service/ServiceRegistry.h" + +#include +#include +#include + +namespace tt::service::development { + +extern const ServiceManifest manifest; + +constexpr const char* TAG = "DevService"; + + +static char* rest_read_buffer(httpd_req_t* request) { + static char buffer[1024]; + int contentLength = request->content_len; + int currentLength = 0; + int received = 0; + if (contentLength >= 1024) { + // Respond with 500 Internal Server Error + httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "content too long"); + return NULL; + } + while (currentLength < contentLength) { + received = httpd_req_recv(request, buffer + currentLength, contentLength); + if (received <= 0) { + // Respond with 500 Internal Server Error + httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to post control value"); + return NULL; + } + currentLength += received; + } + buffer[contentLength] = '\0'; + return buffer; +} + +void Development::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(true); +} + +void Development::onStop(ServiceContext& service) { + auto lock = mutex.asScopedLock(); + lock.lock(); + + kernel::unsubscribeSystemEvent(networkConnectEventSubscription); + kernel::unsubscribeSystemEvent(networkDisconnectEventSubscription); + + if (isEnabled()) { + setEnabled(false); + } +} + +// region Enable/disable + +void Development::setEnabled(bool enabled) { + auto lock = mutex.asScopedLock(); + lock.lock(); + this->enabled = enabled; +} + +bool Development::isEnabled() const { + auto lock = mutex.asScopedLock(); + lock.lock(); + return enabled; +} + +// region Enable/disable + +// region Handlers + +void Development::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 << "\""; + stream << "}"; + deviceResponse = stream.str(); + + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + + config.server_port = 6666; + config.uri_match_fn = httpd_uri_match_wildcard; + + if (httpd_start(&server, &config) == ESP_OK) { + httpd_register_uri_handler(server, &getDeviceEndpoint); + TT_LOG_I(TAG, "Started on port %d", config.server_port); + } else { + TT_LOG_E(TAG, "Failed to start"); + } +} + +void Development::stopServer() { + 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 Development::isStarted() const { + auto lock = mutex.asScopedLock(); + lock.lock(); + return server != nullptr; +} + +void Development::onNetworkConnected() { + TT_LOG_I(TAG, "onNetworkConnected"); + mutex.withLock([this] { + if (isEnabled() && !isStarted()) { + startServer(); + } + }); +} + +void Development::onNetworkDisconnected() { + TT_LOG_I(TAG, "onNetworkDisconnected"); + mutex.withLock([this] { + if (isStarted()) { + stopServer(); + } + }); +} + +// endregion + +// region endpoints + +esp_err_t Development::getDevice(httpd_req_t* request) { + if (httpd_resp_set_type(request, "application/json") != ESP_OK) { + TT_LOG_W(TAG, "Failed to send header"); + return ESP_FAIL; + } + + auto* service = static_cast(request->user_ctx); + + if (httpd_resp_sendstr(request, service->deviceResponse.c_str()) != ESP_OK) { + TT_LOG_W(TAG, "Failed to send response body"); + return ESP_FAIL; + } + + TT_LOG_I(TAG, "[200] /device from"); + return ESP_OK; +} + +std::shared_ptr findService() { + return std::static_pointer_cast( + findServiceById(manifest.id) + ); +} + +extern const ServiceManifest manifest = { + .id = "Development", + .createService = create +}; + +} + +#endif // ESP_PLATFORM diff --git a/Tactility/Source/service/wifi/WifiEsp.cpp b/Tactility/Source/service/wifi/WifiEsp.cpp index 1573b3b0..959ae1aa 100644 --- a/Tactility/Source/service/wifi/WifiEsp.cpp +++ b/Tactility/Source/service/wifi/WifiEsp.cpp @@ -12,6 +12,7 @@ #include #include +#include #include namespace tt::service::wifi { @@ -476,6 +477,7 @@ static void eventHandler(TT_UNUSED void* arg, esp_event_base_t event_base, int32 } wifi->setRadioState(RadioState::On); publish_event_simple(wifi, EventType::Disconnected); + kernel::publishSystemEvent(kernel::SystemEvent::NetworkDisconnected); } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { auto* event = static_cast(event_data); TT_LOG_I(TAG, "eventHandler: got ip:" IPSTR, IP2STR(&event->ip_info.ip)); @@ -485,6 +487,7 @@ static void eventHandler(TT_UNUSED void* arg, esp_event_base_t event_base, int32 // TODO: Make thread-safe wifi->pause_auto_connect = false; // Resume auto-connection } + kernel::publishSystemEvent(kernel::SystemEvent::NetworkConnected); } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_SCAN_DONE) { auto* event = static_cast(event_data); TT_LOG_I(TAG, "eventHandler: wifi scanning done (scan id %u)", event->scan_id);