diff --git a/Tactility/Private/app/AppInstance.h b/Tactility/Private/app/AppInstance.h index 1408fdd6..0b98b89f 100644 --- a/Tactility/Private/app/AppInstance.h +++ b/Tactility/Private/app/AppInstance.h @@ -34,12 +34,6 @@ private: * Do not mutate after app creation. */ std::shared_ptr _Nullable parameters; - /** @brief @brief Contextual data related to the running app's instance - * The app can attach its data to this. - * The lifecycle is determined by the on_start and on_stop methods in the AppManifest. - * These manifest methods can optionally allocate/free data that is attached here. - */ - std::shared_ptr _Nullable data; std::shared_ptr app; diff --git a/Tactility/Source/Tactility.cpp b/Tactility/Source/Tactility.cpp index 7c9baafc..247facfa 100644 --- a/Tactility/Source/Tactility.cpp +++ b/Tactility/Source/Tactility.cpp @@ -23,15 +23,6 @@ namespace service { #endif } -static const std::vector system_services = { - &service::loader::manifest, - &service::gui::manifest, // depends on loader service - &service::statusbar::manifest, -#if TT_FEATURE_SCREENSHOT_ENABLED - &service::screenshot::manifest -#endif -}; - // endregion // region Default apps @@ -125,18 +116,19 @@ static void register_user_apps(const std::vector& apps) static void register_and_start_system_services() { TT_LOG_I(TAG, "Registering and starting system services"); - for (const auto& service_manifest: system_services) { - addService(service_manifest); - tt_check(service::startService(service_manifest->id)); - } + addService(service::loader::manifest); + addService(service::gui::manifest); + addService(service::statusbar::manifest); +#if TT_FEATURE_SCREENSHOT_ENABLED + addService(service::screenshot::manifest); +#endif } -static void register_and_start_user_services(const std::vector& services) { +static void register_and_start_user_services(const std::vector& manifests) { TT_LOG_I(TAG, "Registering and starting user services"); - for (auto* manifest : services) { + for (auto* manifest : manifests) { assert(manifest != nullptr); - addService(manifest); - tt_check(service::startService(manifest->id)); + addService(*manifest); } } diff --git a/Tactility/Source/service/gui/Gui.cpp b/Tactility/Source/service/gui/Gui.cpp index 82139fc8..bb71f136 100644 --- a/Tactility/Source/service/gui/Gui.cpp +++ b/Tactility/Source/service/gui/Gui.cpp @@ -142,30 +142,36 @@ static int32_t guiMain(TT_UNUSED void* p) { // region AppManifest -static void start(TT_UNUSED ServiceContext& service) { - gui = gui_alloc(); +class GuiService : public Service { - gui->thread->setPriority(THREAD_PRIORITY_SERVICE); - gui->thread->start(); -} +public: -static void stop(TT_UNUSED ServiceContext& service) { - lock(); + void onStart(TT_UNUSED ServiceContext& service) override { + tt_assert(gui == nullptr); + gui = gui_alloc(); - ThreadId thread_id = gui->thread->getId(); - thread_flags_set(thread_id, GUI_THREAD_FLAG_EXIT); - gui->thread->join(); - delete gui->thread; + gui->thread->setPriority(THREAD_PRIORITY_SERVICE); + gui->thread->start(); + } - unlock(); + void onStop(TT_UNUSED ServiceContext& service) override { + tt_assert(gui != nullptr); + lock(); - gui_free(gui); -} + ThreadId thread_id = gui->thread->getId(); + thread_flags_set(thread_id, GUI_THREAD_FLAG_EXIT); + gui->thread->join(); + delete gui->thread; + + unlock(); + + gui_free(gui); + } +}; extern const ServiceManifest manifest = { .id = "Gui", - .onStart = &start, - .onStop = &stop + .createService = create }; // endregion diff --git a/Tactility/Source/service/loader/Loader.cpp b/Tactility/Source/service/loader/Loader.cpp index 6099486c..786f3a95 100644 --- a/Tactility/Source/service/loader/Loader.cpp +++ b/Tactility/Source/service/loader/Loader.cpp @@ -295,31 +295,35 @@ static void stopAppInternal() { // region AppManifest -static void loader_start(TT_UNUSED ServiceContext& service) { - tt_check(loader_singleton == nullptr); - loader_singleton = loader_alloc(); - loader_singleton->dispatcherThread->start(); -} +class LoaderService final : public Service { -static void loader_stop(TT_UNUSED ServiceContext& service) { - tt_check(loader_singleton != nullptr); +public: - // Send stop signal to thread and wait for thread to finish - if (!loader_singleton->mutex.lock(2000 / portTICK_PERIOD_MS)) { - TT_LOG_W(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "loader_stop"); + void onStart(TT_UNUSED ServiceContext& service) final { + tt_check(loader_singleton == nullptr); + loader_singleton = loader_alloc(); + loader_singleton->dispatcherThread->start(); } - loader_singleton->dispatcherThread->stop(); - loader_singleton->mutex.unlock(); + void onStop(TT_UNUSED ServiceContext& service) final { + tt_check(loader_singleton != nullptr); - loader_free(); - loader_singleton = nullptr; -} + // Send stop signal to thread and wait for thread to finish + if (!loader_singleton->mutex.lock(2000 / portTICK_PERIOD_MS)) { + TT_LOG_W(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "loader_stop"); + } + loader_singleton->dispatcherThread->stop(); + + loader_singleton->mutex.unlock(); + + loader_free(); + loader_singleton = nullptr; + } +}; extern const ServiceManifest manifest = { .id = "Loader", - .onStart = &loader_start, - .onStop = &loader_stop + .createService = create }; // endregion diff --git a/Tactility/Source/service/screenshot/Screenshot.cpp b/Tactility/Source/service/screenshot/Screenshot.cpp index 71c3ee53..05ffc459 100644 --- a/Tactility/Source/service/screenshot/Screenshot.cpp +++ b/Tactility/Source/service/screenshot/Screenshot.cpp @@ -15,12 +15,7 @@ namespace tt::service::screenshot { extern const ServiceManifest manifest; std::shared_ptr _Nullable optScreenshotService() { - ServiceContext* context = service::findServiceById(manifest.id); - if (context != nullptr) { - return std::static_pointer_cast(context->getData()); - } else { - return nullptr; - } + return service::findServiceById(manifest.id); } void ScreenshotService::startApps(const char* path) { @@ -89,14 +84,9 @@ bool ScreenshotService::isTaskStarted() { } } -static void onStart(ServiceContext& serviceContext) { - auto service = std::make_shared(); - serviceContext.setData(service); -} - extern const ServiceManifest manifest = { .id = "Screenshot", - .onStart = onStart + .createService = create }; } // namespace diff --git a/Tactility/Source/service/screenshot/Screenshot.h b/Tactility/Source/service/screenshot/Screenshot.h index c9850e2f..53384fad 100644 --- a/Tactility/Source/service/screenshot/Screenshot.h +++ b/Tactility/Source/service/screenshot/Screenshot.h @@ -1,11 +1,12 @@ #pragma once -#include "Mutex.h" -#include "ScreenshotTask.h" #include "TactilityConfig.h" #if TT_FEATURE_SCREENSHOT_ENABLED +#include "Mutex.h" +#include "ScreenshotTask.h" +#include "service/Service.h" #include namespace tt::service::screenshot { @@ -16,7 +17,7 @@ enum class Mode { Apps }; -class ScreenshotService { +class ScreenshotService final : public Service { private: diff --git a/Tactility/Source/service/statusbar/Statusbar.cpp b/Tactility/Source/service/statusbar/Statusbar.cpp index ae8e9577..2abfb4c9 100644 --- a/Tactility/Source/service/statusbar/Statusbar.cpp +++ b/Tactility/Source/service/statusbar/Statusbar.cpp @@ -41,35 +41,6 @@ namespace tt::service::statusbar { extern const ServiceManifest manifest; -struct ServiceData { - Mutex mutex; - std::unique_ptr updateTimer; - int8_t wifi_icon_id = lvgl::statusbar_icon_add(); - const char* wifi_last_icon = nullptr; - int8_t sdcard_icon_id = lvgl::statusbar_icon_add(); - const char* sdcard_last_icon = nullptr; - int8_t power_icon_id = lvgl::statusbar_icon_add(); - const char* power_last_icon = nullptr; - - std::unique_ptr paths; - - ~ServiceData() { - lvgl::statusbar_icon_remove(wifi_icon_id); - lvgl::statusbar_icon_remove(sdcard_icon_id); - lvgl::statusbar_icon_remove(power_icon_id); - } - - void lock() const { - tt_check(mutex.acquire(TtWaitForever) == TtStatusOk); - } - - void unlock() const { - tt_check(mutex.release() == TtStatusOk); - } -}; - -// region wifi - const char* getWifiStatusIconForRssi(int rssi) { if (rssi >= -60) { return STATUSBAR_ICON_WIFI_SIGNAL_STRONG_WHITE; @@ -98,26 +69,6 @@ static const char* getWifiStatusIcon(wifi::RadioState state, bool secure) { } } -static void updateWifiIcon(const service::Paths* paths, const std::shared_ptr& data) { - wifi::RadioState radio_state = wifi::getRadioState(); - bool is_secure = wifi::isConnectionSecure(); - const char* desired_icon = getWifiStatusIcon(radio_state, is_secure); - if (data->wifi_last_icon != desired_icon) { - if (desired_icon != nullptr) { - auto icon_path = paths->getSystemPathLvgl(desired_icon); - lvgl::statusbar_icon_set_image(data->wifi_icon_id, icon_path); - lvgl::statusbar_icon_set_visibility(data->wifi_icon_id, true); - } else { - lvgl::statusbar_icon_set_visibility(data->wifi_icon_id, false); - } - data->wifi_last_icon = desired_icon; - } -} - -// endregion wifi - -// region sdcard - static const char* getSdCardStatusIcon(hal::SdCard::State state) { switch (state) { case hal::SdCard::State::Mounted: @@ -131,27 +82,6 @@ static const char* getSdCardStatusIcon(hal::SdCard::State state) { } } -static void updateSdCardIcon(const service::Paths* paths, const std::shared_ptr& data) { - auto sdcard = tt::hal::getConfiguration()->sdcard; - if (sdcard != nullptr) { - auto state = sdcard->getState(); - if (state != hal::SdCard::State::Unknown) { - auto* desired_icon = getSdCardStatusIcon(state); - if (data->sdcard_last_icon != desired_icon) { - auto icon_path = paths->getSystemPathLvgl(desired_icon); - lvgl::statusbar_icon_set_image(data->sdcard_icon_id, icon_path); - lvgl::statusbar_icon_set_visibility(data->sdcard_icon_id, true); - data->sdcard_last_icon = desired_icon; - } - } - // TODO: Consider tracking how long the SD card has been in unknown status and then show error - } -} - -// endregion sdcard - -// region power - static _Nullable const char* getPowerStatusIcon() { auto get_power = getConfiguration()->hardware->power; if (get_power == nullptr) { @@ -192,69 +122,120 @@ static _Nullable const char* getPowerStatusIcon() { } } -static void updatePowerStatusIcon(const service::Paths* paths, const std::shared_ptr& data) { - const char* desired_icon = getPowerStatusIcon(); - if (data->power_last_icon != desired_icon) { - if (desired_icon != nullptr) { - auto icon_path = paths->getSystemPathLvgl(desired_icon); - lvgl::statusbar_icon_set_image(data->power_icon_id, icon_path); - lvgl::statusbar_icon_set_visibility(data->power_icon_id, true); - } else { - lvgl::statusbar_icon_set_visibility(data->power_icon_id, false); - } - data->power_last_icon = desired_icon; +class StatusbarService final : public Service { + +private: + + Mutex mutex; + std::unique_ptr updateTimer; + int8_t wifi_icon_id = lvgl::statusbar_icon_add(); + const char* wifi_last_icon = nullptr; + int8_t sdcard_icon_id = lvgl::statusbar_icon_add(); + const char* sdcard_last_icon = nullptr; + int8_t power_icon_id = lvgl::statusbar_icon_add(); + const char* power_last_icon = nullptr; + + std::unique_ptr paths; + + void lock() const { + tt_check(mutex.acquire(TtWaitForever) == TtStatusOk); } -} -// endregion power + void unlock() const { + tt_check(mutex.release() == TtStatusOk); + } -// region service + void updateWifiIcon() { + wifi::RadioState radio_state = wifi::getRadioState(); + bool is_secure = wifi::isConnectionSecure(); + const char* desired_icon = getWifiStatusIcon(radio_state, is_secure); + if (wifi_last_icon != desired_icon) { + if (desired_icon != nullptr) { + auto icon_path = paths->getSystemPathLvgl(desired_icon); + lvgl::statusbar_icon_set_image(wifi_icon_id, icon_path); + lvgl::statusbar_icon_set_visibility(wifi_icon_id, true); + } else { + lvgl::statusbar_icon_set_visibility(wifi_icon_id, false); + } + wifi_last_icon = desired_icon; + } + } -static void service_data_free(ServiceData* data) { - free(data); -} + void updatePowerStatusIcon() { + const char* desired_icon = getPowerStatusIcon(); + if (power_last_icon != desired_icon) { + if (desired_icon != nullptr) { + auto icon_path = paths->getSystemPathLvgl(desired_icon); + lvgl::statusbar_icon_set_image(power_icon_id, icon_path); + lvgl::statusbar_icon_set_visibility(power_icon_id, true); + } else { + lvgl::statusbar_icon_set_visibility(power_icon_id, false); + } + power_last_icon = desired_icon; + } + } -static void onUpdate(std::shared_ptr parameter) { - auto data = std::static_pointer_cast(parameter); - // TODO: Make thread-safe for LVGL - auto* paths = data->paths.get(); - updateWifiIcon(paths, data); - updateSdCardIcon(paths, data); - updatePowerStatusIcon(paths, data); -} + void updateSdCardIcon() { + auto sdcard = tt::hal::getConfiguration()->sdcard; + if (sdcard != nullptr) { + auto state = sdcard->getState(); + if (state != hal::SdCard::State::Unknown) { + auto* desired_icon = getSdCardStatusIcon(state); + if (sdcard_last_icon != desired_icon) { + auto icon_path = paths->getSystemPathLvgl(desired_icon); + lvgl::statusbar_icon_set_image(sdcard_icon_id, icon_path); + lvgl::statusbar_icon_set_visibility(sdcard_icon_id, true); + sdcard_last_icon = desired_icon; + } + } + // TODO: Consider tracking how long the SD card has been in unknown status and then show error + } + } -static void onStart(ServiceContext& service) { - lvgl::lock(TtWaitForever); - auto data = std::make_shared(); - lvgl::unlock(); + void update() { + // TODO: Make thread-safe for LVGL + updateWifiIcon(); + updateSdCardIcon(); + updatePowerStatusIcon(); + } - data->paths = service.getPaths(); + static void onUpdate(std::shared_ptr parameter) { + auto service = std::static_pointer_cast(parameter); + service->update(); + } - service.setData(data); +public: - // TODO: Make thread-safe for LVGL - lvgl::statusbar_icon_set_visibility(data->wifi_icon_id, true); - updateWifiIcon(data->paths.get(), data); - updateSdCardIcon(data->paths.get(), data); // also updates visibility - updatePowerStatusIcon(data->paths.get(), data); + ~StatusbarService() final { + lvgl::statusbar_icon_remove(wifi_icon_id); + lvgl::statusbar_icon_remove(sdcard_icon_id); + lvgl::statusbar_icon_remove(power_icon_id); + } - data->updateTimer = std::make_unique(Timer::Type::Periodic, onUpdate, data); - // We want to try and scan more often in case of startup or scan lock failure - data->updateTimer->start(1000); -} + void onStart(ServiceContext& serviceContext) override { + paths = serviceContext.getPaths(); -static void onStop(ServiceContext& service) { - auto data = std::static_pointer_cast(service.getData()); + // TODO: Make thread-safe for LVGL + lvgl::statusbar_icon_set_visibility(wifi_icon_id, true); - // Stop thread - data->updateTimer->stop(); - data->updateTimer = nullptr; -} + auto service = findServiceById(manifest.id); + assert(service); + onUpdate(service); + + updateTimer = std::make_unique(Timer::Type::Periodic, onUpdate, service); + // We want to try and scan more often in case of startup or scan lock failure + updateTimer->start(1000); + } + + void onStop(ServiceContext& service) override{ + updateTimer->stop(); + updateTimer = nullptr; + } +}; extern const ServiceManifest manifest = { .id = "Statusbar", - .onStart = onStart, - .onStop = onStop + .createService = create }; // endregion service diff --git a/TactilityHeadless/Private/service/ServiceInstance.h b/TactilityHeadless/Private/service/ServiceInstance.h index 48ecadd0..fb19724e 100644 --- a/TactilityHeadless/Private/service/ServiceInstance.h +++ b/TactilityHeadless/Private/service/ServiceInstance.h @@ -1,6 +1,7 @@ #pragma once #include "service/ServiceContext.h" +#include "service/Service.h" namespace tt::service { @@ -9,19 +10,21 @@ class ServiceInstance : public ServiceContext { private: Mutex mutex = Mutex(Mutex::Type::Normal); - const service::ServiceManifest& manifest; - std::shared_ptr data = nullptr; + std::shared_ptr manifest; + std::shared_ptr service; public: - explicit ServiceInstance(const service::ServiceManifest& manifest); + explicit ServiceInstance(std::shared_ptr manifest); ~ServiceInstance() override = default; + /** @return a reference ot the service's manifest */ const service::ServiceManifest& getManifest() const override; - std::shared_ptr getData() const override; - void setData(std::shared_ptr newData) override; + /** Retrieve the paths that are relevant to this service */ std::unique_ptr getPaths() const override; + + std::shared_ptr getService() const { return service; } }; } diff --git a/TactilityHeadless/Private/service/ServiceInstancePaths.h b/TactilityHeadless/Private/service/ServiceInstancePaths.h index ee95f16b..f215d034 100644 --- a/TactilityHeadless/Private/service/ServiceInstancePaths.h +++ b/TactilityHeadless/Private/service/ServiceInstancePaths.h @@ -8,11 +8,11 @@ class ServiceInstancePaths final : public Paths { private: - const ServiceManifest& manifest; + std::shared_ptr manifest; public: - explicit ServiceInstancePaths(const ServiceManifest& manifest) : manifest(manifest) {} + explicit ServiceInstancePaths(std::shared_ptr manifest) : manifest(std::move(manifest)) {} ~ServiceInstancePaths() final = default; std::string getDataDirectory() const final; diff --git a/TactilityHeadless/Source/TactilityHeadless.cpp b/TactilityHeadless/Source/TactilityHeadless.cpp index bed67ea6..f59ed52a 100644 --- a/TactilityHeadless/Source/TactilityHeadless.cpp +++ b/TactilityHeadless/Source/TactilityHeadless.cpp @@ -21,20 +21,12 @@ namespace service::sdcard { extern const ServiceManifest manifest; } static Dispatcher mainDispatcher; -static const service::ServiceManifest* const system_services[] = { - &service::sdcard::manifest, - &service::wifi::manifest -}; - static const hal::Configuration* hardwareConfig = nullptr; static void register_and_start_system_services() { TT_LOG_I(TAG, "Registering and starting system services"); - int app_count = sizeof(system_services) / sizeof(service::ServiceManifest*); - for (int i = 0; i < app_count; ++i) { - addService(system_services[i]); - tt_check(service::startService(system_services[i]->id)); - } + addService(service::sdcard::manifest); + addService(service::wifi::manifest); } void initHeadless(const hal::Configuration& config) { diff --git a/TactilityHeadless/Source/service/Service.h b/TactilityHeadless/Source/service/Service.h new file mode 100644 index 00000000..c5983c4a --- /dev/null +++ b/TactilityHeadless/Source/service/Service.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +namespace tt::service { + +// Forward declaration +class ServiceContext; + +class Service { + +public: + + Service() = default; + virtual ~Service() = default; + + virtual void onStart(ServiceContext& serviceContext) {} + virtual void onStop(ServiceContext& serviceContext) {} +}; + +template +std::shared_ptr create() { return std::shared_ptr(new T); } + +} \ No newline at end of file diff --git a/TactilityHeadless/Source/service/ServiceContext.h b/TactilityHeadless/Source/service/ServiceContext.h index 23b9a419..7a421d78 100644 --- a/TactilityHeadless/Source/service/ServiceContext.h +++ b/TactilityHeadless/Source/service/ServiceContext.h @@ -22,10 +22,7 @@ public: /** @return a reference ot the service's manifest */ virtual const service::ServiceManifest& getManifest() const = 0; - /** @return a shared pointer to the data that is attached to the service */ - virtual std::shared_ptr _Nullable getData() const = 0; - /** Set the data for a service. */ - virtual void setData(std::shared_ptr newData) = 0; + /** Retrieve the paths that are relevant to this service */ virtual std::unique_ptr getPaths() const = 0; }; diff --git a/TactilityHeadless/Source/service/ServiceInstance.cpp b/TactilityHeadless/Source/service/ServiceInstance.cpp index 5dea1c8f..08ebbe6d 100644 --- a/TactilityHeadless/Source/service/ServiceInstance.cpp +++ b/TactilityHeadless/Source/service/ServiceInstance.cpp @@ -1,26 +1,14 @@ -#include - #include "service/ServiceInstance.h" #include "service/ServiceInstancePaths.h" namespace tt::service { -ServiceInstance::ServiceInstance(const service::ServiceManifest&manifest) : manifest(manifest) {} +ServiceInstance::ServiceInstance(std::shared_ptr manifest) : + manifest(manifest), + service(manifest->createService()) +{} -const service::ServiceManifest& ServiceInstance::getManifest() const { return manifest; } - -std::shared_ptr ServiceInstance::getData() const { - mutex.acquire(TtWaitForever); - std::shared_ptr result = data; - mutex.release(); - return result; -} - -void ServiceInstance::setData(std::shared_ptr newData) { - mutex.acquire(TtWaitForever); - data = std::move(newData); - mutex.release(); -} +const service::ServiceManifest& ServiceInstance::getManifest() const { return *manifest; } std::unique_ptr ServiceInstance::getPaths() const { return std::make_unique(manifest); diff --git a/TactilityHeadless/Source/service/ServiceInstancePaths.cpp b/TactilityHeadless/Source/service/ServiceInstancePaths.cpp index 9c1faaf1..f575ed32 100644 --- a/TactilityHeadless/Source/service/ServiceInstancePaths.cpp +++ b/TactilityHeadless/Source/service/ServiceInstancePaths.cpp @@ -11,38 +11,38 @@ namespace tt::service { std::string ServiceInstancePaths::getDataDirectory() const { - return PARTITION_PREFIX + DATA_PARTITION_NAME + "/service/" + manifest.id; + return PARTITION_PREFIX + DATA_PARTITION_NAME + "/service/" + manifest->id; } std::string ServiceInstancePaths::getDataDirectoryLvgl() const { - return LVGL_PATH_PREFIX + DATA_PARTITION_NAME + "/service/" + manifest.id; + return LVGL_PATH_PREFIX + DATA_PARTITION_NAME + "/service/" + manifest->id; } std::string ServiceInstancePaths::getDataPath(const std::string& childPath) const { assert(!childPath.starts_with('/')); - return PARTITION_PREFIX + DATA_PARTITION_NAME + "/service/" + manifest.id + '/' + childPath; + return PARTITION_PREFIX + DATA_PARTITION_NAME + "/service/" + manifest->id + '/' + childPath; } std::string ServiceInstancePaths::getDataPathLvgl(const std::string& childPath) const { assert(!childPath.starts_with('/')); - return LVGL_PATH_PREFIX + DATA_PARTITION_NAME + "/service/" + manifest.id + '/' + childPath; + return LVGL_PATH_PREFIX + DATA_PARTITION_NAME + "/service/" + manifest->id + '/' + childPath; } std::string ServiceInstancePaths::getSystemDirectory() const { - return PARTITION_PREFIX + SYSTEM_PARTITION_NAME + "/service/" + manifest.id; + return PARTITION_PREFIX + SYSTEM_PARTITION_NAME + "/service/" + manifest->id; } std::string ServiceInstancePaths::getSystemDirectoryLvgl() const { - return LVGL_PATH_PREFIX + SYSTEM_PARTITION_NAME + "/service/" + manifest.id; + return LVGL_PATH_PREFIX + SYSTEM_PARTITION_NAME + "/service/" + manifest->id; } std::string ServiceInstancePaths::getSystemPath(const std::string& childPath) const { assert(!childPath.starts_with('/')); - return PARTITION_PREFIX + SYSTEM_PARTITION_NAME + "/service/" + manifest.id + '/' + childPath; + return PARTITION_PREFIX + SYSTEM_PARTITION_NAME + "/service/" + manifest->id + '/' + childPath; } std::string ServiceInstancePaths::getSystemPathLvgl(const std::string& childPath) const { - return LVGL_PATH_PREFIX + SYSTEM_PARTITION_NAME + "/service/" + manifest.id + '/' + childPath; + return LVGL_PATH_PREFIX + SYSTEM_PARTITION_NAME + "/service/" + manifest->id + '/' + childPath; } } diff --git a/TactilityHeadless/Source/service/ServiceManifest.h b/TactilityHeadless/Source/service/ServiceManifest.h index 50da638a..d1f7f38d 100644 --- a/TactilityHeadless/Source/service/ServiceManifest.h +++ b/TactilityHeadless/Source/service/ServiceManifest.h @@ -1,24 +1,22 @@ #pragma once +#include "service/Service.h" #include namespace tt::service { +// Forward declarations class ServiceContext; -typedef void (*ServiceOnStart)(ServiceContext& service); -typedef void (*ServiceOnStop)(ServiceContext& service); +typedef std::shared_ptr(*CreateService)(); /** A ledger that describes the main parts of a service. */ struct ServiceManifest { /** The identifier by which the app is launched by the system and other apps. */ std::string id {}; - /** Non-blocking method to call when service is started. */ - const ServiceOnStart onStart = nullptr; - - /** Non-blocking method to call when service is stopped. */ - const ServiceOnStop onStop = nullptr; + /** Create the instance of the app */ + CreateService createService = nullptr; }; } // namespace diff --git a/TactilityHeadless/Source/service/ServiceRegistry.cpp b/TactilityHeadless/Source/service/ServiceRegistry.cpp index 86793b76..93d28efe 100644 --- a/TactilityHeadless/Source/service/ServiceRegistry.cpp +++ b/TactilityHeadless/Source/service/ServiceRegistry.cpp @@ -3,7 +3,6 @@ #include "Mutex.h" #include "service/ServiceInstance.h" #include "service/ServiceManifest.h" -#include "TactilityCore.h" #include #include @@ -11,8 +10,8 @@ namespace tt::service { #define TAG "service_registry" -typedef std::unordered_map ManifestMap; -typedef std::unordered_map ServiceInstanceMap; +typedef std::unordered_map> ManifestMap; +typedef std::unordered_map> ServiceInstanceMap; static ManifestMap service_manifest_map; static ServiceInstanceMap service_instance_map; @@ -20,30 +19,41 @@ static ServiceInstanceMap service_instance_map; static Mutex manifest_mutex(Mutex::Type::Normal); static Mutex instance_mutex(Mutex::Type::Normal); -void addService(const ServiceManifest* manifest) { - TT_LOG_I(TAG, "Adding %s", manifest->id.c_str()); +void addService(std::shared_ptr manifest, bool autoStart) { + // We'll move the manifest pointer, but we'll need to id later + std::string id = manifest->id; - manifest_mutex.acquire(TtWaitForever); - if (service_manifest_map[manifest->id] == nullptr) { - service_manifest_map[manifest->id] = manifest; + TT_LOG_I(TAG, "Adding %s", id.c_str()); + + manifest_mutex.lock(TtWaitForever); + if (service_manifest_map[id] == nullptr) { + service_manifest_map[id] = std::move(manifest); } else { - TT_LOG_E(TAG, "Service id in use: %s", manifest->id.c_str()); + TT_LOG_E(TAG, "Service id in use: %s", id.c_str()); + } + manifest_mutex.unlock(); + + if (autoStart) { + startService(id); } - manifest_mutex.release(); } -const ServiceManifest* _Nullable findManifestId(const std::string& id) { +void addService(const ServiceManifest& manifest, bool autoStart) { + addService(std::make_shared(manifest), autoStart); +} + +std::shared_ptr _Nullable findManifestId(const std::string& id) { manifest_mutex.acquire(TtWaitForever); auto iterator = service_manifest_map.find(id); - _Nullable const ServiceManifest * manifest = iterator != service_manifest_map.end() ? iterator->second : nullptr; + auto manifest = iterator != service_manifest_map.end() ? iterator->second : nullptr; manifest_mutex.release(); return manifest; } -static ServiceInstance* _Nullable service_registry_find_instance_by_id(const std::string& id) { +static std::shared_ptr _Nullable findServiceInstanceById(const std::string& id) { manifest_mutex.acquire(TtWaitForever); auto iterator = service_instance_map.find(id); - _Nullable ServiceInstance* service = iterator != service_instance_map.end() ? iterator->second : nullptr; + auto service = iterator != service_instance_map.end() ? iterator->second : nullptr; manifest_mutex.release(); return service; } @@ -51,42 +61,53 @@ static ServiceInstance* _Nullable service_registry_find_instance_by_id(const std // TODO: Return proper error/status instead of BOOL? bool startService(const std::string& id) { TT_LOG_I(TAG, "Starting %s", id.c_str()); - const ServiceManifest* manifest = findManifestId(id); + auto manifest = findManifestId(id); if (manifest == nullptr) { TT_LOG_E(TAG, "manifest not found for service %s", id.c_str()); return false; } - auto* service = new ServiceInstance(*manifest); - manifest->onStart(*service); + auto service_instance = std::make_shared(manifest); + // Register first, so that a service can retrieve itself during onStart() instance_mutex.acquire(TtWaitForever); - service_instance_map[manifest->id] = service; + service_instance_map[manifest->id] = service_instance; instance_mutex.release(); + + service_instance->getService()->onStart(*service_instance); + TT_LOG_I(TAG, "Started %s", id.c_str()); return true; } -_Nullable ServiceContext* findServiceById(const std::string& service_id) { - return static_cast(service_registry_find_instance_by_id(service_id)); +std::shared_ptr _Nullable findServiceContextById(const std::string& id) { + return findServiceInstanceById(id); +} + +std::shared_ptr _Nullable findServiceById(const std::string& id) { + auto instance = findServiceInstanceById(id); + return instance != nullptr ? instance->getService() : nullptr; } bool stopService(const std::string& id) { TT_LOG_I(TAG, "Stopping %s", id.c_str()); - ServiceInstance* service = service_registry_find_instance_by_id(id); - if (service == nullptr) { + auto service_instance = findServiceInstanceById(id); + if (service_instance == nullptr) { TT_LOG_W(TAG, "service not running: %s", id.c_str()); return false; } - service->getManifest().onStop(*service); - delete service; + service_instance->getService()->onStop(*service_instance); instance_mutex.acquire(TtWaitForever); service_instance_map.erase(id); instance_mutex.release(); + if (service_instance.use_count() > 1) { + TT_LOG_W(TAG, "Possible memory leak: service %s still has %ld references", service_instance->getManifest().id.c_str(), service_instance.use_count() - 1); + } + TT_LOG_I(TAG, "Stopped %s", id.c_str()); return true; diff --git a/TactilityHeadless/Source/service/ServiceRegistry.h b/TactilityHeadless/Source/service/ServiceRegistry.h index 6d9e1e9e..99b777cc 100644 --- a/TactilityHeadless/Source/service/ServiceRegistry.h +++ b/TactilityHeadless/Source/service/ServiceRegistry.h @@ -1,20 +1,20 @@ #pragma once #include "service/ServiceManifest.h" +#include "service/Service.h" +#include namespace tt::service { -void initRegistry(); - /** Register a service. * @param[in] the service manifest */ -void addService(const ServiceManifest* manifest); +void addService(std::shared_ptr manifest, bool autoStart = true); -/** Unregister a service. +/** Register a service. * @param[in] the service manifest */ -void removeService(const ServiceManifest* manifest); +void addService(const ServiceManifest& manifest, bool autoStart = true); /** Start a service. * @param[in] the service id as defined in its manifest @@ -32,12 +32,27 @@ bool stopService(const std::string& id); * @param[in] id the id as defined in the manifest * @return the matching manifest or nullptr when it wasn't found */ -const ServiceManifest* _Nullable findManifestId(const std::string& id); +std::shared_ptr _Nullable findManifestId(const std::string& id); -/** Find a service by its manifest id. +/** Find a ServiceContext by its manifest id. * @param[in] id the id as defined in the manifest * @return the service context or nullptr when it wasn't found */ -ServiceContext* _Nullable findServiceById(const std::string& id); +std::shared_ptr _Nullable findServiceContextById(const std::string& id); + +/** Find a Service by its manifest id. + * @param[in] id the id as defined in the manifest + * @return the service context or nullptr when it wasn't found + */ +std::shared_ptr _Nullable findServiceById(const std::string& id); + +/** Find a Service by its manifest id. + * @param[in] id the id as defined in the manifest + * @return the service context or nullptr when it wasn't found + */ +template +std::shared_ptr _Nullable findServiceById(const std::string& id) { + return std::static_pointer_cast(findServiceById(id)); +} } // namespace diff --git a/TactilityHeadless/Source/service/sdcard/Sdcard.cpp b/TactilityHeadless/Source/service/sdcard/Sdcard.cpp index c61abba1..7e27c979 100644 --- a/TactilityHeadless/Source/service/sdcard/Sdcard.cpp +++ b/TactilityHeadless/Source/service/sdcard/Sdcard.cpp @@ -11,7 +11,10 @@ namespace tt::service::sdcard { extern const ServiceManifest manifest; -struct ServiceData { +class SdCardService final : public Service { + +private: + Mutex mutex; std::unique_ptr updateTimer; hal::SdCard::State lastState = hal::SdCard::State::Unmounted; @@ -23,61 +26,59 @@ struct ServiceData { void unlock() const { tt_check(mutex.release() == TtStatusOk); } + + void update() { + auto sdcard = tt::hal::getConfiguration()->sdcard; + tt_assert(sdcard); + + if (lock(50)) { + auto new_state = sdcard->getState(); + + if (new_state == hal::SdCard::State::Error) { + TT_LOG_W(TAG, "Sdcard error - unmounting. Did you eject the card in an unsafe manner?"); + sdcard->unmount(); + } + + if (new_state != lastState) { + lastState = new_state; + } + + unlock(); + } else { + TT_LOG_W(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED); + } + } + + static void onUpdate(std::shared_ptr context) { + auto service = std::static_pointer_cast(context); + service->update(); + } + +public: + + void onStart(ServiceContext& serviceContext) final { + if (hal::getConfiguration()->sdcard != nullptr) { + auto service = findServiceById(manifest.id); + updateTimer = std::make_unique(Timer::Type::Periodic, onUpdate, service); + // We want to try and scan more often in case of startup or scan lock failure + updateTimer->start(1000); + } else { + TT_LOG_I(TAG, "Timer not started: no SD card config"); + } + } + + void onStop(ServiceContext& serviceContext) final { + if (updateTimer != nullptr) { + // Stop thread + updateTimer->stop(); + updateTimer = nullptr; + } + } }; -static void onUpdate(std::shared_ptr context) { - auto sdcard = tt::hal::getConfiguration()->sdcard; - if (sdcard == nullptr) { - return; - } - - auto data = std::static_pointer_cast(context); - - if (!data->lock(50)) { - TT_LOG_W(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED); - return; - } - - auto new_state = sdcard->getState(); - - if (new_state == hal::SdCard::State::Error) { - TT_LOG_W(TAG, "Sdcard error - unmounting. Did you eject the card in an unsafe manner?"); - sdcard->unmount(); - } - - if (new_state != data->lastState) { - data->lastState = new_state; - } - - data->unlock(); -} - -static void onStart(ServiceContext& service) { - if (hal::getConfiguration()->sdcard != nullptr) { - auto data = std::make_shared(); - service.setData(data); - - data->updateTimer = std::make_unique(Timer::Type::Periodic, onUpdate, data); - // We want to try and scan more often in case of startup or scan lock failure - data->updateTimer->start(1000); - } else { - TT_LOG_I(TAG, "task not started due to config"); - } -} - -static void onStop(ServiceContext& service) { - auto data = std::static_pointer_cast(service.getData()); - if (data->updateTimer != nullptr) { - // Stop thread - data->updateTimer->stop(); - data->updateTimer = nullptr; - } -} - extern const ServiceManifest manifest = { .id = "sdcard", - .onStart = onStart, - .onStop = onStop + .createService = create }; } // namespace diff --git a/TactilityHeadless/Source/service/wifi/WifiEsp.cpp b/TactilityHeadless/Source/service/wifi/WifiEsp.cpp index ebd8b531..8ddac1bc 100644 --- a/TactilityHeadless/Source/service/wifi/WifiEsp.cpp +++ b/TactilityHeadless/Source/service/wifi/WifiEsp.cpp @@ -936,52 +936,54 @@ void onAutoConnectTimer(std::shared_ptr context) { } } -static void onStart(ServiceContext& service) { - tt_assert(wifi_singleton == nullptr); - wifi_singleton = std::make_shared(); +class WifiService final : public Service { - service.setData(wifi_singleton); +public: - wifi_singleton->autoConnectTimer = std::make_unique(Timer::Type::Periodic, onAutoConnectTimer, wifi_singleton); - // We want to try and scan more often in case of startup or scan lock failure - wifi_singleton->autoConnectTimer->start(TT_MIN(2000, AUTO_SCAN_INTERVAL)); + void onStart(ServiceContext& service) override { + tt_assert(wifi_singleton == nullptr); + wifi_singleton = std::make_shared(); - if (settings::shouldEnableOnBoot()) { - TT_LOG_I(TAG, "Auto-enabling due to setting"); - getMainDispatcher().dispatch(dispatchEnable, wifi_singleton); - } -} + wifi_singleton->autoConnectTimer = std::make_unique(Timer::Type::Periodic, onAutoConnectTimer, wifi_singleton); + // We want to try and scan more often in case of startup or scan lock failure + wifi_singleton->autoConnectTimer->start(TT_MIN(2000, AUTO_SCAN_INTERVAL)); -static void onStop(ServiceContext& service) { - auto wifi = wifi_singleton; - tt_assert(wifi != nullptr); - - RadioState state = wifi->getRadioState(); - if (state != RadioState::Off) { - dispatchDisable(wifi); + if (settings::shouldEnableOnBoot()) { + TT_LOG_I(TAG, "Auto-enabling due to setting"); + getMainDispatcher().dispatch(dispatchEnable, wifi_singleton); + } } - wifi->autoConnectTimer->stop(); - wifi->autoConnectTimer = nullptr; // Must release as it holds a reference to this Wifi instance + void onStop(ServiceContext& service) override { + auto wifi = wifi_singleton; + tt_assert(wifi != nullptr); - // Acquire all mutexes - wifi->dataMutex.acquire(TtWaitForever); - wifi->radioMutex.acquire(TtWaitForever); + RadioState state = wifi->getRadioState(); + if (state != RadioState::Off) { + dispatchDisable(wifi); + } - // Detach - wifi_singleton = nullptr; + wifi->autoConnectTimer->stop(); + wifi->autoConnectTimer = nullptr; // Must release as it holds a reference to this Wifi instance - // Release mutexes - wifi->dataMutex.release(); - wifi->radioMutex.release(); + // Acquire all mutexes + wifi->dataMutex.acquire(TtWaitForever); + wifi->radioMutex.acquire(TtWaitForever); - // Release (hopefully) last Wifi instance by scope -} + // Detach + wifi_singleton = nullptr; + + // Release mutexes + wifi->dataMutex.release(); + wifi->radioMutex.release(); + + // Release (hopefully) last Wifi instance by scope + } +}; extern const ServiceManifest manifest = { .id = "Wifi", - .onStart = onStart, - .onStop = onStop + .createService = create }; } // namespace diff --git a/TactilityHeadless/Source/service/wifi/WifiMock.cpp b/TactilityHeadless/Source/service/wifi/WifiMock.cpp index 4eade964..54f5da88 100644 --- a/TactilityHeadless/Source/service/wifi/WifiMock.cpp +++ b/TactilityHeadless/Source/service/wifi/WifiMock.cpp @@ -137,25 +137,25 @@ int getRssi() { // endregion Public functions -static void service_start(TT_UNUSED ServiceContext& service) { - tt_check(wifi == nullptr); - wifi = new Wifi(); -} +class WifiService final : public Service { -static void service_stop(TT_UNUSED ServiceContext& service) { - tt_check(wifi != nullptr); - delete wifi; - wifi = nullptr; -} +public: -void wifi_main(void*) { - // NO-OP -} + void onStart(TT_UNUSED ServiceContext& service) final { + tt_check(wifi == nullptr); + wifi = new Wifi(); + } + + void onStop(TT_UNUSED ServiceContext& service) final { + tt_check(wifi != nullptr); + delete wifi; + wifi = nullptr; + } +}; extern const ServiceManifest manifest = { .id = "Wifi", - .onStart = &service_start, - .onStop = &service_stop + .createService = create }; } // namespace