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.
*/
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;

View File

@ -23,15 +23,6 @@ namespace service {
#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
// 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() {
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<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");
for (auto* manifest : services) {
for (auto* manifest : manifests) {
assert(manifest != nullptr);
addService(manifest);
tt_check(service::startService(manifest->id));
addService(*manifest);
}
}

View File

@ -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<GuiService>
};
// endregion

View File

@ -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<LoaderService>
};
// endregion

View File

@ -15,12 +15,7 @@ namespace tt::service::screenshot {
extern const ServiceManifest manifest;
std::shared_ptr<ScreenshotService> _Nullable optScreenshotService() {
ServiceContext* context = service::findServiceById(manifest.id);
if (context != nullptr) {
return std::static_pointer_cast<ScreenshotService>(context->getData());
} else {
return nullptr;
}
return service::findServiceById<ScreenshotService>(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<ScreenshotService>();
serviceContext.setData(service);
}
extern const ServiceManifest manifest = {
.id = "Screenshot",
.onStart = onStart
.createService = create<ScreenshotService>
};
} // namespace

View File

@ -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 <cstdint>
namespace tt::service::screenshot {
@ -16,7 +17,7 @@ enum class Mode {
Apps
};
class ScreenshotService {
class ScreenshotService final : public Service {
private:

View File

@ -41,35 +41,6 @@ namespace tt::service::statusbar {
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) {
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<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) {
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<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() {
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<ServiceData>& 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<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);
}
}
// 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<void> parameter) {
auto data = std::static_pointer_cast<ServiceData>(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<ServiceData>();
lvgl::unlock();
void update() {
// TODO: Make thread-safe for LVGL
updateWifiIcon();
updateSdCardIcon();
updatePowerStatusIcon();
}
data->paths = service.getPaths();
static void onUpdate(std::shared_ptr<void> parameter) {
auto service = std::static_pointer_cast<StatusbarService>(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>(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<ServiceData>(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>(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<StatusbarService>
};
// endregion service

View File

@ -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<void> data = nullptr;
std::shared_ptr<const ServiceManifest> manifest;
std::shared_ptr<Service> service;
public:
explicit ServiceInstance(const service::ServiceManifest& manifest);
explicit ServiceInstance(std::shared_ptr<const service::ServiceManifest> manifest);
~ServiceInstance() override = default;
/** @return a reference ot the service's manifest */
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::shared_ptr<Service> getService() const { return service; }
};
}

View File

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

View File

@ -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) {

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 */
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 */
virtual std::unique_ptr<Paths> getPaths() const = 0;
};

View File

@ -1,26 +1,14 @@
#include <utility>
#include "service/ServiceInstance.h"
#include "service/ServiceInstancePaths.h"
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; }
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();
}
const service::ServiceManifest& ServiceInstance::getManifest() const { return *manifest; }
std::unique_ptr<Paths> ServiceInstance::getPaths() const {
return std::make_unique<ServiceInstancePaths>(manifest);

View File

@ -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;
}
}

View File

@ -1,24 +1,22 @@
#pragma once
#include "service/Service.h"
#include <string>
namespace tt::service {
// Forward declarations
class ServiceContext;
typedef void (*ServiceOnStart)(ServiceContext& service);
typedef void (*ServiceOnStop)(ServiceContext& service);
typedef std::shared_ptr<Service>(*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

View File

@ -3,7 +3,6 @@
#include "Mutex.h"
#include "service/ServiceInstance.h"
#include "service/ServiceManifest.h"
#include "TactilityCore.h"
#include <string>
#include <unordered_map>
@ -11,8 +10,8 @@ namespace tt::service {
#define TAG "service_registry"
typedef std::unordered_map<std::string, const ServiceManifest*> ManifestMap;
typedef std::unordered_map<std::string, ServiceInstance*> ServiceInstanceMap;
typedef std::unordered_map<std::string, std::shared_ptr<const ServiceManifest>> ManifestMap;
typedef std::unordered_map<std::string, std::shared_ptr<ServiceInstance>> 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<const ServiceManifest> 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<const ServiceManifest>(manifest), autoStart);
}
std::shared_ptr<const ServiceManifest> _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<ServiceInstance> _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<ServiceInstance>(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<ServiceInstance*>(service_registry_find_instance_by_id(service_id));
std::shared_ptr<ServiceContext> _Nullable findServiceContextById(const std::string& 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) {
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;

View File

@ -1,20 +1,20 @@
#pragma once
#include "service/ServiceManifest.h"
#include "service/Service.h"
#include <memory>
namespace tt::service {
void initRegistry();
/** Register a service.
* @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
*/
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<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
* @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

View File

@ -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<Timer> 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<void> context) {
auto service = std::static_pointer_cast<SdCardService>(context);
service->update();
}
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;
}
}
};
static void onUpdate(std::shared_ptr<void> context) {
auto sdcard = tt::hal::getConfiguration()->sdcard;
if (sdcard == nullptr) {
return;
}
auto data = std::static_pointer_cast<ServiceData>(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<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 {
TT_LOG_I(TAG, "task not started due to config");
}
}
static void onStop(ServiceContext& service) {
auto data = std::static_pointer_cast<ServiceData>(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<SdCardService>
};
} // namespace

View File

@ -936,52 +936,54 @@ void onAutoConnectTimer(std::shared_ptr<void> context) {
}
}
static void onStart(ServiceContext& service) {
tt_assert(wifi_singleton == nullptr);
wifi_singleton = std::make_shared<Wifi>();
class WifiService final : public Service {
service.setData(wifi_singleton);
public:
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
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<Wifi>();
if (settings::shouldEnableOnBoot()) {
TT_LOG_I(TAG, "Auto-enabling due to setting");
getMainDispatcher().dispatch(dispatchEnable, 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
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<WifiService>
};
} // namespace

View File

@ -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<WifiService>
};
} // namespace