App Loading via Loader (#1)
* app loading wip * various improvements irq/isr stuff is now working lvgl locking where needed hello world now uses proper mutex for app unlocking etc? * various improvements * cmsis_esp improvements * implement interrupts
This commit is contained in:
committed by
GitHub
parent
60372076d5
commit
b9427d4eba
@@ -1,10 +1,9 @@
|
||||
idf_component_register(
|
||||
SRC_DIRS "src"
|
||||
"src/applications"
|
||||
"src/applications/system/system_info"
|
||||
"src/applications/services/desktop"
|
||||
"src/applications/services/loader"
|
||||
"src/applications/services/gui"
|
||||
"src/apps/system/system_info"
|
||||
"src/apps/services/desktop"
|
||||
"src/apps/services/loader"
|
||||
"src/apps/services/gui"
|
||||
INCLUDE_DIRS "src"
|
||||
REQUIRES esp_lvgl_port esp_lcd esp_lcd_touch driver mlib cmsis_core furi
|
||||
)
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
#include "app_i.h"
|
||||
#include "check.h"
|
||||
|
||||
const char* prv_type_service = "service";
|
||||
const char* prv_type_system = "system";
|
||||
const char* prv_type_user = "user";
|
||||
|
||||
const char* nb_app_type_to_string(AppType type) {
|
||||
switch (type) {
|
||||
case SERVICE:
|
||||
return prv_type_service;
|
||||
case SYSTEM:
|
||||
return prv_type_system;
|
||||
case USER:
|
||||
return prv_type_user;
|
||||
default:
|
||||
furi_crash();
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "lvgl.h"
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define NB_APP_ID_LENGTH 32
|
||||
#define NB_APP_NAME_LENGTH 32
|
||||
|
||||
typedef enum {
|
||||
SERVICE,
|
||||
SYSTEM,
|
||||
USER
|
||||
} AppType;
|
||||
|
||||
typedef enum {
|
||||
NB_TASK_PRIORITY_DEFAULT = 10
|
||||
} AppPriority;
|
||||
|
||||
typedef enum {
|
||||
NB_TASK_STACK_SIZE_DEFAULT = 2048
|
||||
} AppStackSize;
|
||||
|
||||
typedef int32_t (*AppEntryPoint)(void _Nonnull* parameter);
|
||||
|
||||
typedef struct {
|
||||
const char id[NB_APP_ID_LENGTH];
|
||||
const char name[NB_APP_NAME_LENGTH];
|
||||
const AppType type;
|
||||
const AppEntryPoint _Nullable entry_point;
|
||||
const AppStackSize stack_size;
|
||||
const AppPriority priority;
|
||||
} App;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,13 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "app.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
const char* nb_app_type_to_string(AppType type);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,28 +0,0 @@
|
||||
#include "applications_i.h"
|
||||
|
||||
// System services
|
||||
extern const App desktop_app;
|
||||
extern const App gui_app;
|
||||
extern const App loader_app;
|
||||
|
||||
// System apps
|
||||
extern const App system_info_app;
|
||||
|
||||
const App* const NANOBAKE_SERVICES[] = {
|
||||
&desktop_app,
|
||||
&gui_app,
|
||||
&loader_app
|
||||
};
|
||||
|
||||
const size_t NANOBAKE_SERVICES_COUNT = sizeof(NANOBAKE_SERVICES) / sizeof(App*);
|
||||
|
||||
const App* const NANOBAKE_SYSTEM_APPS[] = {
|
||||
&system_info_app
|
||||
};
|
||||
|
||||
const size_t NANOBAKE_SYSTEM_APPS_COUNT = sizeof(NANOBAKE_SYSTEM_APPS) / sizeof(App*);
|
||||
|
||||
const OnSystemStart NANOBAKE_ON_SYSTEM_START[] = {
|
||||
};
|
||||
|
||||
const size_t NANOBAKE_ON_SYSTEM_START_COUNT = sizeof(NANOBAKE_ON_SYSTEM_START) / sizeof(OnSystemStart);
|
||||
@@ -1,23 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "app.h"
|
||||
#include "devices.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef void (*OnSystemStart)(Devices* hardware);
|
||||
|
||||
extern const App* const NANOBAKE_SERVICES[];
|
||||
extern const size_t NANOBAKE_SERVICES_COUNT;
|
||||
|
||||
extern const App* const NANOBAKE_SYSTEM_APPS[];
|
||||
extern const size_t NANOBAKE_SYSTEM_APPS_COUNT;
|
||||
|
||||
extern const OnSystemStart NANOBAKE_ON_SYSTEM_START[];
|
||||
extern const size_t NANOBAKE_ON_SYSTEM_START_COUNT;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,17 +0,0 @@
|
||||
#include "loader.h"
|
||||
#include "core_defines.h"
|
||||
|
||||
static int32_t prv_loader_main(void* param) {
|
||||
UNUSED(param);
|
||||
printf("loader app init\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
const App loader_app = {
|
||||
.id = "loader",
|
||||
.name = "Loader",
|
||||
.type = SERVICE,
|
||||
.entry_point = &prv_loader_main,
|
||||
.stack_size = NB_TASK_STACK_SIZE_DEFAULT,
|
||||
.priority = NB_TASK_PRIORITY_DEFAULT
|
||||
};
|
||||
@@ -1,13 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "app.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern const App loader_app;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,39 +0,0 @@
|
||||
#include "system_info.h"
|
||||
#include "core_defines.h"
|
||||
#include "nanobake.h"
|
||||
#include "thread.h"
|
||||
|
||||
static int32_t system_info_entry_point(void* param) {
|
||||
UNUSED(param);
|
||||
|
||||
// Wait for all apps to start
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
|
||||
size_t system_service_count = nanobake_get_app_thread_count();
|
||||
printf("Running apps:\n");
|
||||
for (int i = 0; i < system_service_count; ++i) {
|
||||
FuriThreadId thread_id = nanobake_get_app_thread_id(i);
|
||||
const char* appid = furi_thread_get_appid(thread_id);
|
||||
const char* name = furi_thread_get_name(thread_id);
|
||||
bool is_suspended = furi_thread_is_suspended(thread_id);
|
||||
const char* status = is_suspended ? "suspended" : "active";
|
||||
printf(" - [%s] %s (%s)\n", status, name, appid);
|
||||
}
|
||||
|
||||
printf(
|
||||
"Heap memory available: %d / %d\n",
|
||||
heap_caps_get_free_size(MALLOC_CAP_DEFAULT),
|
||||
heap_caps_get_total_size(MALLOC_CAP_DEFAULT)
|
||||
);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
App system_info_app = {
|
||||
.id = "systeminfo",
|
||||
.name = "System Info",
|
||||
.type = SYSTEM,
|
||||
.entry_point = &system_info_entry_point,
|
||||
.stack_size = NB_TASK_STACK_SIZE_DEFAULT,
|
||||
.priority = NB_TASK_PRIORITY_DEFAULT
|
||||
};
|
||||
+5
-6
@@ -1,6 +1,5 @@
|
||||
#include "desktop.h"
|
||||
#include "core_defines.h"
|
||||
#include "devices.h"
|
||||
#include "furi_extra_defines.h"
|
||||
|
||||
static int32_t prv_desktop_main(void* param) {
|
||||
UNUSED(param);
|
||||
@@ -8,11 +7,11 @@ static int32_t prv_desktop_main(void* param) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const App desktop_app = {
|
||||
const AppManifest desktop_app = {
|
||||
.id = "desktop",
|
||||
.name = "Desktop",
|
||||
.type = SERVICE,
|
||||
.icon = NULL,
|
||||
.type = AppTypeService,
|
||||
.entry_point = &prv_desktop_main,
|
||||
.stack_size = NB_TASK_STACK_SIZE_DEFAULT,
|
||||
.priority = NB_TASK_PRIORITY_DEFAULT
|
||||
.stack_size = AppStackSizeNormal
|
||||
};
|
||||
+2
-2
@@ -1,12 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "app.h"
|
||||
#include "app_manifest.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern const App desktop_app;
|
||||
extern const AppManifest desktop_app;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
+10
-5
@@ -1,5 +1,6 @@
|
||||
#include "check.h"
|
||||
#include "core_defines.h"
|
||||
#include "esp_lvgl_port.h"
|
||||
#include "furi_extra_defines.h"
|
||||
#include "gui_i.h"
|
||||
#include "record.h"
|
||||
|
||||
@@ -184,7 +185,11 @@ Gui* gui_alloc() {
|
||||
Gui* gui = malloc(sizeof(Gui));
|
||||
gui->thread_id = furi_thread_get_current_id();
|
||||
gui->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
|
||||
furi_check(lvgl_port_lock(100));
|
||||
gui->lvgl_parent = lv_scr_act();
|
||||
lvgl_port_unlock();
|
||||
|
||||
gui->lockdown = false;
|
||||
furi_check(gui->mutex);
|
||||
for (size_t i = 0; i < GuiLayerMAX; i++) {
|
||||
@@ -231,11 +236,11 @@ __attribute((__noreturn__)) int32_t prv_gui_main(void* parameter) {
|
||||
}
|
||||
}
|
||||
|
||||
const App gui_app = {
|
||||
const AppManifest gui_app = {
|
||||
.id = "gui",
|
||||
.name = "GUI",
|
||||
.type = SERVICE,
|
||||
.icon = NULL,
|
||||
.type = AppTypeService,
|
||||
.entry_point = &prv_gui_main,
|
||||
.stack_size = NB_TASK_STACK_SIZE_DEFAULT,
|
||||
.priority = NB_TASK_PRIORITY_DEFAULT
|
||||
.stack_size = AppStackSizeNormal
|
||||
};
|
||||
+2
-12
@@ -1,22 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "app.h"
|
||||
#include "lvgl.h"
|
||||
#include "app_manifest.h"
|
||||
#include "view_port.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern const App gui_app;
|
||||
|
||||
/** Canvas Orientation */
|
||||
typedef enum {
|
||||
CanvasOrientationHorizontal,
|
||||
CanvasOrientationHorizontalFlip,
|
||||
CanvasOrientationVertical,
|
||||
CanvasOrientationVerticalFlip,
|
||||
} CanvasOrientation;
|
||||
extern const AppManifest gui_app;
|
||||
|
||||
/** Gui layers */
|
||||
typedef enum {
|
||||
@@ -36,7 +27,6 @@ typedef enum {
|
||||
typedef void (*GuiCanvasCommitCallback)(
|
||||
uint8_t* data,
|
||||
size_t size,
|
||||
CanvasOrientation orientation,
|
||||
void* context
|
||||
);
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
#include "icon_i.h"
|
||||
|
||||
uint8_t icon_get_width(const Icon* instance) {
|
||||
return instance->width;
|
||||
}
|
||||
|
||||
uint8_t icon_get_height(const Icon* instance) {
|
||||
return instance->height;
|
||||
}
|
||||
|
||||
const uint8_t* icon_get_data(const Icon* instance) {
|
||||
return instance->frames[0];
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
struct Icon {
|
||||
const uint8_t width;
|
||||
const uint8_t height;
|
||||
const uint8_t frame_count;
|
||||
const uint8_t frame_rate;
|
||||
const uint8_t* const* frames;
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include "icon.h"
|
||||
|
||||
typedef struct {
|
||||
const uint8_t width;
|
||||
const uint8_t height;
|
||||
const uint8_t frame_count;
|
||||
const uint8_t frame_rate;
|
||||
const uint8_t* const* frames;
|
||||
} Icon;
|
||||
+4
@@ -2,6 +2,7 @@
|
||||
#include "gui.h"
|
||||
#include "gui_i.h"
|
||||
#include "view_port_i.h"
|
||||
#include "esp_lvgl_port.h"
|
||||
|
||||
#define TAG "viewport"
|
||||
|
||||
@@ -88,7 +89,10 @@ void view_port_draw(ViewPort* view_port, lv_obj_t* parent) {
|
||||
furi_check(view_port->gui);
|
||||
|
||||
if (view_port->draw_callback) {
|
||||
furi_check(lvgl_port_lock(100));
|
||||
lv_obj_clean(parent);
|
||||
lvgl_port_unlock();
|
||||
|
||||
view_port->draw_callback(parent, view_port->draw_callback_context);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,290 @@
|
||||
#include "loader.h"
|
||||
#include "app_i.h"
|
||||
#include "app_manifest.h"
|
||||
#include "app_manifest_registry.h"
|
||||
#include "loader_i.h"
|
||||
#include <sys/cdefs.h>
|
||||
#include "esp_heap_caps.h"
|
||||
|
||||
#define TAG "Loader"
|
||||
#define LOADER_MAGIC_THREAD_VALUE 0xDEADBEEF
|
||||
|
||||
LoaderStatus loader_start(Loader* loader, const char* id, const char* args, FuriString* error_message) {
|
||||
LoaderMessage message;
|
||||
LoaderMessageLoaderStatusResult result;
|
||||
|
||||
message.type = LoaderMessageTypeStartByName;
|
||||
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;
|
||||
furi_message_queue_put(loader->queue, &message, FuriWaitForever);
|
||||
api_lock_wait_unlock_and_free(message.api_lock);
|
||||
return result.value;
|
||||
}
|
||||
|
||||
bool loader_lock(Loader* loader) {
|
||||
LoaderMessage message;
|
||||
LoaderMessageBoolResult result;
|
||||
message.type = LoaderMessageTypeLock;
|
||||
message.api_lock = api_lock_alloc_locked();
|
||||
message.bool_value = &result;
|
||||
furi_message_queue_put(loader->queue, &message, FuriWaitForever);
|
||||
api_lock_wait_unlock_and_free(message.api_lock);
|
||||
return result.value;
|
||||
}
|
||||
|
||||
void loader_unlock(Loader* loader) {
|
||||
LoaderMessage message;
|
||||
message.type = LoaderMessageTypeUnlock;
|
||||
furi_message_queue_put(loader->queue, &message, FuriWaitForever);
|
||||
}
|
||||
|
||||
bool loader_is_locked(Loader* loader) {
|
||||
LoaderMessage message;
|
||||
LoaderMessageBoolResult result;
|
||||
message.type = LoaderMessageTypeIsLocked;
|
||||
message.api_lock = api_lock_alloc_locked();
|
||||
message.bool_value = &result;
|
||||
furi_message_queue_put(loader->queue, &message, FuriWaitForever);
|
||||
api_lock_wait_unlock_and_free(message.api_lock);
|
||||
return result.value;
|
||||
}
|
||||
|
||||
FuriPubSub* loader_get_pubsub(Loader* loader) {
|
||||
furi_assert(loader);
|
||||
// it's safe to return pubsub without locking
|
||||
// because it's never freed and loader is never exited
|
||||
// also the loader instance cannot be obtained until the pubsub is created
|
||||
return loader->pubsub;
|
||||
}
|
||||
|
||||
// callbacks
|
||||
|
||||
static void loader_thread_state_callback(FuriThreadState thread_state, void* context) {
|
||||
furi_assert(context);
|
||||
|
||||
Loader* loader = context;
|
||||
|
||||
if (thread_state == FuriThreadStateRunning) {
|
||||
LoaderEvent event;
|
||||
event.type = LoaderEventTypeApplicationStarted;
|
||||
furi_pubsub_publish(loader->pubsub, &event);
|
||||
} else if (thread_state == FuriThreadStateStopped) {
|
||||
LoaderMessage message;
|
||||
message.type = LoaderMessageTypeAppClosed;
|
||||
furi_message_queue_put(loader->queue, &message, FuriWaitForever);
|
||||
}
|
||||
}
|
||||
|
||||
// implementation
|
||||
|
||||
static Loader* loader_alloc() {
|
||||
Loader* loader = malloc(sizeof(Loader));
|
||||
loader->pubsub = furi_pubsub_alloc();
|
||||
loader->queue = furi_message_queue_alloc(1, sizeof(LoaderMessage));
|
||||
loader->app_data.args = NULL;
|
||||
loader->app_data.thread = NULL;
|
||||
loader->app_data.app = NULL;
|
||||
return loader;
|
||||
}
|
||||
|
||||
static void loader_start_app_thread(Loader* loader) {
|
||||
// setup thread state callbacks
|
||||
furi_thread_set_state_context(loader->app_data.thread, loader);
|
||||
furi_thread_set_state_callback(loader->app_data.thread, loader_thread_state_callback);
|
||||
|
||||
// start app thread
|
||||
furi_thread_start(loader->app_data.thread);
|
||||
}
|
||||
|
||||
static void loader_log_status_error(
|
||||
LoaderStatus status,
|
||||
FuriString* error_message,
|
||||
const char* format,
|
||||
va_list args
|
||||
) {
|
||||
if (error_message) {
|
||||
furi_string_vprintf(error_message, format, args);
|
||||
FURI_LOG_E(TAG, "Status [%d]: %s", status, furi_string_get_cstr(error_message));
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Status [%d]", status);
|
||||
}
|
||||
}
|
||||
|
||||
static LoaderStatus loader_make_status_error(
|
||||
LoaderStatus status,
|
||||
FuriString* error_message,
|
||||
const char* format,
|
||||
...
|
||||
) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
loader_log_status_error(status, error_message, format, args);
|
||||
va_end(args);
|
||||
return status;
|
||||
}
|
||||
|
||||
static LoaderStatus loader_make_success_status(FuriString* error_message) {
|
||||
if (error_message) {
|
||||
furi_string_set(error_message, "App started");
|
||||
}
|
||||
|
||||
return LoaderStatusOk;
|
||||
}
|
||||
|
||||
static void loader_start_app(
|
||||
Loader* loader,
|
||||
const AppManifest* _Nonnull manifest,
|
||||
const char* args
|
||||
) {
|
||||
FURI_LOG_I(TAG, "Starting %s", manifest->id);
|
||||
|
||||
App* _Nonnull app = furi_app_alloc(manifest);
|
||||
loader->app_data.app = app;
|
||||
|
||||
FuriThread* thread = furi_app_alloc_thread(loader->app_data.app, args);
|
||||
loader->app_data.app->thread = thread;
|
||||
loader->app_data.thread = thread;
|
||||
|
||||
loader_start_app_thread(loader);
|
||||
}
|
||||
|
||||
// process messages
|
||||
|
||||
static bool loader_do_is_locked(Loader* loader) {
|
||||
return loader->app_data.thread != NULL;
|
||||
}
|
||||
|
||||
static LoaderStatus loader_do_start_by_id(
|
||||
Loader* loader,
|
||||
const char* id,
|
||||
const char* args,
|
||||
FuriString* error_message
|
||||
) {
|
||||
// check lock
|
||||
if (loader_do_is_locked(loader)) {
|
||||
const char* current_thread_name =
|
||||
furi_thread_get_name(furi_thread_get_id(loader->app_data.thread));
|
||||
return loader_make_status_error(
|
||||
LoaderStatusErrorAppStarted,
|
||||
error_message,
|
||||
"Loader is locked, please close the \"%s\" first",
|
||||
current_thread_name
|
||||
);
|
||||
}
|
||||
|
||||
const AppManifest* manifest = app_manifest_registry_find_by_id(id);
|
||||
if (manifest == NULL) {
|
||||
return loader_make_status_error(
|
||||
LoaderStatusErrorUnknownApp,
|
||||
error_message,
|
||||
"Application \"%s\" not found",
|
||||
id
|
||||
);
|
||||
}
|
||||
|
||||
loader_start_app(loader, manifest, args);
|
||||
return loader_make_success_status(error_message);
|
||||
}
|
||||
|
||||
static bool loader_do_lock(Loader* loader) {
|
||||
if (loader->app_data.thread) {
|
||||
return false;
|
||||
}
|
||||
|
||||
loader->app_data.thread = (FuriThread*)LOADER_MAGIC_THREAD_VALUE;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void loader_do_unlock(Loader* loader) {
|
||||
furi_check(loader->app_data.thread == (FuriThread*)LOADER_MAGIC_THREAD_VALUE);
|
||||
loader->app_data.thread = NULL;
|
||||
}
|
||||
|
||||
static void loader_do_app_closed(Loader* loader) {
|
||||
furi_assert(loader->app_data.thread);
|
||||
|
||||
furi_thread_join(loader->app_data.thread);
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"App %s returned: %li",
|
||||
loader->app_data.app->manifest->id,
|
||||
furi_thread_get_return_code(loader->app_data.thread)
|
||||
);
|
||||
|
||||
if (loader->app_data.args) {
|
||||
free(loader->app_data.args);
|
||||
loader->app_data.args = NULL;
|
||||
}
|
||||
|
||||
if (loader->app_data.app) {
|
||||
furi_app_free(loader->app_data.app);
|
||||
loader->app_data.app = NULL;
|
||||
} else {
|
||||
assert(loader->app_data.thread == NULL);
|
||||
furi_thread_free(loader->app_data.thread);
|
||||
}
|
||||
|
||||
loader->app_data.thread = NULL;
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Application stopped. Free heap: %zu",
|
||||
heap_caps_get_free_size(MALLOC_CAP_DEFAULT)
|
||||
);
|
||||
|
||||
LoaderEvent event;
|
||||
event.type = LoaderEventTypeApplicationStopped;
|
||||
furi_pubsub_publish(loader->pubsub, &event);
|
||||
}
|
||||
|
||||
// app
|
||||
|
||||
_Noreturn int32_t loader_main(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
Loader* loader = loader_alloc();
|
||||
furi_record_create(RECORD_LOADER, loader);
|
||||
|
||||
LoaderMessage message;
|
||||
while (true) {
|
||||
if (furi_message_queue_get(loader->queue, &message, FuriWaitForever) == FuriStatusOk) {
|
||||
switch (message.type) {
|
||||
case LoaderMessageTypeStartByName:
|
||||
message.status_value->value = loader_do_start_by_id(
|
||||
loader,
|
||||
message.start.id,
|
||||
message.start.args,
|
||||
message.start.error_message
|
||||
);
|
||||
api_lock_unlock(message.api_lock);
|
||||
break;
|
||||
case LoaderMessageTypeIsLocked:
|
||||
message.bool_value->value = loader_do_is_locked(loader);
|
||||
api_lock_unlock(message.api_lock);
|
||||
break;
|
||||
case LoaderMessageTypeAppClosed:
|
||||
loader_do_app_closed(loader);
|
||||
break;
|
||||
case LoaderMessageTypeLock:
|
||||
message.bool_value->value = loader_do_lock(loader);
|
||||
api_lock_unlock(message.api_lock);
|
||||
break;
|
||||
case LoaderMessageTypeUnlock:
|
||||
loader_do_unlock(loader);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const AppManifest loader_app = {
|
||||
.id = "loader",
|
||||
.name = "Loader",
|
||||
.icon = NULL,
|
||||
.type = AppTypeService,
|
||||
.entry_point = &loader_main,
|
||||
.stack_size = AppStackSizeNormal
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
#pragma once
|
||||
#include "furi_core.h"
|
||||
#include "furi_string.h"
|
||||
#include "pubsub.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define RECORD_LOADER "loader"
|
||||
|
||||
typedef struct Loader Loader;
|
||||
|
||||
typedef enum {
|
||||
LoaderStatusOk,
|
||||
LoaderStatusErrorAppStarted,
|
||||
LoaderStatusErrorUnknownApp,
|
||||
LoaderStatusErrorInternal,
|
||||
} LoaderStatus;
|
||||
|
||||
typedef enum {
|
||||
LoaderEventTypeApplicationStarted,
|
||||
LoaderEventTypeApplicationStopped
|
||||
} LoaderEventType;
|
||||
|
||||
typedef struct {
|
||||
LoaderEventType type;
|
||||
} LoaderEvent;
|
||||
|
||||
/**
|
||||
* @brief Start application
|
||||
* @param[in] instance 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(Loader* instance, const char* id, const char* args, FuriString* error_message);
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
//LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const char* args);
|
||||
|
||||
/**
|
||||
* @brief Lock application start
|
||||
* @param[in] instance loader instance
|
||||
* @return true on success
|
||||
*/
|
||||
bool loader_lock(Loader* instance);
|
||||
|
||||
/**
|
||||
* @brief Unlock application start
|
||||
* @param[in] instance loader instance
|
||||
*/
|
||||
void loader_unlock(Loader* instance);
|
||||
|
||||
/**
|
||||
* @brief Check if loader is locked
|
||||
* @param[in] instance loader instance
|
||||
* @return true if locked
|
||||
*/
|
||||
bool loader_is_locked(Loader* instance);
|
||||
|
||||
/**
|
||||
* @brief Show loader menu
|
||||
* @param[in] instance loader instance
|
||||
*/
|
||||
void loader_show_menu(Loader* instance);
|
||||
|
||||
/**
|
||||
* @brief Get loader pubsub
|
||||
* @param[in] instance loader instance
|
||||
* @return FuriPubSub*
|
||||
*/
|
||||
FuriPubSub* loader_get_pubsub(Loader* instance);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
#include "api_lock.h"
|
||||
#include "app_manifest.h"
|
||||
#include "loader.h"
|
||||
#include "message_queue.h"
|
||||
#include "pubsub.h"
|
||||
#include "thread.h"
|
||||
|
||||
typedef struct {
|
||||
char* args;
|
||||
FuriThread* thread;
|
||||
App* app;
|
||||
} LoaderAppData;
|
||||
|
||||
struct Loader {
|
||||
FuriPubSub* pubsub;
|
||||
FuriMessageQueue* queue;
|
||||
LoaderAppData app_data;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
LoaderMessageTypeStartByName,
|
||||
LoaderMessageTypeAppClosed,
|
||||
LoaderMessageTypeLock,
|
||||
LoaderMessageTypeUnlock,
|
||||
LoaderMessageTypeIsLocked,
|
||||
} LoaderMessageType;
|
||||
|
||||
typedef struct {
|
||||
const char* id;
|
||||
const char* args;
|
||||
FuriString* error_message;
|
||||
} LoaderMessageStartById;
|
||||
|
||||
typedef struct {
|
||||
LoaderStatus value;
|
||||
} LoaderMessageLoaderStatusResult;
|
||||
|
||||
typedef struct {
|
||||
bool value;
|
||||
} LoaderMessageBoolResult;
|
||||
|
||||
typedef struct {
|
||||
FuriApiLock api_lock;
|
||||
LoaderMessageType type;
|
||||
|
||||
union {
|
||||
LoaderMessageStartById start;
|
||||
};
|
||||
|
||||
union {
|
||||
LoaderMessageLoaderStatusResult* status_value;
|
||||
LoaderMessageBoolResult* bool_value;
|
||||
};
|
||||
} LoaderMessage;
|
||||
@@ -0,0 +1,24 @@
|
||||
#include "system_info.h"
|
||||
#include "furi_extra_defines.h"
|
||||
#include "thread.h"
|
||||
|
||||
static int32_t system_info_entry_point(void* param) {
|
||||
UNUSED(param);
|
||||
|
||||
printf(
|
||||
"Heap memory available: %d / %d\n",
|
||||
heap_caps_get_free_size(MALLOC_CAP_DEFAULT),
|
||||
heap_caps_get_total_size(MALLOC_CAP_DEFAULT)
|
||||
);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
AppManifest system_info_app = {
|
||||
.id = "systeminfo",
|
||||
.name = "System Info",
|
||||
.icon = NULL,
|
||||
.type = AppTypeSystem,
|
||||
.entry_point = &system_info_entry_point,
|
||||
.stack_size = AppStackSizeNormal
|
||||
};
|
||||
+2
-2
@@ -1,12 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "app.h"
|
||||
#include "app_manifest.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern App system_info_app;
|
||||
extern AppManifest system_info_app;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
@@ -9,7 +9,7 @@ Devices nb_devices_create(Config _Nonnull* config) {
|
||||
furi_check(config->display_driver != NULL, "no display driver configured");
|
||||
DisplayDriver display_driver = config->display_driver();
|
||||
ESP_LOGI(TAG, "display with driver %s", display_driver.name);
|
||||
DisplayDevice* display = nb_display_alloc(&display_driver);
|
||||
DisplayDevice* display = nb_display_device_alloc(&display_driver);
|
||||
|
||||
TouchDevice* touch = NULL;
|
||||
if (config->touch_driver != NULL) {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#include "check.h"
|
||||
#include "display.h"
|
||||
|
||||
DisplayDevice _Nonnull* nb_display_alloc(DisplayDriver _Nonnull* driver) {
|
||||
DisplayDevice _Nonnull* nb_display_device_alloc(DisplayDriver _Nonnull* driver) {
|
||||
DisplayDevice _Nonnull* display = malloc(sizeof(DisplayDevice));
|
||||
memset(display, 0, sizeof(DisplayDevice));
|
||||
furi_check(driver->create_display(display), "failed to create display");
|
||||
furi_check(driver->create_display_device(display), "failed to create display");
|
||||
furi_check(display->io_handle != NULL);
|
||||
furi_check(display->display_handle != NULL);
|
||||
furi_check(display->horizontal_resolution != 0);
|
||||
|
||||
@@ -16,20 +16,21 @@ typedef struct {
|
||||
bool mirror_x;
|
||||
bool mirror_y;
|
||||
bool swap_xy;
|
||||
bool monochrome;
|
||||
} DisplayDevice;
|
||||
|
||||
typedef bool (*CreateDisplay)(DisplayDevice* display);
|
||||
|
||||
typedef struct {
|
||||
char name[32];
|
||||
CreateDisplay create_display;
|
||||
CreateDisplay create_display_device;
|
||||
} DisplayDriver;
|
||||
|
||||
/**
|
||||
* @param[in] driver
|
||||
* @return allocated display object
|
||||
*/
|
||||
DisplayDevice _Nonnull* nb_display_alloc(DisplayDriver _Nonnull* driver);
|
||||
DisplayDevice _Nonnull* nb_display_device_alloc(DisplayDriver _Nonnull* driver);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ Lvgl nb_graphics_init(Devices _Nonnull* hardware) {
|
||||
.double_buffer = 0,
|
||||
.hres = display->horizontal_resolution,
|
||||
.vres = display->vertical_resolution,
|
||||
.monochrome = false,
|
||||
.monochrome = display->monochrome,
|
||||
.rotation = {
|
||||
.swap_xy = display->swap_xy,
|
||||
.mirror_x = display->mirror_x,
|
||||
@@ -37,6 +37,8 @@ Lvgl nb_graphics_init(Devices _Nonnull* hardware) {
|
||||
};
|
||||
|
||||
lv_disp_t _Nonnull* disp = lvgl_port_add_disp(&disp_cfg);
|
||||
furi_check(disp != NULL, "failed to add display");
|
||||
|
||||
lv_indev_t _Nullable* touch_indev = NULL;
|
||||
|
||||
// Add touch
|
||||
|
||||
@@ -1,86 +1,61 @@
|
||||
#include "nanobake.h"
|
||||
#include "app_i.h"
|
||||
#include "applications/applications_i.h"
|
||||
#include "app_manifest_registry.h"
|
||||
#include "devices_i.h"
|
||||
#include "esp_log.h"
|
||||
#include "furi.h"
|
||||
#include "graphics_i.h"
|
||||
#include "m-list.h"
|
||||
// Furi
|
||||
#include "kernel.h"
|
||||
#include "record.h"
|
||||
#include "thread.h"
|
||||
|
||||
M_LIST_DEF(thread_ids, FuriThreadId);
|
||||
|
||||
#define TAG "nanobake"
|
||||
|
||||
thread_ids_t prv_thread_ids;
|
||||
// System services
|
||||
extern const AppManifest desktop_app;
|
||||
extern const AppManifest gui_app;
|
||||
extern const AppManifest loader_app;
|
||||
|
||||
static void prv_furi_init() {
|
||||
// TODO: can we remove the suspend-resume logic?
|
||||
if (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) {
|
||||
vTaskSuspendAll();
|
||||
}
|
||||
|
||||
furi_record_init();
|
||||
|
||||
xTaskResumeAll();
|
||||
}
|
||||
|
||||
FuriThreadId nanobake_get_app_thread_id(size_t index) {
|
||||
return *thread_ids_get(prv_thread_ids, index);
|
||||
}
|
||||
|
||||
size_t nanobake_get_app_thread_count() {
|
||||
return thread_ids_size(prv_thread_ids);
|
||||
}
|
||||
|
||||
static void prv_start_app(const App _Nonnull* app) {
|
||||
ESP_LOGI(TAG, "Starting %s app \"%s\"", nb_app_type_to_string(app->type), app->name);
|
||||
// System apps
|
||||
extern const AppManifest system_info_app;
|
||||
|
||||
void start_service(const AppManifest* _Nonnull manifest) {
|
||||
// TODO: keep track of running services
|
||||
FURI_LOG_I(TAG, "Starting service %s", manifest->name);
|
||||
FuriThread* thread = furi_thread_alloc_ex(
|
||||
app->name,
|
||||
app->stack_size,
|
||||
app->entry_point,
|
||||
manifest->name,
|
||||
manifest->stack_size,
|
||||
manifest->entry_point,
|
||||
NULL
|
||||
);
|
||||
|
||||
if (app->type == SERVICE) {
|
||||
furi_thread_mark_as_service(thread);
|
||||
}
|
||||
|
||||
furi_thread_set_appid(thread, app->id);
|
||||
furi_thread_set_priority(thread, app->priority);
|
||||
furi_thread_mark_as_service(thread);
|
||||
furi_thread_set_appid(thread, manifest->id);
|
||||
furi_thread_start(thread);
|
||||
|
||||
FuriThreadId thread_id = furi_thread_get_id(thread);
|
||||
thread_ids_push_back(prv_thread_ids, thread_id);
|
||||
}
|
||||
|
||||
__attribute__((unused)) extern void nanobake_start(Config _Nonnull* config) {
|
||||
prv_furi_init();
|
||||
static void register_apps(Config* _Nonnull config) {
|
||||
FURI_LOG_I(TAG, "Registering core apps");
|
||||
app_manifest_registry_add(&desktop_app);
|
||||
app_manifest_registry_add(&gui_app);
|
||||
app_manifest_registry_add(&loader_app);
|
||||
app_manifest_registry_add(&system_info_app);
|
||||
|
||||
FURI_LOG_I(TAG, "Registering user apps");
|
||||
for (size_t i = 0; i < config->apps_count; i++) {
|
||||
app_manifest_registry_add(config->apps[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void start_services() {
|
||||
FURI_LOG_I(TAG, "Starting services");
|
||||
app_manifest_registry_for_each_of_type(AppTypeService, start_service);
|
||||
FURI_LOG_I(TAG, "Startup complete");
|
||||
}
|
||||
|
||||
__attribute__((unused)) extern void nanobake_start(Config* _Nonnull config) {
|
||||
furi_init();
|
||||
|
||||
Devices hardware = nb_devices_create(config);
|
||||
/*NbLvgl lvgl =*/nb_graphics_init(&hardware);
|
||||
|
||||
thread_ids_init(prv_thread_ids);
|
||||
register_apps(config);
|
||||
|
||||
ESP_LOGI(TAG, "Starting apps");
|
||||
|
||||
// Services
|
||||
for (size_t i = 0; i < NANOBAKE_SERVICES_COUNT; i++) {
|
||||
prv_start_app(NANOBAKE_SERVICES[i]);
|
||||
}
|
||||
|
||||
// System
|
||||
for (size_t i = 0; i < NANOBAKE_SYSTEM_APPS_COUNT; i++) {
|
||||
prv_start_app(NANOBAKE_SYSTEM_APPS[i]);
|
||||
}
|
||||
|
||||
// User
|
||||
for (size_t i = 0; i < config->apps_count; i++) {
|
||||
prv_start_app(config->apps[i]);
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Startup complete");
|
||||
start_services();
|
||||
// TODO: option to await starting services?
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "app.h"
|
||||
#include "app_manifest.h"
|
||||
#include "devices.h"
|
||||
#include "core_defines.h"
|
||||
#include "base.h"
|
||||
#include "furi_extra_defines.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -21,7 +20,7 @@ typedef struct {
|
||||
const CreateTouchDriver _Nullable touch_driver;
|
||||
// List of user applications
|
||||
const size_t apps_count;
|
||||
const App* const apps[];
|
||||
const AppManifest* const apps[];
|
||||
} Config;
|
||||
|
||||
__attribute__((unused)) extern void nanobake_start(Config _Nonnull* config);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
TouchDevice _Nonnull* nb_touch_alloc(TouchDriver _Nonnull* driver) {
|
||||
TouchDevice _Nonnull* touch = malloc(sizeof(TouchDevice));
|
||||
bool success = driver->create_touch(
|
||||
bool success = driver->create_touch_device(
|
||||
&(touch->io_handle),
|
||||
&(touch->touch_handle)
|
||||
);
|
||||
|
||||
@@ -11,7 +11,7 @@ typedef bool (*CreateTouch)(esp_lcd_panel_io_handle_t* io_handle, esp_lcd_touch_
|
||||
|
||||
typedef struct {
|
||||
char name[32];
|
||||
CreateTouch create_touch;
|
||||
CreateTouch create_touch_device;
|
||||
} TouchDriver;
|
||||
|
||||
typedef struct {
|
||||
|
||||
Reference in New Issue
Block a user