implemented service registry (#8)

+ implemented app and service context for data sharing
This commit is contained in:
Ken Van Hoeylandt 2024-01-06 20:37:41 +01:00 committed by GitHub
parent 051b1548ec
commit 83e226f696
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 282 additions and 101 deletions

View File

@ -8,7 +8,9 @@
App* furi_app_alloc(const AppManifest* _Nonnull manifest) {
App app = {
.manifest = manifest,
.ep_thread_args = NULL
.context = {
.data = NULL
}
};
App* app_ptr = malloc(sizeof(App));
return memcpy(app_ptr, &app, sizeof(App));
@ -16,11 +18,5 @@ App* furi_app_alloc(const AppManifest* _Nonnull manifest) {
void furi_app_free(App* app) {
furi_assert(app);
if (app->ep_thread_args) {
free(app->ep_thread_args);
app->ep_thread_args = NULL;
}
free(app);
}

View File

@ -9,11 +9,9 @@ extern "C" {
typedef struct {
const AppManifest* manifest;
void* ep_thread_args;
Context context;
} App;
const char* furi_app_type_to_string(AppType type);
FuriThread* furi_app_alloc_thread(App* _Nonnull app, const char* args);
App* furi_app_alloc(const AppManifest* _Nonnull manifest);
void furi_app_free(App* _Nonnull app);

View File

@ -1,5 +1,6 @@
#pragma once
#include "context.h"
#include <stdio.h>
#ifdef __cplusplus
@ -15,20 +16,20 @@ typedef enum {
AppTypeUser
} AppType;
typedef void (*AppOnStart)(void _Nonnull* parameter);
typedef void (*AppOnStop)();
typedef void (*AppOnShow)(lv_obj_t* parent, void* context);
typedef void (*AppOnStart)(Context* context);
typedef void (*AppOnStop)(Context* context);
typedef void (*AppOnShow)(Context* context, lv_obj_t* parent);
typedef struct {
/**
* The identifier by which the app is launched by the system and other apps.
*/
const char* _Nonnull id;
const char* id;
/**
* The user-readable name of the app. Used in UI.
*/
const char* _Nonnull name;
const char* name;
/**
* Optional icon.

View File

@ -0,0 +1,11 @@
#pragma once
typedef struct {
/** 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.
*/
void* data;
} Context;

View File

@ -1,7 +1,9 @@
#include "furi.h"
#include "app_manifest_registry.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "service_registry.h"
#define TAG "furi"
@ -22,6 +24,7 @@ void furi_init() {
NVIC_SetPriority(SVCall_IRQn, 0U);
#endif
service_registry_init();
app_manifest_registry_init();
FURI_LOG_I(TAG, "init complete");
}

View File

@ -0,0 +1,21 @@
#include "service_i.h"
#include "furi_core.h"
#include "log.h"
#define TAG "service"
Service* furi_service_alloc(const ServiceManifest* _Nonnull manifest) {
Service app = {
.manifest = manifest,
.context = {
.data = NULL
}
};
Service* app_ptr = malloc(sizeof(Service));
return memcpy(app_ptr, &app, sizeof(Service));
}
void furi_service_free(Service* app) {
furi_assert(app);
free(app);
}

View File

@ -0,0 +1,12 @@
#pragma once
#include "service_manifest.h"
#include "context.h"
typedef struct {
const ServiceManifest* manifest;
Context context;
} Service;
Service* furi_service_alloc(const ServiceManifest* _Nonnull manifest);
void furi_service_free(Service* _Nonnull service);

View File

@ -1,13 +1,14 @@
#pragma once
#include <stdio.h>
#include "context.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef void (*ServiceOnStart)(void _Nonnull* parameter);
typedef void (*ServiceOnStop)();
typedef void (*ServiceOnStart)(Context* context);
typedef void (*ServiceOnStop)(Context* context);
typedef struct {
/**

View File

@ -0,0 +1,136 @@
#include "service_registry.h"
#include "furi_core.h"
#include "m-dict.h"
#include "m_cstr_dup.h"
#include "mutex.h"
#include "service_i.h"
#define TAG "service_registry"
DICT_DEF2(ServiceManifestDict, const char*, M_CSTR_DUP_OPLIST, const ServiceManifest*, M_PTR_OPLIST)
DICT_DEF2(ServiceInstanceDict, const char*, M_CSTR_DUP_OPLIST, const Service*, M_PTR_OPLIST)
#define APP_REGISTRY_FOR_EACH(manifest_var_name, code_to_execute) \
{ \
service_registry_manifest_lock(); \
ServiceManifestDict_it_t it; \
for (ServiceManifestDict_it(it, service_manifest_dict); !ServiceManifestDict_end_p(it); ServiceManifestDict_next(it)) { \
const ServiceManifest*(manifest_var_name) = ServiceManifestDict_cref(it)->value; \
code_to_execute; \
} \
service_registry_manifest_unlock(); \
}
ServiceManifestDict_t service_manifest_dict;
ServiceInstanceDict_t service_instance_dict;
FuriMutex* manifest_mutex = NULL;
FuriMutex* instance_mutex = NULL;
void service_registry_init() {
furi_assert(manifest_mutex == NULL);
manifest_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
ServiceManifestDict_init(service_manifest_dict);
furi_assert(instance_mutex == NULL);
instance_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
ServiceInstanceDict_init(service_instance_dict);
}
void service_registry_instance_lock() {
furi_assert(instance_mutex != NULL);
furi_mutex_acquire(instance_mutex, FuriWaitForever);
}
void service_registry_instance_unlock() {
furi_assert(instance_mutex != NULL);
furi_mutex_release(instance_mutex);
}
void service_registry_manifest_lock() {
furi_assert(manifest_mutex != NULL);
furi_mutex_acquire(manifest_mutex, FuriWaitForever);
}
void service_registry_manifest_unlock() {
furi_assert(manifest_mutex != NULL);
furi_mutex_release(manifest_mutex);
}
void service_registry_add(const ServiceManifest _Nonnull* manifest) {
FURI_LOG_I(TAG, "adding %s", manifest->id);
service_registry_manifest_lock();
ServiceManifestDict_set_at(service_manifest_dict, manifest->id, manifest);
service_registry_manifest_unlock();
}
void service_registry_remove(const ServiceManifest _Nonnull* manifest) {
FURI_LOG_I(TAG, "removing %s", manifest->id);
service_registry_manifest_lock();
ServiceManifestDict_erase(service_manifest_dict, manifest->id);
service_registry_manifest_unlock();
}
const ServiceManifest* _Nullable service_registry_find_manifest_by_id(const char* id) {
service_registry_manifest_lock();
const ServiceManifest** _Nullable manifest = ServiceManifestDict_get(service_manifest_dict, id);
service_registry_manifest_unlock();
return (manifest != NULL) ? *manifest : NULL;
}
Service* _Nullable service_registry_find_instance_by_id(const char* id) {
service_registry_instance_lock();
const Service** _Nullable service_ptr = ServiceInstanceDict_get(service_instance_dict, id);
if (service_ptr == NULL) {
return NULL;
}
Service* service = (Service*) *service_ptr;
service_registry_instance_unlock();
return service;
}
void service_registry_for_each_manifest(ServiceManifestCallback callback, void* _Nullable context) {
APP_REGISTRY_FOR_EACH(manifest, {
callback(manifest, context);
});
}
// TODO: return proper error/status instead of BOOL
bool service_registry_start(const char* service_id) {
FURI_LOG_I(TAG, "starting %s", service_id);
const ServiceManifest* manifest = service_registry_find_manifest_by_id(service_id);
if (manifest == NULL) {
FURI_LOG_I(TAG, "manifest not found for %s", service_id);
return false;
}
Service* service = furi_service_alloc(manifest);
service->manifest->on_start(&service->context);
service_registry_instance_lock();
ServiceInstanceDict_set_at(service_instance_dict, manifest->id, service);
service_registry_instance_unlock();
FURI_LOG_I(TAG, "started %s", service_id);
return true;
}
bool service_registry_stop(const char* service_id) {
FURI_LOG_I(TAG, "stopping %s", service_id);
Service* service = service_registry_find_instance_by_id(service_id);
if (service == NULL) {
FURI_LOG_I(TAG, "service not running: %s", service_id);
return false;
}
service->manifest->on_stop(&service->context);
furi_service_free(service);
service_registry_instance_lock();
ServiceInstanceDict_erase(service_instance_dict, service_id);
service_registry_instance_unlock();
FURI_LOG_I(TAG, "stopped %s", service_id);
return true;
}

View File

@ -0,0 +1,25 @@
#pragma once
#include "service_manifest.h"
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
typedef void (*ServiceManifestCallback)(const ServiceManifest*, void* context);
void service_registry_init();
void service_registry_add(const ServiceManifest* manifest);
void service_registry_remove(const ServiceManifest* manifest);
const ServiceManifest _Nullable* service_registry_find_manifest_by_id(const char* id);
void service_registry_for_each_manifest(ServiceManifestCallback callback, void* _Nullable context);
bool service_registry_start(const char* service_id);
bool service_registry_stop(const char* service_id);
#ifdef __cplusplus
}
#endif

View File

@ -5,8 +5,19 @@ idf_component_register(
"src/services/loader"
"src/services/gui"
"src/services/gui/widgets"
INCLUDE_DIRS "src"
REQUIRES esp_lvgl_port esp_lcd esp_lcd_touch driver mlib cmsis_core furi nvs_flash spiffs fatfs
REQUIRES cmsis_core
esp_lcd
esp_lcd_touch
esp_lvgl_port
driver
fatfs
furi
mlib
nvs_flash
spiffs
)
set(ASSETS_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/assets")

View File

@ -1,9 +1,9 @@
#include "system_info.h"
#include "app_manifest.h"
#include "furi_extra_defines.h"
#include "thread.h"
#include "lvgl.h"
static void app_show(lv_obj_t* parent, void* context) {
static void app_show(Context* context, lv_obj_t* parent) {
UNUSED(context);
lv_obj_t* heap_info = lv_label_create(parent);
@ -38,5 +38,5 @@ AppManifest system_info_app = {
.type = AppTypeSystem,
.on_start = NULL,
.on_stop = NULL,
.on_show = app_show
.on_show = &app_show
};

View File

@ -1,13 +0,0 @@
#pragma once
#include "app_manifest.h"
#ifdef __cplusplus
extern "C" {
#endif
extern AppManifest system_info_app;
#ifdef __cplusplus
}
#endif

View File

@ -9,7 +9,7 @@ static void on_open_app(lv_event_t* e) {
lv_event_code_t code = lv_event_get_code(e);
if (code == LV_EVENT_CLICKED) {
const AppManifest* manifest = lv_event_get_user_data(e);
loader_start_app_nonblocking(manifest->id, NULL);
loader_start_app_nonblocking(manifest->id);
}
}
@ -20,7 +20,7 @@ static void add_app_to_list(const AppManifest* manifest, void* _Nullable parent)
lv_obj_add_event_cb(btn, &on_open_app, LV_EVENT_CLICKED, (void*)manifest);
}
static void desktop_show(lv_obj_t* parent, void* context) {
static void desktop_show(Context* context, lv_obj_t* parent) {
lv_obj_t* list = lv_list_create(parent);
lv_obj_set_size(list, LV_PCT(100), LV_PCT(100));
lv_obj_center(list);

View File

@ -143,8 +143,8 @@ static int32_t gui_main(void* p) {
// region AppManifest
static void gui_start(void* parameter) {
UNUSED(parameter);
static void gui_start(Context* context) {
UNUSED(context);
gui = gui_alloc();
@ -152,7 +152,9 @@ static void gui_start(void* parameter) {
furi_thread_start(gui->thread);
}
static void gui_stop() {
static void gui_stop(Context* context) {
UNUSED(context);
gui_lock();
FuriThreadId thread_id = furi_thread_get_id(gui->thread);

View File

@ -21,7 +21,6 @@ typedef struct Gui Gui;
*
* @remark thread safe
*
* @param gui Gui instance
* @param view_port ViewPort instance
* @param[in] layer GuiLayer where to place view_port
*/
@ -31,7 +30,6 @@ void gui_add_view_port(ViewPort* view_port, GuiLayer layer);
*
* @remark thread safe
*
* @param gui Gui instance
* @param view_port ViewPort instance
*/
void gui_remove_view_port(ViewPort* view_port);

View File

@ -33,8 +33,6 @@ struct Gui {
};
/** Update GUI, request redraw
*
* @param gui Gui instance
*/
void gui_request_draw();
@ -48,13 +46,9 @@ void gui_request_draw();
//void gui_input_events_callback(const void* value, void* ctx);
/** Lock GUI
*
* @param gui The Gui instance
*/
void gui_lock();
/** Unlock GUI
*
* @param gui The Gui instance
*/
void gui_unlock();

View File

@ -48,7 +48,7 @@ bool view_port_is_enabled(const ViewPort* view_port) {
return is_enabled;
}
void view_port_draw_callback_set(ViewPort* view_port, ViewPortDrawCallback callback, void* context) {
void view_port_draw_callback_set(ViewPort* view_port, ViewPortDrawCallback callback, Context* context) {
furi_assert(view_port);
furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk);
view_port->draw_callback = callback;
@ -91,7 +91,7 @@ void view_port_draw(ViewPort* view_port, lv_obj_t* parent) {
if (view_port->draw_callback) {
lv_obj_clean(parent);
lv_obj_set_style_no_padding(parent);
view_port->draw_callback(parent, view_port->draw_callback_context);
view_port->draw_callback(view_port->draw_callback_context, parent);
}
furi_mutex_release(view_port->mutex);

View File

@ -5,6 +5,7 @@ extern "C" {
#endif
#include "lvgl.h"
#include "context.h"
typedef struct ViewPort ViewPort;
@ -19,7 +20,7 @@ typedef enum {
/** ViewPort Draw callback
* @warning called from GUI thread
*/
typedef void (*ViewPortDrawCallback)(lv_obj_t* parent, void* context);
typedef void (*ViewPortDrawCallback)(Context* context, lv_obj_t* parent);
/** ViewPort allocator
*
@ -52,7 +53,7 @@ bool view_port_is_enabled(const ViewPort* view_port);
* @param callback appropriate callback function
* @param context context to pass to callback
*/
void view_port_draw_callback_set(ViewPort* view_port, ViewPortDrawCallback callback, void* context);
void view_port_draw_callback_set(ViewPort* view_port, ViewPortDrawCallback callback, Context* context);
/** Emit update signal to GUI system.
*
* Rendering will happen later after GUI system process signal.

View File

@ -1,5 +1,6 @@
#pragma once
#include "context.h"
#include "gui_i.h"
#include "mutex.h"
#include "view_port.h"
@ -10,7 +11,7 @@ struct ViewPort {
bool is_enabled;
ViewPortDrawCallback draw_callback;
void* draw_callback_context;
Context* draw_callback_context;
/*
ViewPortInputCallback input_callback;

View File

@ -27,7 +27,6 @@ static Loader* loader_alloc() {
&loader_main,
NULL
);
loader->app_data.args = NULL;
loader->app_data.app = NULL;
loader->app_data.view_port = NULL;
loader->mutex = xSemaphoreCreateRecursiveMutex();
@ -55,13 +54,12 @@ void loader_unlock() {
furi_check(xSemaphoreGiveRecursive(loader->mutex) == pdPASS);
}
LoaderStatus loader_start_app(const char* id, const char* args, FuriString* error_message) {
LoaderStatus loader_start_app(const char* id, FuriString* error_message) {
LoaderMessage message;
LoaderMessageLoaderStatusResult result;
message.type = LoaderMessageTypeAppStart;
message.start.id = id;
message.start.args = args;
message.start.error_message = error_message;
message.api_lock = api_lock_alloc_locked();
message.status_value = &result;
@ -70,13 +68,12 @@ LoaderStatus loader_start_app(const char* id, const char* args, FuriString* erro
return result.value;
}
void loader_start_app_nonblocking(const char* id, const char* args) {
void loader_start_app_nonblocking(const char* id) {
LoaderMessage message;
LoaderMessageLoaderStatusResult result;
message.type = LoaderMessageTypeAppStart;
message.start.id = id;
message.start.args = args;
message.start.error_message = NULL;
message.api_lock = NULL;
message.status_value = &result;
@ -142,8 +139,7 @@ static LoaderStatus loader_make_success_status(FuriString* error_message) {
}
static void loader_start_app_with_manifest(
const AppManifest* _Nonnull manifest,
const char* args
const AppManifest* _Nonnull manifest
) {
FURI_LOG_I(TAG, "start with manifest %s", manifest->id);
@ -156,17 +152,19 @@ static void loader_start_app_with_manifest(
loader_lock();
loader->app_data.app = app;
loader->app_data.args = (void*)args;
if (manifest->on_start != NULL) {
manifest->on_start((void*)args);
manifest->on_start(&loader->app_data.app->context);
}
if (manifest->on_show != NULL) {
ViewPort* view_port = view_port_alloc();
loader->app_data.view_port = view_port;
view_port_draw_callback_set(view_port, manifest->on_show, NULL);
view_port_draw_callback_set(
view_port,
manifest->on_show,
&loader->app_data.app->context
);
gui_add_view_port(view_port, GuiLayerWindow);
} else {
loader->app_data.view_port = NULL;
@ -182,7 +180,6 @@ static void loader_start_app_with_manifest(
static LoaderStatus loader_do_start_by_id(
const char* id,
const char* args,
FuriString* _Nullable error_message
) {
FURI_LOG_I(TAG, "Start by id %s", id);
@ -197,7 +194,7 @@ static LoaderStatus loader_do_start_by_id(
);
}
loader_start_app_with_manifest(manifest, args);
loader_start_app_with_manifest(manifest);
return loader_make_success_status(error_message);
}
@ -220,12 +217,7 @@ static void loader_do_stop_app() {
}
if (app->manifest->on_stop) {
app->manifest->on_stop();
}
if (loader->app_data.args) {
free(loader->app_data.args);
loader->app_data.args = NULL;
app->manifest->on_stop(&app->context);
}
furi_app_free(loader->app_data.app);
@ -268,7 +260,6 @@ static int32_t loader_main(void* p) {
}
message.status_value->value = loader_do_start_by_id(
message.start.id,
message.start.args,
message.start.error_message
);
if (message.api_lock) {
@ -290,8 +281,8 @@ static int32_t loader_main(void* p) {
// region AppManifest
static void loader_start(void* parameter) {
UNUSED(parameter);
static void loader_start(Context* context) {
UNUSED(context);
furi_check(loader == NULL);
loader = loader_alloc();
@ -299,7 +290,8 @@ static void loader_start(void* parameter) {
furi_thread_start(loader->thread);
}
static void loader_stop() {
static void loader_stop(Context* context) {
UNUSED(context);
furi_check(loader != NULL);
LoaderMessage message = {
.api_lock = NULL,

View File

@ -29,21 +29,19 @@ typedef struct {
/**
* @brief Close any running app, then start new one. Blocking.
* @param[in] loader loader instance
* @param[in] id application name or id
* @param[in] args application arguments
* @param[out] error_message detailed error message, can be NULL
* @return LoaderStatus
*/
LoaderStatus loader_start_app(const char* id, const char* args, FuriString* error_message);
LoaderStatus loader_start_app(const char* id, FuriString* error_message);
/**
* @brief Close any running app, then start new one. Non-blocking.
* @param[in] loader loader instance
* @param[in] id application name or id
* @param[in] args application arguments
*/
void loader_start_app_nonblocking(const char* id, const char* args);
void loader_start_app_nonblocking(const char* id);
void loader_stop_app();
@ -52,7 +50,6 @@ bool loader_is_app_running();
const AppManifest* _Nullable loader_get_current_app();
/**
* @brief Start application with GUI error message
* @param[in] instance loader instance
* @param[in] name application name or id
* @param[in] args application arguments
* @return LoaderStatus
@ -61,13 +58,11 @@ const AppManifest* _Nullable loader_get_current_app();
/**
* @brief Show loader menu
* @param[in] instance loader instance
*/
void loader_show_menu();
/**
* @brief Get loader pubsub
* @param[in] instance loader instance
* @return FuriPubSub*
*/
FuriPubSub* loader_get_pubsub();

View File

@ -11,7 +11,6 @@
#include "thread.h"
typedef struct {
char* args;
App* app;
ViewPort* view_port;
} LoaderAppData;
@ -32,7 +31,6 @@ typedef enum {
typedef struct {
const char* id;
const char* args;
FuriString* error_message;
} LoaderMessageAppStart;

View File

@ -1,15 +1,14 @@
#include "tactility.h"
#include "app_manifest_registry.h"
#include "devices_i.h"
#include "furi.h"
#include "graphics_i.h"
#include "partitions.h"
#include "services/gui/gui.h"
#include "tactility.h"
#include "service_registry.h"
#define TAG "tactility"
Gui* gui_alloc();
// System services
extern const ServiceManifest gui_service;
extern const ServiceManifest loader_service;
@ -18,13 +17,6 @@ extern const ServiceManifest desktop_service;
// System apps
extern const AppManifest system_info_app;
void start_service(const ServiceManifest* _Nonnull manifest) {
FURI_LOG_I(TAG, "Starting service %s", manifest->id);
furi_check(manifest->on_start, "service must define on_start");
manifest->on_start(NULL);
// TODO: keep track of running services
}
static void register_system_apps() {
FURI_LOG_I(TAG, "Registering default apps");
app_manifest_registry_add(&system_info_app);
@ -43,12 +35,18 @@ static void register_user_apps(const Config* _Nonnull config) {
}
}
static void register_system_services() {
FURI_LOG_I(TAG, "Registering system services");
service_registry_add(&gui_service);
service_registry_add(&loader_service);
service_registry_add(&desktop_service);
}
static void start_system_services() {
FURI_LOG_I(TAG, "Starting system services");
start_service(&gui_service);
start_service(&loader_service);
start_service(&desktop_service);
FURI_LOG_I(TAG, "System services started");
service_registry_start(gui_service.id);
service_registry_start(loader_service.id);
service_registry_start(desktop_service.id);
}
static void start_user_services(const Config* _Nonnull config) {
@ -63,7 +61,6 @@ static void start_user_services(const Config* _Nonnull config) {
break;
}
}
FURI_LOG_I(TAG, "User services started");
}
__attribute__((unused)) extern void tactility_start(const Config* _Nonnull config) {
@ -75,6 +72,7 @@ __attribute__((unused)) extern void tactility_start(const Config* _Nonnull confi
/*NbLvgl lvgl =*/tt_graphics_init(&hardware);
// Register all apps
register_system_services();
register_system_apps();
register_user_apps(config);

View File

@ -2,7 +2,7 @@
#include "services/gui/gui.h"
#include "services/loader/loader.h"
static void app_show(lv_obj_t* parent, void* context) {
static void app_show(Context* context, lv_obj_t* parent) {
UNUSED(context);
lv_obj_t* label = lv_label_create(parent);