Refactor services into classes (#183)

This commit is contained in:
Ken Van Hoeylandt 2025-01-24 18:21:47 +01:00 committed by GitHub
parent bb7e79886f
commit 3be251d8fb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 382 additions and 373 deletions

View File

@ -34,12 +34,6 @@ private:
* Do not mutate after app creation. * Do not mutate after app creation.
*/ */
std::shared_ptr<const tt::Bundle> _Nullable parameters; std::shared_ptr<const tt::Bundle> _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<void> _Nullable data;
std::shared_ptr<App> app; std::shared_ptr<App> app;

View File

@ -23,15 +23,6 @@ namespace service {
#endif #endif
} }
static const std::vector<const service::ServiceManifest*> 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 // endregion
// region Default apps // region Default apps
@ -125,18 +116,19 @@ static void register_user_apps(const std::vector<const app::AppManifest*>& apps)
static void register_and_start_system_services() { static void register_and_start_system_services() {
TT_LOG_I(TAG, "Registering and starting system services"); TT_LOG_I(TAG, "Registering and starting system services");
for (const auto& service_manifest: system_services) { addService(service::loader::manifest);
addService(service_manifest); addService(service::gui::manifest);
tt_check(service::startService(service_manifest->id)); addService(service::statusbar::manifest);
} #if TT_FEATURE_SCREENSHOT_ENABLED
addService(service::screenshot::manifest);
#endif
} }
static void register_and_start_user_services(const std::vector<const service::ServiceManifest*>& services) { static void register_and_start_user_services(const std::vector<const service::ServiceManifest*>& manifests) {
TT_LOG_I(TAG, "Registering and starting user services"); TT_LOG_I(TAG, "Registering and starting user services");
for (auto* manifest : services) { for (auto* manifest : manifests) {
assert(manifest != nullptr); assert(manifest != nullptr);
addService(manifest); addService(*manifest);
tt_check(service::startService(manifest->id));
} }
} }

View File

@ -142,14 +142,20 @@ static int32_t guiMain(TT_UNUSED void* p) {
// region AppManifest // region AppManifest
static void start(TT_UNUSED ServiceContext& service) { class GuiService : public Service {
public:
void onStart(TT_UNUSED ServiceContext& service) override {
tt_assert(gui == nullptr);
gui = gui_alloc(); gui = gui_alloc();
gui->thread->setPriority(THREAD_PRIORITY_SERVICE); gui->thread->setPriority(THREAD_PRIORITY_SERVICE);
gui->thread->start(); gui->thread->start();
} }
static void stop(TT_UNUSED ServiceContext& service) { void onStop(TT_UNUSED ServiceContext& service) override {
tt_assert(gui != nullptr);
lock(); lock();
ThreadId thread_id = gui->thread->getId(); ThreadId thread_id = gui->thread->getId();
@ -160,12 +166,12 @@ static void stop(TT_UNUSED ServiceContext& service) {
unlock(); unlock();
gui_free(gui); gui_free(gui);
} }
};
extern const ServiceManifest manifest = { extern const ServiceManifest manifest = {
.id = "Gui", .id = "Gui",
.onStart = &start, .createService = create<GuiService>
.onStop = &stop
}; };
// endregion // endregion

View File

@ -295,13 +295,17 @@ static void stopAppInternal() {
// region AppManifest // region AppManifest
static void loader_start(TT_UNUSED ServiceContext& service) { class LoaderService final : public Service {
public:
void onStart(TT_UNUSED ServiceContext& service) final {
tt_check(loader_singleton == nullptr); tt_check(loader_singleton == nullptr);
loader_singleton = loader_alloc(); loader_singleton = loader_alloc();
loader_singleton->dispatcherThread->start(); loader_singleton->dispatcherThread->start();
} }
static void loader_stop(TT_UNUSED ServiceContext& service) { void onStop(TT_UNUSED ServiceContext& service) final {
tt_check(loader_singleton != nullptr); tt_check(loader_singleton != nullptr);
// Send stop signal to thread and wait for thread to finish // Send stop signal to thread and wait for thread to finish
@ -314,12 +318,12 @@ static void loader_stop(TT_UNUSED ServiceContext& service) {
loader_free(); loader_free();
loader_singleton = nullptr; loader_singleton = nullptr;
} }
};
extern const ServiceManifest manifest = { extern const ServiceManifest manifest = {
.id = "Loader", .id = "Loader",
.onStart = &loader_start, .createService = create<LoaderService>
.onStop = &loader_stop
}; };
// endregion // endregion

View File

@ -15,12 +15,7 @@ namespace tt::service::screenshot {
extern const ServiceManifest manifest; extern const ServiceManifest manifest;
std::shared_ptr<ScreenshotService> _Nullable optScreenshotService() { std::shared_ptr<ScreenshotService> _Nullable optScreenshotService() {
ServiceContext* context = service::findServiceById(manifest.id); return service::findServiceById<ScreenshotService>(manifest.id);
if (context != nullptr) {
return std::static_pointer_cast<ScreenshotService>(context->getData());
} else {
return nullptr;
}
} }
void ScreenshotService::startApps(const char* path) { void ScreenshotService::startApps(const char* path) {
@ -89,14 +84,9 @@ bool ScreenshotService::isTaskStarted() {
} }
} }
static void onStart(ServiceContext& serviceContext) {
auto service = std::make_shared<ScreenshotService>();
serviceContext.setData(service);
}
extern const ServiceManifest manifest = { extern const ServiceManifest manifest = {
.id = "Screenshot", .id = "Screenshot",
.onStart = onStart .createService = create<ScreenshotService>
}; };
} // namespace } // namespace

View File

@ -1,11 +1,12 @@
#pragma once #pragma once
#include "Mutex.h"
#include "ScreenshotTask.h"
#include "TactilityConfig.h" #include "TactilityConfig.h"
#if TT_FEATURE_SCREENSHOT_ENABLED #if TT_FEATURE_SCREENSHOT_ENABLED
#include "Mutex.h"
#include "ScreenshotTask.h"
#include "service/Service.h"
#include <cstdint> #include <cstdint>
namespace tt::service::screenshot { namespace tt::service::screenshot {
@ -16,7 +17,7 @@ enum class Mode {
Apps Apps
}; };
class ScreenshotService { class ScreenshotService final : public Service {
private: private:

View File

@ -41,35 +41,6 @@ namespace tt::service::statusbar {
extern const ServiceManifest manifest; extern const ServiceManifest manifest;
struct ServiceData {
Mutex mutex;
std::unique_ptr<Timer> 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<service::Paths> 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) { const char* getWifiStatusIconForRssi(int rssi) {
if (rssi >= -60) { if (rssi >= -60) {
return STATUSBAR_ICON_WIFI_SIGNAL_STRONG_WHITE; 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<ServiceData>& 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) { static const char* getSdCardStatusIcon(hal::SdCard::State state) {
switch (state) { switch (state) {
case hal::SdCard::State::Mounted: 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<ServiceData>& 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() { static _Nullable const char* getPowerStatusIcon() {
auto get_power = getConfiguration()->hardware->power; auto get_power = getConfiguration()->hardware->power;
if (get_power == nullptr) { if (get_power == nullptr) {
@ -192,69 +122,120 @@ static _Nullable const char* getPowerStatusIcon() {
} }
} }
static void updatePowerStatusIcon(const service::Paths* paths, const std::shared_ptr<ServiceData>& data) { class StatusbarService final : public Service {
const char* desired_icon = getPowerStatusIcon();
if (data->power_last_icon != desired_icon) { private:
Mutex mutex;
std::unique_ptr<Timer> 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<service::Paths> paths;
void lock() const {
tt_check(mutex.acquire(TtWaitForever) == TtStatusOk);
}
void unlock() const {
tt_check(mutex.release() == TtStatusOk);
}
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) { if (desired_icon != nullptr) {
auto icon_path = paths->getSystemPathLvgl(desired_icon); auto icon_path = paths->getSystemPathLvgl(desired_icon);
lvgl::statusbar_icon_set_image(data->power_icon_id, icon_path); lvgl::statusbar_icon_set_image(wifi_icon_id, icon_path);
lvgl::statusbar_icon_set_visibility(data->power_icon_id, true); lvgl::statusbar_icon_set_visibility(wifi_icon_id, true);
} else { } else {
lvgl::statusbar_icon_set_visibility(data->power_icon_id, false); lvgl::statusbar_icon_set_visibility(wifi_icon_id, false);
}
wifi_last_icon = desired_icon;
} }
data->power_last_icon = desired_icon;
} }
}
// endregion power 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;
}
}
// region service 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 service_data_free(ServiceData* data) { void update() {
free(data);
}
static void onUpdate(std::shared_ptr<void> parameter) {
auto data = std::static_pointer_cast<ServiceData>(parameter);
// TODO: Make thread-safe for LVGL // TODO: Make thread-safe for LVGL
auto* paths = data->paths.get(); updateWifiIcon();
updateWifiIcon(paths, data); updateSdCardIcon();
updateSdCardIcon(paths, data); updatePowerStatusIcon();
updatePowerStatusIcon(paths, data); }
}
static void onStart(ServiceContext& service) { static void onUpdate(std::shared_ptr<void> parameter) {
lvgl::lock(TtWaitForever); auto service = std::static_pointer_cast<StatusbarService>(parameter);
auto data = std::make_shared<ServiceData>(); service->update();
lvgl::unlock(); }
data->paths = service.getPaths(); public:
service.setData(data); ~StatusbarService() final {
lvgl::statusbar_icon_remove(wifi_icon_id);
lvgl::statusbar_icon_remove(sdcard_icon_id);
lvgl::statusbar_icon_remove(power_icon_id);
}
void onStart(ServiceContext& serviceContext) override {
paths = serviceContext.getPaths();
// TODO: Make thread-safe for LVGL // TODO: Make thread-safe for LVGL
lvgl::statusbar_icon_set_visibility(data->wifi_icon_id, true); lvgl::statusbar_icon_set_visibility(wifi_icon_id, true);
updateWifiIcon(data->paths.get(), data);
updateSdCardIcon(data->paths.get(), data); // also updates visibility
updatePowerStatusIcon(data->paths.get(), data);
data->updateTimer = std::make_unique<Timer>(Timer::Type::Periodic, onUpdate, data); auto service = findServiceById(manifest.id);
assert(service);
onUpdate(service);
updateTimer = std::make_unique<Timer>(Timer::Type::Periodic, onUpdate, service);
// We want to try and scan more often in case of startup or scan lock failure // We want to try and scan more often in case of startup or scan lock failure
data->updateTimer->start(1000); updateTimer->start(1000);
} }
static void onStop(ServiceContext& service) { void onStop(ServiceContext& service) override{
auto data = std::static_pointer_cast<ServiceData>(service.getData()); updateTimer->stop();
updateTimer = nullptr;
// Stop thread }
data->updateTimer->stop(); };
data->updateTimer = nullptr;
}
extern const ServiceManifest manifest = { extern const ServiceManifest manifest = {
.id = "Statusbar", .id = "Statusbar",
.onStart = onStart, .createService = create<StatusbarService>
.onStop = onStop
}; };
// endregion service // endregion service

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "service/ServiceContext.h" #include "service/ServiceContext.h"
#include "service/Service.h"
namespace tt::service { namespace tt::service {
@ -9,19 +10,21 @@ class ServiceInstance : public ServiceContext {
private: private:
Mutex mutex = Mutex(Mutex::Type::Normal); Mutex mutex = Mutex(Mutex::Type::Normal);
const service::ServiceManifest& manifest; std::shared_ptr<const ServiceManifest> manifest;
std::shared_ptr<void> data = nullptr; std::shared_ptr<Service> service;
public: public:
explicit ServiceInstance(const service::ServiceManifest& manifest); explicit ServiceInstance(std::shared_ptr<const service::ServiceManifest> manifest);
~ServiceInstance() override = default; ~ServiceInstance() override = default;
/** @return a reference ot the service's manifest */
const service::ServiceManifest& getManifest() const override; const service::ServiceManifest& getManifest() const override;
std::shared_ptr<void> getData() const override;
void setData(std::shared_ptr<void> newData) override;
/** Retrieve the paths that are relevant to this service */
std::unique_ptr<Paths> getPaths() const override; std::unique_ptr<Paths> getPaths() const override;
std::shared_ptr<Service> getService() const { return service; }
}; };
} }

View File

@ -8,11 +8,11 @@ class ServiceInstancePaths final : public Paths {
private: private:
const ServiceManifest& manifest; std::shared_ptr<const ServiceManifest> manifest;
public: public:
explicit ServiceInstancePaths(const ServiceManifest& manifest) : manifest(manifest) {} explicit ServiceInstancePaths(std::shared_ptr<const ServiceManifest> manifest) : manifest(std::move(manifest)) {}
~ServiceInstancePaths() final = default; ~ServiceInstancePaths() final = default;
std::string getDataDirectory() const final; std::string getDataDirectory() const final;

View File

@ -21,20 +21,12 @@ namespace service::sdcard { extern const ServiceManifest manifest; }
static Dispatcher mainDispatcher; static Dispatcher mainDispatcher;
static const service::ServiceManifest* const system_services[] = {
&service::sdcard::manifest,
&service::wifi::manifest
};
static const hal::Configuration* hardwareConfig = nullptr; static const hal::Configuration* hardwareConfig = nullptr;
static void register_and_start_system_services() { static void register_and_start_system_services() {
TT_LOG_I(TAG, "Registering and starting system services"); TT_LOG_I(TAG, "Registering and starting system services");
int app_count = sizeof(system_services) / sizeof(service::ServiceManifest*); addService(service::sdcard::manifest);
for (int i = 0; i < app_count; ++i) { addService(service::wifi::manifest);
addService(system_services[i]);
tt_check(service::startService(system_services[i]->id));
}
} }
void initHeadless(const hal::Configuration& config) { void initHeadless(const hal::Configuration& config) {

View File

@ -0,0 +1,24 @@
#pragma once
#include <memory>
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<typename T>
std::shared_ptr<Service> create() { return std::shared_ptr<T>(new T); }
}

View File

@ -22,10 +22,7 @@ public:
/** @return a reference ot the service's manifest */ /** @return a reference ot the service's manifest */
virtual const service::ServiceManifest& getManifest() const = 0; virtual const service::ServiceManifest& getManifest() const = 0;
/** @return a shared pointer to the data that is attached to the service */
virtual std::shared_ptr<void> _Nullable getData() const = 0;
/** Set the data for a service. */
virtual void setData(std::shared_ptr<void> newData) = 0;
/** Retrieve the paths that are relevant to this service */ /** Retrieve the paths that are relevant to this service */
virtual std::unique_ptr<Paths> getPaths() const = 0; virtual std::unique_ptr<Paths> getPaths() const = 0;
}; };

View File

@ -1,26 +1,14 @@
#include <utility>
#include "service/ServiceInstance.h" #include "service/ServiceInstance.h"
#include "service/ServiceInstancePaths.h" #include "service/ServiceInstancePaths.h"
namespace tt::service { namespace tt::service {
ServiceInstance::ServiceInstance(const service::ServiceManifest&manifest) : manifest(manifest) {} ServiceInstance::ServiceInstance(std::shared_ptr<const service::ServiceManifest> manifest) :
manifest(manifest),
service(manifest->createService())
{}
const service::ServiceManifest& ServiceInstance::getManifest() const { return manifest; } const service::ServiceManifest& ServiceInstance::getManifest() const { return *manifest; }
std::shared_ptr<void> ServiceInstance::getData() const {
mutex.acquire(TtWaitForever);
std::shared_ptr<void> result = data;
mutex.release();
return result;
}
void ServiceInstance::setData(std::shared_ptr<void> newData) {
mutex.acquire(TtWaitForever);
data = std::move(newData);
mutex.release();
}
std::unique_ptr<Paths> ServiceInstance::getPaths() const { std::unique_ptr<Paths> ServiceInstance::getPaths() const {
return std::make_unique<ServiceInstancePaths>(manifest); return std::make_unique<ServiceInstancePaths>(manifest);

View File

@ -11,38 +11,38 @@
namespace tt::service { namespace tt::service {
std::string ServiceInstancePaths::getDataDirectory() const { 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 { 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 { std::string ServiceInstancePaths::getDataPath(const std::string& childPath) const {
assert(!childPath.starts_with('/')); 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 { std::string ServiceInstancePaths::getDataPathLvgl(const std::string& childPath) const {
assert(!childPath.starts_with('/')); 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 { 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 { 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 { std::string ServiceInstancePaths::getSystemPath(const std::string& childPath) const {
assert(!childPath.starts_with('/')); 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 { 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;
} }
} }

View File

@ -1,24 +1,22 @@
#pragma once #pragma once
#include "service/Service.h"
#include <string> #include <string>
namespace tt::service { namespace tt::service {
// Forward declarations
class ServiceContext; class ServiceContext;
typedef void (*ServiceOnStart)(ServiceContext& service); typedef std::shared_ptr<Service>(*CreateService)();
typedef void (*ServiceOnStop)(ServiceContext& service);
/** A ledger that describes the main parts of a service. */ /** A ledger that describes the main parts of a service. */
struct ServiceManifest { struct ServiceManifest {
/** The identifier by which the app is launched by the system and other apps. */ /** The identifier by which the app is launched by the system and other apps. */
std::string id {}; std::string id {};
/** Non-blocking method to call when service is started. */ /** Create the instance of the app */
const ServiceOnStart onStart = nullptr; CreateService createService = nullptr;
/** Non-blocking method to call when service is stopped. */
const ServiceOnStop onStop = nullptr;
}; };
} // namespace } // namespace

View File

@ -3,7 +3,6 @@
#include "Mutex.h" #include "Mutex.h"
#include "service/ServiceInstance.h" #include "service/ServiceInstance.h"
#include "service/ServiceManifest.h" #include "service/ServiceManifest.h"
#include "TactilityCore.h"
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
@ -11,8 +10,8 @@ namespace tt::service {
#define TAG "service_registry" #define TAG "service_registry"
typedef std::unordered_map<std::string, const ServiceManifest*> ManifestMap; typedef std::unordered_map<std::string, std::shared_ptr<const ServiceManifest>> ManifestMap;
typedef std::unordered_map<std::string, ServiceInstance*> ServiceInstanceMap; typedef std::unordered_map<std::string, std::shared_ptr<ServiceInstance>> ServiceInstanceMap;
static ManifestMap service_manifest_map; static ManifestMap service_manifest_map;
static ServiceInstanceMap service_instance_map; static ServiceInstanceMap service_instance_map;
@ -20,30 +19,41 @@ static ServiceInstanceMap service_instance_map;
static Mutex manifest_mutex(Mutex::Type::Normal); static Mutex manifest_mutex(Mutex::Type::Normal);
static Mutex instance_mutex(Mutex::Type::Normal); static Mutex instance_mutex(Mutex::Type::Normal);
void addService(const ServiceManifest* manifest) { void addService(std::shared_ptr<const ServiceManifest> manifest, bool autoStart) {
TT_LOG_I(TAG, "Adding %s", manifest->id.c_str()); // We'll move the manifest pointer, but we'll need to id later
std::string id = manifest->id;
manifest_mutex.acquire(TtWaitForever); TT_LOG_I(TAG, "Adding %s", id.c_str());
if (service_manifest_map[manifest->id] == nullptr) {
service_manifest_map[manifest->id] = manifest; manifest_mutex.lock(TtWaitForever);
if (service_manifest_map[id] == nullptr) {
service_manifest_map[id] = std::move(manifest);
} else { } 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<const ServiceManifest>(manifest), autoStart);
}
std::shared_ptr<const ServiceManifest> _Nullable findManifestId(const std::string& id) {
manifest_mutex.acquire(TtWaitForever); manifest_mutex.acquire(TtWaitForever);
auto iterator = service_manifest_map.find(id); 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(); manifest_mutex.release();
return manifest; return manifest;
} }
static ServiceInstance* _Nullable service_registry_find_instance_by_id(const std::string& id) { static std::shared_ptr<ServiceInstance> _Nullable findServiceInstanceById(const std::string& id) {
manifest_mutex.acquire(TtWaitForever); manifest_mutex.acquire(TtWaitForever);
auto iterator = service_instance_map.find(id); 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(); manifest_mutex.release();
return service; 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? // TODO: Return proper error/status instead of BOOL?
bool startService(const std::string& id) { bool startService(const std::string& id) {
TT_LOG_I(TAG, "Starting %s", id.c_str()); TT_LOG_I(TAG, "Starting %s", id.c_str());
const ServiceManifest* manifest = findManifestId(id); auto manifest = findManifestId(id);
if (manifest == nullptr) { if (manifest == nullptr) {
TT_LOG_E(TAG, "manifest not found for service %s", id.c_str()); TT_LOG_E(TAG, "manifest not found for service %s", id.c_str());
return false; return false;
} }
auto* service = new ServiceInstance(*manifest); auto service_instance = std::make_shared<ServiceInstance>(manifest);
manifest->onStart(*service);
// Register first, so that a service can retrieve itself during onStart()
instance_mutex.acquire(TtWaitForever); instance_mutex.acquire(TtWaitForever);
service_instance_map[manifest->id] = service; service_instance_map[manifest->id] = service_instance;
instance_mutex.release(); instance_mutex.release();
service_instance->getService()->onStart(*service_instance);
TT_LOG_I(TAG, "Started %s", id.c_str()); TT_LOG_I(TAG, "Started %s", id.c_str());
return true; return true;
} }
_Nullable ServiceContext* findServiceById(const std::string& service_id) { std::shared_ptr<ServiceContext> _Nullable findServiceContextById(const std::string& id) {
return static_cast<ServiceInstance*>(service_registry_find_instance_by_id(service_id)); return findServiceInstanceById(id);
}
std::shared_ptr<Service> _Nullable findServiceById(const std::string& id) {
auto instance = findServiceInstanceById(id);
return instance != nullptr ? instance->getService() : nullptr;
} }
bool stopService(const std::string& id) { bool stopService(const std::string& id) {
TT_LOG_I(TAG, "Stopping %s", id.c_str()); TT_LOG_I(TAG, "Stopping %s", id.c_str());
ServiceInstance* service = service_registry_find_instance_by_id(id); auto service_instance = findServiceInstanceById(id);
if (service == nullptr) { if (service_instance == nullptr) {
TT_LOG_W(TAG, "service not running: %s", id.c_str()); TT_LOG_W(TAG, "service not running: %s", id.c_str());
return false; return false;
} }
service->getManifest().onStop(*service); service_instance->getService()->onStop(*service_instance);
delete service;
instance_mutex.acquire(TtWaitForever); instance_mutex.acquire(TtWaitForever);
service_instance_map.erase(id); service_instance_map.erase(id);
instance_mutex.release(); 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()); TT_LOG_I(TAG, "Stopped %s", id.c_str());
return true; return true;

View File

@ -1,20 +1,20 @@
#pragma once #pragma once
#include "service/ServiceManifest.h" #include "service/ServiceManifest.h"
#include "service/Service.h"
#include <memory>
namespace tt::service { namespace tt::service {
void initRegistry();
/** Register a service. /** Register a service.
* @param[in] the service manifest * @param[in] the service manifest
*/ */
void addService(const ServiceManifest* manifest); void addService(std::shared_ptr<const ServiceManifest> manifest, bool autoStart = true);
/** Unregister a service. /** Register a service.
* @param[in] the service manifest * @param[in] the service manifest
*/ */
void removeService(const ServiceManifest* manifest); void addService(const ServiceManifest& manifest, bool autoStart = true);
/** Start a service. /** Start a service.
* @param[in] the service id as defined in its manifest * @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 * @param[in] id the id as defined in the manifest
* @return the matching manifest or nullptr when it wasn't found * @return the matching manifest or nullptr when it wasn't found
*/ */
const ServiceManifest* _Nullable findManifestId(const std::string& id); std::shared_ptr<const ServiceManifest> _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 * @param[in] id the id as defined in the manifest
* @return the service context or nullptr when it wasn't found * @return the service context or nullptr when it wasn't found
*/ */
ServiceContext* _Nullable findServiceById(const std::string& id); std::shared_ptr<ServiceContext> _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<Service> _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 <typename T>
std::shared_ptr<T> _Nullable findServiceById(const std::string& id) {
return std::static_pointer_cast<T>(findServiceById(id));
}
} // namespace } // namespace

View File

@ -11,7 +11,10 @@ namespace tt::service::sdcard {
extern const ServiceManifest manifest; extern const ServiceManifest manifest;
struct ServiceData { class SdCardService final : public Service {
private:
Mutex mutex; Mutex mutex;
std::unique_ptr<Timer> updateTimer; std::unique_ptr<Timer> updateTimer;
hal::SdCard::State lastState = hal::SdCard::State::Unmounted; hal::SdCard::State lastState = hal::SdCard::State::Unmounted;
@ -23,21 +26,12 @@ struct ServiceData {
void unlock() const { void unlock() const {
tt_check(mutex.release() == TtStatusOk); tt_check(mutex.release() == TtStatusOk);
} }
};
static void onUpdate(std::shared_ptr<void> context) { void update() {
auto sdcard = tt::hal::getConfiguration()->sdcard; auto sdcard = tt::hal::getConfiguration()->sdcard;
if (sdcard == nullptr) { tt_assert(sdcard);
return;
}
auto data = std::static_pointer_cast<ServiceData>(context);
if (!data->lock(50)) {
TT_LOG_W(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED);
return;
}
if (lock(50)) {
auto new_state = sdcard->getState(); auto new_state = sdcard->getState();
if (new_state == hal::SdCard::State::Error) { if (new_state == hal::SdCard::State::Error) {
@ -45,39 +39,46 @@ static void onUpdate(std::shared_ptr<void> context) {
sdcard->unmount(); sdcard->unmount();
} }
if (new_state != data->lastState) { if (new_state != lastState) {
data->lastState = new_state; lastState = new_state;
} }
data->unlock(); unlock();
}
static void onStart(ServiceContext& service) {
if (hal::getConfiguration()->sdcard != nullptr) {
auto data = std::make_shared<ServiceData>();
service.setData(data);
data->updateTimer = std::make_unique<Timer>(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 { } else {
TT_LOG_I(TAG, "task not started due to config"); TT_LOG_W(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED);
}
} }
}
static void onStop(ServiceContext& service) { static void onUpdate(std::shared_ptr<void> context) {
auto data = std::static_pointer_cast<ServiceData>(service.getData()); auto service = std::static_pointer_cast<SdCardService>(context);
if (data->updateTimer != nullptr) { service->update();
// Stop thread
data->updateTimer->stop();
data->updateTimer = nullptr;
} }
}
public:
void onStart(ServiceContext& serviceContext) final {
if (hal::getConfiguration()->sdcard != nullptr) {
auto service = findServiceById(manifest.id);
updateTimer = std::make_unique<Timer>(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;
}
}
};
extern const ServiceManifest manifest = { extern const ServiceManifest manifest = {
.id = "sdcard", .id = "sdcard",
.onStart = onStart, .createService = create<SdCardService>
.onStop = onStop
}; };
} // namespace } // namespace

View File

@ -936,12 +936,14 @@ void onAutoConnectTimer(std::shared_ptr<void> context) {
} }
} }
static void onStart(ServiceContext& service) { class WifiService final : public Service {
public:
void onStart(ServiceContext& service) override {
tt_assert(wifi_singleton == nullptr); tt_assert(wifi_singleton == nullptr);
wifi_singleton = std::make_shared<Wifi>(); wifi_singleton = std::make_shared<Wifi>();
service.setData(wifi_singleton);
wifi_singleton->autoConnectTimer = std::make_unique<Timer>(Timer::Type::Periodic, onAutoConnectTimer, wifi_singleton); wifi_singleton->autoConnectTimer = std::make_unique<Timer>(Timer::Type::Periodic, onAutoConnectTimer, wifi_singleton);
// We want to try and scan more often in case of startup or scan lock failure // 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)); wifi_singleton->autoConnectTimer->start(TT_MIN(2000, AUTO_SCAN_INTERVAL));
@ -950,9 +952,9 @@ static void onStart(ServiceContext& service) {
TT_LOG_I(TAG, "Auto-enabling due to setting"); TT_LOG_I(TAG, "Auto-enabling due to setting");
getMainDispatcher().dispatch(dispatchEnable, wifi_singleton); getMainDispatcher().dispatch(dispatchEnable, wifi_singleton);
} }
} }
static void onStop(ServiceContext& service) { void onStop(ServiceContext& service) override {
auto wifi = wifi_singleton; auto wifi = wifi_singleton;
tt_assert(wifi != nullptr); tt_assert(wifi != nullptr);
@ -976,12 +978,12 @@ static void onStop(ServiceContext& service) {
wifi->radioMutex.release(); wifi->radioMutex.release();
// Release (hopefully) last Wifi instance by scope // Release (hopefully) last Wifi instance by scope
} }
};
extern const ServiceManifest manifest = { extern const ServiceManifest manifest = {
.id = "Wifi", .id = "Wifi",
.onStart = onStart, .createService = create<WifiService>
.onStop = onStop
}; };
} // namespace } // namespace

View File

@ -137,25 +137,25 @@ int getRssi() {
// endregion Public functions // endregion Public functions
static void service_start(TT_UNUSED ServiceContext& service) { class WifiService final : public Service {
public:
void onStart(TT_UNUSED ServiceContext& service) final {
tt_check(wifi == nullptr); tt_check(wifi == nullptr);
wifi = new Wifi(); wifi = new Wifi();
} }
static void service_stop(TT_UNUSED ServiceContext& service) { void onStop(TT_UNUSED ServiceContext& service) final {
tt_check(wifi != nullptr); tt_check(wifi != nullptr);
delete wifi; delete wifi;
wifi = nullptr; wifi = nullptr;
} }
};
void wifi_main(void*) {
// NO-OP
}
extern const ServiceManifest manifest = { extern const ServiceManifest manifest = {
.id = "Wifi", .id = "Wifi",
.onStart = &service_start, .createService = create<WifiService>
.onStop = &service_stop
}; };
} // namespace } // namespace