diff --git a/README.md b/README.md index 4a113de9..b6d5020c 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ Predefined configurations are available for: - SSD1963 - ST7262E43 - ST7789 +- ST7796 **Touch** (see [Espressif Registry](https://components.espressif.com/components?q=esp_lcd_touch)): - CST8xx diff --git a/components/furi/src/app.c b/components/furi/src/app.c index b9a0e7fd..359290c8 100644 --- a/components/furi/src/app.c +++ b/components/furi/src/app.c @@ -5,40 +5,9 @@ #define TAG "app" -const char* prv_type_service = "service"; -const char* prv_type_system = "system"; -const char* prv_type_user = "user"; - -static FuriThreadPriority get_thread_priority(AppType type) { - switch (type) { - case AppTypeService: - return FuriThreadPriorityHighest; - case AppTypeSystem: - return FuriThreadPriorityHigh; - case AppTypeUser: - return FuriThreadPriorityNormal; - default: - furi_crash("no priority defined for app type"); - } -} - -const char* furi_app_type_to_string(AppType type) { - switch (type) { - case AppTypeService: - return prv_type_service; - case AppTypeSystem: - return prv_type_system; - case AppTypeUser: - return prv_type_user; - default: - furi_crash(); - } -} - App* furi_app_alloc(const AppManifest* _Nonnull manifest) { App app = { .manifest = manifest, - .thread = NULL, .ep_thread_args = NULL }; App* app_ptr = malloc(sizeof(App)); @@ -48,11 +17,6 @@ App* furi_app_alloc(const AppManifest* _Nonnull manifest) { void furi_app_free(App* app) { furi_assert(app); - if(app->thread) { - furi_thread_join(app->thread); - furi_thread_free(app->thread); - } - if (app->ep_thread_args) { free(app->ep_thread_args); app->ep_thread_args = NULL; @@ -60,43 +24,3 @@ void furi_app_free(App* app) { free(app); } - -FuriThread* furi_app_alloc_thread(App _Nonnull* app, const char* args) { - FURI_LOG_I( - TAG, - "Starting %s app \"%s\"", - furi_app_type_to_string(app->manifest->type), - app->manifest->name - ); - - // Free any previous app launching arguments - if (app->ep_thread_args) { - free(app->ep_thread_args); - } - - if (args) { - app->ep_thread_args = strdup(args); - } else { - app->ep_thread_args = NULL; - } - - FuriThread* thread = furi_thread_alloc_ex( - app->manifest->name, - app->manifest->stack_size, - app->manifest->entry_point, - app - ); - - if (app->manifest->type == AppTypeService) { - furi_thread_mark_as_service(thread); - } - - FuriString* app_name = furi_string_alloc(); - furi_thread_set_appid(thread, furi_string_get_cstr(app_name)); - furi_string_free(app_name); - - FuriThreadPriority priority = get_thread_priority(app->manifest->type); - furi_thread_set_priority(thread, priority); - - return thread; -} diff --git a/components/furi/src/app_i.h b/components/furi/src/app_i.h index 1090db51..984f1646 100644 --- a/components/furi/src/app_i.h +++ b/components/furi/src/app_i.h @@ -8,7 +8,6 @@ extern "C" { #endif typedef struct { - FuriThread* thread; const AppManifest* manifest; void* ep_thread_args; } App; diff --git a/components/furi/src/app_manifest.h b/components/furi/src/app_manifest.h index a400e9bd..7e30df79 100644 --- a/components/furi/src/app_manifest.h +++ b/components/furi/src/app_manifest.h @@ -6,6 +6,9 @@ extern "C" { #endif +// Forward declarations +typedef struct _lv_obj_t lv_obj_t; + typedef enum { AppTypeService, AppTypeSystem, @@ -16,14 +19,49 @@ typedef enum { AppStackSizeNormal = 2048 } AppStackSize; -typedef int32_t (*AppEntryPoint)(void _Nonnull* parameter); +typedef void (*AppOnStart)(void _Nonnull* parameter); +typedef void (*AppOnStop)(); +typedef void (*AppOnShow)(lv_obj_t* parent, void* context); typedef struct { + /** + * The identifier by which the app is launched by the system and other apps. + */ const char* _Nonnull id; + + /** + * The user-readable name of the app. Used in UI. + */ const char* _Nonnull name; + + /** + * Optional icon. + */ const char* _Nullable icon; + + /** + * App type affects launch behaviour. + */ const AppType type; - const AppEntryPoint _Nullable entry_point; + + /** + * Non-blocking method to call when app is started. + */ + const AppOnStart _Nullable on_start; + + /** + * Non-blocking method to call when app is stopped. + */ + const AppOnStop _Nullable on_stop; + + /** + * Non-blocking method to create the GUI + */ + const AppOnShow _Nullable on_show; + + /** + * Callstack size. If you get a stackoverflow, then consider increasing this value. + */ const AppStackSize stack_size; } AppManifest; diff --git a/components/furi/src/furi.c b/components/furi/src/furi.c index bb113de6..5dbbb2cf 100644 --- a/components/furi/src/furi.c +++ b/components/furi/src/furi.c @@ -5,14 +5,11 @@ #include #include -static bool scheduler_was_running = false; - void furi_init() { furi_assert(!furi_kernel_is_irq()); if (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) { vTaskSuspendAll(); - scheduler_was_running = true; } furi_record_init(); diff --git a/components/nanobake/CMakeLists.txt b/components/nanobake/CMakeLists.txt index 9be4bc94..ebf57239 100644 --- a/components/nanobake/CMakeLists.txt +++ b/components/nanobake/CMakeLists.txt @@ -5,5 +5,11 @@ idf_component_register( "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 + REQUIRES esp_lvgl_port esp_lcd esp_lcd_touch driver mlib cmsis_core furi nvs_flash spiffs fatfs ) + +set(ASSETS_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/assets") +spiffs_create_partition_image(assets ${ASSETS_SRC_DIR} FLASH_IN_PROJECT) + +set(CONFIG_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/config") +spiffs_create_partition_image(config ${CONFIG_SRC_DIR} FLASH_IN_PROJECT) diff --git a/components/nanobake/assets/placeholder.txt b/components/nanobake/assets/placeholder.txt new file mode 100644 index 00000000..e69de29b diff --git a/components/nanobake/config/placeholder.txt b/components/nanobake/config/placeholder.txt new file mode 100644 index 00000000..e69de29b diff --git a/components/nanobake/src/apps/services/desktop/desktop.c b/components/nanobake/src/apps/services/desktop/desktop.c index 64e3fe57..f1e5de04 100644 --- a/components/nanobake/src/apps/services/desktop/desktop.c +++ b/components/nanobake/src/apps/services/desktop/desktop.c @@ -1,10 +1,9 @@ #include "desktop.h" #include "furi_extra_defines.h" -static int32_t prv_desktop_main(void* param) { +static void desktop_start(void* param) { UNUSED(param); printf("desktop app init\n"); - return 0; } const AppManifest desktop_app = { @@ -12,6 +11,8 @@ const AppManifest desktop_app = { .name = "Desktop", .icon = NULL, .type = AppTypeService, - .entry_point = &prv_desktop_main, + .on_start = &desktop_start, + .on_stop = NULL, + .on_show = NULL, .stack_size = AppStackSizeNormal }; diff --git a/components/nanobake/src/apps/services/gui/gui.c b/components/nanobake/src/apps/services/gui/gui.c index 3a48aa91..ef95899d 100644 --- a/components/nanobake/src/apps/services/gui/gui.c +++ b/components/nanobake/src/apps/services/gui/gui.c @@ -2,13 +2,15 @@ #include "esp_lvgl_port.h" #include "furi_extra_defines.h" #include "gui_i.h" +#include "log.h" #include "record.h" #define TAG "gui" // Forward declarations from gui_draw.c -bool gui_redraw_fs(Gui* gui); -void gui_redraw(Gui* gui); +bool gui_redraw_fs(Gui*); +void gui_redraw(Gui*); +static int32_t gui_main(void*); ViewPort* gui_view_port_find_enabled(ViewPortArray_t array) { // Iterating backward @@ -17,6 +19,7 @@ ViewPort* gui_view_port_find_enabled(ViewPortArray_t array) { while (!ViewPortArray_end_p(it)) { ViewPort* view_port = *ViewPortArray_ref(it); if (view_port_is_enabled(view_port)) { + ViewPort* view_port = *ViewPortArray_ref(it); return view_port; } ViewPortArray_previous(it); @@ -45,9 +48,10 @@ size_t gui_active_view_port_count(Gui* gui, GuiLayer layer) { } void gui_update(Gui* gui) { - ESP_LOGI(TAG, "gui_update"); furi_assert(gui); - furi_thread_flags_set(gui->thread_id, GUI_THREAD_FLAG_DRAW); + + FuriThreadId thread_id = furi_thread_get_id(gui->thread); + furi_thread_flags_set(thread_id, GUI_THREAD_FLAG_DRAW); } void gui_lock(Gui* gui) { @@ -170,28 +174,20 @@ void gui_view_port_send_to_back(Gui* gui, ViewPort* view_port) { gui_update(gui); } -void gui_set_lockdown(Gui* gui, bool lockdown) { - furi_assert(gui); - - gui_lock(gui); - gui->lockdown = lockdown; - gui_unlock(gui); - - // Request redraw - gui_update(gui); -} - Gui* gui_alloc() { Gui* gui = malloc(sizeof(Gui)); - gui->thread_id = furi_thread_get_current_id(); + gui->thread = furi_thread_alloc_ex( + "gui", + 2048, + &gui_main, + NULL + ); 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++) { ViewPortArray_init(gui->layers[i]); } @@ -207,11 +203,22 @@ Gui* gui_alloc() { return gui; } -__attribute((__noreturn__)) int32_t prv_gui_main(void* parameter) { - UNUSED(parameter); - Gui* gui = gui_alloc(); +void gui_free(Gui* gui) { + furi_thread_free(gui->thread); - furi_record_create(RECORD_GUI, gui); + if (gui->mutex) { + furi_mutex_free(gui->mutex); + } + + for (size_t i = 0; i < GuiLayerMAX; i++) { + ViewPortArray_clear(gui->layers[i]); + } + + free(gui); +} + +static int32_t gui_main(void* parameter) { + UNUSED(parameter); while (1) { uint32_t flags = furi_thread_flags_wait( @@ -229,11 +236,45 @@ __attribute((__noreturn__)) int32_t prv_gui_main(void* parameter) { }*/ // Process and dispatch draw call if (flags & GUI_THREAD_FLAG_DRAW) { - // Clear flags that arrived on input step + FURI_LOG_D(TAG, "redraw requested"); furi_thread_flags_clear(GUI_THREAD_FLAG_DRAW); - gui_redraw(gui); + FURI_RECORD_TRANSACTION(RECORD_GUI, Gui*, gui, { + gui_redraw(gui); + }) + } + + if (flags & GUI_THREAD_FLAG_EXIT) { + furi_thread_flags_clear(GUI_THREAD_FLAG_EXIT); + break; } } + + return 0; +} + +static void gui_start(void* parameter) { + UNUSED(parameter); + + Gui* gui = gui_alloc(); + furi_record_create(RECORD_GUI, gui); + furi_thread_set_priority(gui->thread, FuriThreadPriorityHigh); + furi_thread_start(gui->thread); +} + +static void gui_stop() { + FURI_RECORD_TRANSACTION(RECORD_GUI, Gui*, gui, { + gui_lock(gui); + + FuriThreadId thread_id = furi_thread_get_id(gui->thread); + furi_thread_flags_set(thread_id, GUI_THREAD_FLAG_EXIT); + furi_thread_join(gui->thread); + + gui_unlock(gui); + + gui_free(gui); + }) + + furi_record_destroy(RECORD_GUI); } const AppManifest gui_app = { @@ -241,6 +282,8 @@ const AppManifest gui_app = { .name = "GUI", .icon = NULL, .type = AppTypeService, - .entry_point = &prv_gui_main, + .on_start = &gui_start, + .on_stop = &gui_stop, + .on_show = NULL, .stack_size = AppStackSizeNormal }; diff --git a/components/nanobake/src/apps/services/gui/gui.h b/components/nanobake/src/apps/services/gui/gui.h index 62873a34..b2fa87d1 100644 --- a/components/nanobake/src/apps/services/gui/gui.h +++ b/components/nanobake/src/apps/services/gui/gui.h @@ -71,16 +71,6 @@ void gui_view_port_send_to_front(Gui* gui, ViewPort* view_port); */ void gui_view_port_send_to_back(Gui* gui, ViewPort* view_port); -/** Set lockdown mode - * - * When lockdown mode is enabled, only GuiLayerDesktop is shown. - * This feature prevents services from showing sensitive information when flipper is locked. - * - * @param gui Gui instance - * @param lockdown bool, true if enabled - */ -void gui_set_lockdown(Gui* gui, bool lockdown); - #ifdef __cplusplus } #endif diff --git a/components/nanobake/src/apps/services/gui/gui_draw.c b/components/nanobake/src/apps/services/gui/gui_draw.c index 5b995365..40cbfca0 100644 --- a/components/nanobake/src/apps/services/gui/gui_draw.c +++ b/components/nanobake/src/apps/services/gui/gui_draw.c @@ -1,5 +1,6 @@ #include "check.h" #include "gui_i.h" +#include "esp_lvgl_port.h" static void gui_redraw_status_bar(Gui* gui, bool need_attention) { /* @@ -188,24 +189,16 @@ void gui_redraw(Gui* gui) { furi_assert(gui); gui_lock(gui); + furi_check(lvgl_port_lock(100)); lv_obj_clean(gui->lvgl_parent); + lvgl_port_unlock(); - if (gui->lockdown) { - ESP_LOGI("gui", "gui_redraw with lockdown"); - gui_redraw_desktop(gui); - bool need_attention = - (gui_view_port_find_enabled(gui->layers[GuiLayerWindow]) != 0 || - gui_view_port_find_enabled(gui->layers[GuiLayerFullscreen]) != 0); - gui_redraw_status_bar(gui, need_attention); - } else { - gui_redraw_desktop(gui); - ESP_LOGI("gui", "gui_redraw"); - if (!gui_redraw_fs(gui)) { - if (!gui_redraw_window(gui)) { - gui_redraw_desktop(gui); - } - gui_redraw_status_bar(gui, false); + gui_redraw_desktop(gui); + if (!gui_redraw_fs(gui)) { + if (!gui_redraw_window(gui)) { + gui_redraw_desktop(gui); } + gui_redraw_status_bar(gui, false); } gui_unlock(gui); diff --git a/components/nanobake/src/apps/services/gui/gui_i.h b/components/nanobake/src/apps/services/gui/gui_i.h index 938b3b80..63ab36f9 100644 --- a/components/nanobake/src/apps/services/gui/gui_i.h +++ b/components/nanobake/src/apps/services/gui/gui_i.h @@ -34,7 +34,8 @@ #define GUI_THREAD_FLAG_DRAW (1 << 0) #define GUI_THREAD_FLAG_INPUT (1 << 1) -#define GUI_THREAD_FLAG_ALL (GUI_THREAD_FLAG_DRAW | GUI_THREAD_FLAG_INPUT) +#define GUI_THREAD_FLAG_EXIT (1 << 2) +#define GUI_THREAD_FLAG_ALL (GUI_THREAD_FLAG_DRAW | GUI_THREAD_FLAG_INPUT | GUI_THREAD_FLAG_EXIT) ARRAY_DEF(ViewPortArray, ViewPort*, M_PTR_OPLIST); @@ -46,11 +47,10 @@ typedef struct { /** Gui structure */ struct Gui { // Thread and lock - FuriThreadId thread_id; + FuriThread* thread; FuriMutex* mutex; // Layers and Canvas - bool lockdown; ViewPortArray_t layers[GuiLayerMAX]; lv_obj_t* lvgl_parent; diff --git a/components/nanobake/src/apps/services/gui/view_port.c b/components/nanobake/src/apps/services/gui/view_port.c index bf516bc4..b1951aaa 100644 --- a/components/nanobake/src/apps/services/gui/view_port.c +++ b/components/nanobake/src/apps/services/gui/view_port.c @@ -89,11 +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)); + furi_check(lvgl_port_lock(100)); // TODO: fail safely lv_obj_clean(parent); - lvgl_port_unlock(); - view_port->draw_callback(parent, view_port->draw_callback_context); + lvgl_port_unlock(); } furi_mutex_release(view_port->mutex); diff --git a/components/nanobake/src/apps/services/loader/loader.c b/components/nanobake/src/apps/services/loader/loader.c index 5027ebe8..d08b53bf 100644 --- a/components/nanobake/src/apps/services/loader/loader.c +++ b/components/nanobake/src/apps/services/loader/loader.c @@ -5,11 +5,19 @@ #include "loader_i.h" #include #include "esp_heap_caps.h" +#include "apps/services/gui/gui.h" #define TAG "Loader" -#define LOADER_MAGIC_THREAD_VALUE 0xDEADBEEF -LoaderStatus loader_start(Loader* loader, const char* id, const char* args, FuriString* error_message) { +static int32_t loader_main(void* p); +static LoaderStatus loader_do_start_by_id( + Loader* loader, + const char* id, + const char* args, + FuriString* error_message +); + +LoaderStatus loader_start_app(Loader* loader, const char* id, const char* args, FuriString* error_message) { LoaderMessage message; LoaderMessageLoaderStatusResult result; @@ -24,32 +32,23 @@ LoaderStatus loader_start(Loader* loader, const char* id, const char* args, Furi return result.value; } -bool loader_lock(Loader* loader) { +void loader_start_app_nonblocking(Loader* loader, const char* id, const char* args) { 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; -} + LoaderMessageLoaderStatusResult result; -void loader_unlock(Loader* loader) { - LoaderMessage message; - message.type = LoaderMessageTypeUnlock; + message.type = LoaderMessageTypeStartByName; + message.start.id = id; + message.start.args = args; + message.start.error_message = NULL; + message.api_lock = NULL; + message.status_value = &result; furi_message_queue_put(loader->queue, &message, FuriWaitForever); } -bool loader_is_locked(Loader* loader) { +void loader_stop_app(Loader* loader) { LoaderMessage message; - LoaderMessageBoolResult result; - message.type = LoaderMessageTypeIsLocked; - message.api_lock = api_lock_alloc_locked(); - message.bool_value = &result; + message.type = LoaderMessageTypeAppStop; 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) { @@ -60,43 +59,28 @@ FuriPubSub* loader_get_pubsub(Loader* loader) { 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->thread = furi_thread_alloc_ex( + "loader", + 2048, + &loader_main, + NULL + ); 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_free(Loader* loader) { + furi_pubsub_free(loader->pubsub); + furi_message_queue_free(loader->queue); + furi_thread_free(loader->thread); + free(loader); } static void loader_log_status_error( @@ -115,7 +99,7 @@ static void loader_log_status_error( static LoaderStatus loader_make_status_error( LoaderStatus status, - FuriString* error_message, + FuriString* _Nullable error_message, const char* format, ... ) { @@ -134,46 +118,41 @@ static LoaderStatus loader_make_success_status(FuriString* error_message) { return LoaderStatusOk; } -static void loader_start_app( +static void loader_start_app_with_manifest( 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; + loader->app_data.args = (void*)args; - FuriThread* thread = furi_app_alloc_thread(loader->app_data.app, args); - loader->app_data.app->thread = thread; - loader->app_data.thread = thread; + if (manifest->on_start != NULL) { + manifest->on_start((void*)args); + } - loader_start_app_thread(loader); + 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); + + FURI_RECORD_TRANSACTION(RECORD_GUI, Gui*, gui, { + gui_add_view_port(gui, view_port, GuiLayerFullscreen); // TODO: layer type + }) + } else { + loader->app_data.view_port = NULL; + } } // 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 + FuriString* _Nullable 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 - ); - } + FURI_LOG_I(TAG, "loader start by id %s", id); const AppManifest* manifest = app_manifest_registry_find_by_id(id); if (manifest == NULL) { @@ -185,54 +164,44 @@ static LoaderStatus loader_do_start_by_id( ); } - loader_start_app(loader, manifest, args); + loader_start_app_with_manifest(loader, manifest, args); return loader_make_success_status(error_message); } -static bool loader_do_lock(Loader* loader) { - if (loader->app_data.thread) { - return false; +static void loader_do_stop_app(Loader* loader) { + App* app = loader->app_data.app; + if (app == NULL) { + FURI_LOG_W(TAG, "Stop app: no app running"); + return; } - loader->app_data.thread = (FuriThread*)LOADER_MAGIC_THREAD_VALUE; - return true; -} + FURI_LOG_I(TAG, "Stopping %s", app->manifest->id); -static void loader_do_unlock(Loader* loader) { - furi_check(loader->app_data.thread == (FuriThread*)LOADER_MAGIC_THREAD_VALUE); - loader->app_data.thread = NULL; -} + ViewPort* view_port = loader->app_data.view_port; + if (view_port) { + FURI_RECORD_TRANSACTION(RECORD_GUI, Gui*, gui, { + gui_remove_view_port(gui, view_port); + }) + view_port_free(view_port); + loader->app_data.view_port = 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 (app->manifest->on_stop) { + app->manifest->on_stop(); + } 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_app_free(loader->app_data.app); + loader->app_data.app = NULL; FURI_LOG_I( TAG, "Application stopped. Free heap: %zu", - heap_caps_get_free_size(MALLOC_CAP_DEFAULT) + heap_caps_get_free_size(MALLOC_CAP_INTERNAL) ); LoaderEvent event; @@ -242,42 +211,76 @@ static void loader_do_app_closed(Loader* loader) { // app -_Noreturn int32_t loader_main(void* p) { +static int32_t loader_main(void* p) { UNUSED(p); - Loader* loader = loader_alloc(); - furi_record_create(RECORD_LOADER, loader); + FuriMessageQueue* queue = NULL; + FURI_RECORD_TRANSACTION(RECORD_LOADER, Loader*, loader, { + queue = loader->queue; + }) + furi_check(queue != NULL); LoaderMessage message; - while (true) { - if (furi_message_queue_get(loader->queue, &message, FuriWaitForever) == FuriStatusOk) { + bool exit_requested = false; + while (!exit_requested) { + if (furi_message_queue_get(queue, &message, FuriWaitForever) == FuriStatusOk) { + FURI_LOG_I(TAG, "processing message of type %d", message.type); 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); + FURI_RECORD_TRANSACTION(RECORD_LOADER, Loader*, loader, { + if (loader->app_data.app) { + loader_do_stop_app(loader); + } + message.status_value->value = loader_do_start_by_id( + loader, + message.start.id, + message.start.args, + message.start.error_message + ); + if (message.api_lock) { + api_lock_unlock(message.api_lock); + } + }) break; - case LoaderMessageTypeIsLocked: - message.bool_value->value = loader_do_is_locked(loader); - api_lock_unlock(message.api_lock); + case LoaderMessageTypeAppStop: + FURI_RECORD_TRANSACTION(RECORD_LOADER, Loader*, loader, { + loader_do_stop_app(loader); + }) 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); + case LoaderMessageTypeExit: + exit_requested = true; break; } } } + + return 0; +} + +static void loader_start(void* p) { + Loader* loader = loader_alloc(); + furi_record_create(RECORD_LOADER, loader); + furi_thread_set_priority(loader->thread, FuriThreadPriorityHigh); + furi_thread_start(loader->thread); +} + +static void loader_stop() { + LoaderMessage message = { + .api_lock = NULL, + .type = LoaderMessageTypeExit + }; + + FURI_RECORD_TRANSACTION(RECORD_LOADER, Loader*, loader, { + furi_message_queue_put(loader->queue, &message, FuriWaitForever); + + furi_thread_join(loader->thread); + furi_thread_free(loader->thread); + loader->thread = NULL; + + loader_free(loader); + }) + + furi_record_destroy(RECORD_LOADER); } const AppManifest loader_app = { @@ -285,6 +288,8 @@ const AppManifest loader_app = { .name = "Loader", .icon = NULL, .type = AppTypeService, - .entry_point = &loader_main, + .on_start = &loader_start, + .on_stop = &loader_stop, + .on_show = NULL, .stack_size = AppStackSizeNormal }; diff --git a/components/nanobake/src/apps/services/loader/loader.h b/components/nanobake/src/apps/services/loader/loader.h index 3df31ae8..069a629e 100644 --- a/components/nanobake/src/apps/services/loader/loader.h +++ b/components/nanobake/src/apps/services/loader/loader.h @@ -28,14 +28,24 @@ typedef struct { } LoaderEvent; /** - * @brief Start application - * @param[in] instance loader instance + * @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(Loader* instance, const char* id, const char* args, FuriString* error_message); +LoaderStatus loader_start_app(Loader* loader, const char* id, const char* args, 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(Loader* loader, const char* id, const char* args); + +void loader_stop_app(Loader* loader); /** * @brief Start application with GUI error message @@ -46,26 +56,6 @@ LoaderStatus loader_start(Loader* instance, const char* id, const char* args, Fu */ //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 diff --git a/components/nanobake/src/apps/services/loader/loader_i.h b/components/nanobake/src/apps/services/loader/loader_i.h index e51e5ff1..98403127 100644 --- a/components/nanobake/src/apps/services/loader/loader_i.h +++ b/components/nanobake/src/apps/services/loader/loader_i.h @@ -5,14 +5,16 @@ #include "message_queue.h" #include "pubsub.h" #include "thread.h" +#include "apps/services/gui/view_port.h" typedef struct { char* args; - FuriThread* thread; App* app; + ViewPort* view_port; } LoaderAppData; struct Loader { + FuriThread* thread; FuriPubSub* pubsub; FuriMessageQueue* queue; LoaderAppData app_data; @@ -20,10 +22,8 @@ struct Loader { typedef enum { LoaderMessageTypeStartByName, - LoaderMessageTypeAppClosed, - LoaderMessageTypeLock, - LoaderMessageTypeUnlock, - LoaderMessageTypeIsLocked, + LoaderMessageTypeAppStop, + LoaderMessageTypeExit, } LoaderMessageType; typedef struct { @@ -41,6 +41,8 @@ typedef struct { } LoaderMessageBoolResult; typedef struct { + // This lock blocks anyone from starting an app as long + // as an app is already running via loader_start() FuriApiLock api_lock; LoaderMessageType type; diff --git a/components/nanobake/src/apps/system/system_info/system_info.c b/components/nanobake/src/apps/system/system_info/system_info.c index ab2f7bd9..f4988d82 100644 --- a/components/nanobake/src/apps/system/system_info/system_info.c +++ b/components/nanobake/src/apps/system/system_info/system_info.c @@ -1,17 +1,50 @@ #include "system_info.h" #include "furi_extra_defines.h" #include "thread.h" +#include "lvgl.h" -static int32_t system_info_entry_point(void* param) { +static void system_info_main(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) + heap_caps_get_free_size(MALLOC_CAP_INTERNAL), + heap_caps_get_total_size(MALLOC_CAP_INTERNAL) ); - return 0; + printf( + "SPI memory available: %d / %d\n", + heap_caps_get_free_size(MALLOC_CAP_SPIRAM), + heap_caps_get_total_size(MALLOC_CAP_SPIRAM) + ); +} + +static void app_show(lv_obj_t* parent, void* context) { + UNUSED(context); + + lv_obj_t* heap_info = lv_label_create(parent); + lv_label_set_recolor(heap_info, true); + lv_obj_set_width(heap_info, 200); + lv_obj_set_style_text_align(heap_info, LV_TEXT_ALIGN_CENTER, 0); + lv_label_set_text_fmt( + heap_info, + "Heap available:\n%d / %d", + heap_caps_get_free_size(MALLOC_CAP_INTERNAL), + heap_caps_get_total_size(MALLOC_CAP_INTERNAL) + ); + lv_obj_align(heap_info, LV_ALIGN_CENTER, 0, -20); + + lv_obj_t* spi_info = lv_label_create(parent); + lv_label_set_recolor(spi_info, true); + lv_obj_set_width(spi_info, 200); + lv_obj_set_style_text_align(spi_info, LV_TEXT_ALIGN_CENTER, 0); + lv_label_set_text_fmt( + spi_info, + "SPI available\n%d / %d", + heap_caps_get_free_size(MALLOC_CAP_SPIRAM), + heap_caps_get_total_size(MALLOC_CAP_SPIRAM) + ); + lv_obj_align(spi_info, LV_ALIGN_CENTER, 0, 20); } AppManifest system_info_app = { @@ -19,6 +52,8 @@ AppManifest system_info_app = { .name = "System Info", .icon = NULL, .type = AppTypeSystem, - .entry_point = &system_info_entry_point, + .on_start = NULL, + .on_stop = NULL, + .on_show = app_show, .stack_size = AppStackSizeNormal }; diff --git a/components/nanobake/src/nanobake.c b/components/nanobake/src/nanobake.c index d5f6dd59..2d381cca 100644 --- a/components/nanobake/src/nanobake.c +++ b/components/nanobake/src/nanobake.c @@ -4,6 +4,7 @@ #include "devices_i.h" #include "furi.h" #include "graphics_i.h" +#include "partitions.h" #define TAG "nanobake" @@ -18,15 +19,7 @@ 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( - manifest->name, - manifest->stack_size, - manifest->entry_point, - NULL - ); - furi_thread_mark_as_service(thread); - furi_thread_set_appid(thread, manifest->id); - furi_thread_start(thread); + manifest->on_start(NULL); } static void register_apps(Config* _Nonnull config) { @@ -51,6 +44,8 @@ static void start_services() { __attribute__((unused)) extern void nanobake_start(Config* _Nonnull config) { furi_init(); + nb_partitions_init(); + Devices hardware = nb_devices_create(config); /*NbLvgl lvgl =*/nb_graphics_init(&hardware); diff --git a/components/nanobake/src/partitions.c b/components/nanobake/src/partitions.c new file mode 100644 index 00000000..5eb28127 --- /dev/null +++ b/components/nanobake/src/partitions.c @@ -0,0 +1,66 @@ +#include "partitions.h" +#include "esp_spiffs.h" +#include "log.h" +#include "nvs_flash.h" + +static const char* TAG = "filesystem"; + +static esp_err_t nvs_flash_init_safely() { + esp_err_t result = nvs_flash_init(); + if (result == ESP_ERR_NVS_NO_FREE_PAGES || result == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + result = nvs_flash_init(); + } + return result; +} + +static esp_err_t spiffs_init(esp_vfs_spiffs_conf_t* conf) { + esp_err_t ret = esp_vfs_spiffs_register(conf); + if (ret != ESP_OK) { + if (ret == ESP_FAIL) { + FURI_LOG_E(TAG, "Failed to mount or format filesystem %s", conf->base_path); + } else if (ret == ESP_ERR_NOT_FOUND) { + FURI_LOG_E(TAG, "Failed to find SPIFFS partition %s", conf->base_path); + } else { + FURI_LOG_E(TAG, "Failed to initialize SPIFFS %s (%s)", conf->base_path, esp_err_to_name(ret)); + } + return ESP_FAIL; + } + + size_t total = -1, used = 0; + ret = esp_spiffs_info(NULL, &total, &used); + if (ret != ESP_OK) { + FURI_LOG_E(TAG, "Failed to get SPIFFS partition information for %s (%s)", conf->base_path, esp_err_to_name(ret)); + } else { + FURI_LOG_I(TAG, "Partition size for %s: total: %d, used: %d", conf->base_path, total, used); + } + return ESP_OK; +} + +esp_err_t nb_partitions_init() { + ESP_ERROR_CHECK(nvs_flash_init_safely()); + + esp_vfs_spiffs_conf_t assets_spiffs = { + .base_path = MOUNT_POINT_ASSETS, + .partition_label = NULL, + .max_files = 4, + .format_if_mount_failed = false + }; + + if (spiffs_init(&assets_spiffs) != ESP_OK) { + return ESP_FAIL; + } + + esp_vfs_spiffs_conf_t config_spiffs = { + .base_path = MOUNT_POINT_CONFIG, + .partition_label = "config", + .max_files = 2, + .format_if_mount_failed = false + }; + + if (spiffs_init(&config_spiffs) != ESP_OK) { + return ESP_FAIL; + } + + return ESP_OK; +} diff --git a/components/nanobake/src/partitions.h b/components/nanobake/src/partitions.h new file mode 100644 index 00000000..cf9c6fd9 --- /dev/null +++ b/components/nanobake/src/partitions.h @@ -0,0 +1,8 @@ +#pragma once + +#include "esp_err.h" + +#define MOUNT_POINT_ASSETS "/assets" +#define MOUNT_POINT_CONFIG "/config" + +esp_err_t nb_partitions_init(); diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 39f3bf83..a9d59e36 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,4 +1,3 @@ - idf_component_register( SRC_DIRS "src" "src/hello_world" diff --git a/main/src/hello_world/hello_world.c b/main/src/hello_world/hello_world.c index 4cd366a6..65fb82b7 100644 --- a/main/src/hello_world/hello_world.c +++ b/main/src/hello_world/hello_world.c @@ -1,21 +1,16 @@ #include "hello_world.h" #include "furi.h" #include "apps/services/gui/gui.h" -#include "esp_lvgl_port.h" - -static const char* TAG = "app_hello_world"; - -ViewPort* view_port = NULL; -FuriSemaphore* quit_lock = NULL; +#include "apps/services/loader/loader.h" static void on_button_click(lv_event_t _Nonnull* event) { - ESP_LOGI(TAG, "button clicked"); - furi_semaphore_give(quit_lock); + FURI_RECORD_TRANSACTION(RECORD_LOADER, Loader*, loader, { + loader_start_app_nonblocking(loader, "systeminfo", NULL); + }) } -// Main entry point for LVGL widget creation -static void app_lvgl(lv_obj_t* parent, void* context) { - lvgl_port_lock(0); +static void app_show(lv_obj_t* parent, void* context) { + UNUSED(context); lv_obj_t* label = lv_label_create(parent); lv_label_set_recolor(label, true); @@ -26,41 +21,9 @@ static void app_lvgl(lv_obj_t* parent, void* context) { lv_obj_t* btn = lv_btn_create(parent); label = lv_label_create(btn); - lv_label_set_text_static(label, "Exit"); + lv_label_set_text_static(label, "System Info"); lv_obj_align(btn, LV_ALIGN_CENTER, 0, 30); lv_obj_add_event_cb(btn, on_button_click, LV_EVENT_CLICKED, NULL); - - lvgl_port_unlock(); -} - -// Main entry point for the app -static int32_t app_main(void* param) { - UNUSED(param); - - // Configure view port to enable UI with LVGL - view_port = view_port_alloc(); - view_port_draw_callback_set(view_port, &app_lvgl, view_port); - - // The transaction automatically calls furi_record_open() and furi_record_close() - FURI_RECORD_TRANSACTION(RECORD_GUI, Gui*, gui, { - gui_add_view_port(gui, view_port, GuiLayerFullscreen); - }) - - // Wait for the button click to release the mutex (lock) - quit_lock = furi_semaphore_alloc(1, 0); - while (!furi_semaphore_take(quit_lock, UINT32_MAX)) { - // Do nothing - } - furi_semaphore_free(quit_lock); - quit_lock = NULL; - - FURI_RECORD_TRANSACTION(RECORD_GUI, Gui*, gui, { - gui_remove_view_port(gui, view_port); - view_port_free(view_port); - view_port = NULL; - }); - - return 0; } const AppManifest hello_world_app = { @@ -68,6 +31,8 @@ const AppManifest hello_world_app = { .name = "Hello World", .icon = NULL, .type = AppTypeUser, - .entry_point = &app_main, + .on_start = NULL, + .on_stop = NULL, + .on_show = &app_show, .stack_size = AppStackSizeNormal, }; diff --git a/main/src/main.c b/main/src/main.c index 6a5bdaf2..80960deb 100644 --- a/main/src/main.c +++ b/main/src/main.c @@ -22,8 +22,9 @@ __attribute__((unused)) void app_main(void) { FURI_RECORD_TRANSACTION(RECORD_LOADER, Loader*, loader, { FuriString* error_message = furi_string_alloc(); - if (loader_start(loader, hello_world_app.id, NULL, error_message) != LoaderStatusOk) { + if (loader_start_app(loader, hello_world_app.id, NULL, error_message) != LoaderStatusOk) { FURI_LOG_E(hello_world_app.id, "%s\r\n", furi_string_get_cstr(error_message)); } + furi_string_free(error_message); }); } diff --git a/partitions.csv b/partitions.csv new file mode 100644 index 00000000..2292c96e --- /dev/null +++ b/partitions.csv @@ -0,0 +1,7 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 1M, +assets, data, spiffs, , 128k, +config, data, spiffs, , 64k, diff --git a/sdkconfig.defaults b/sdkconfig.defaults index 6257bd63..0bac7a49 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -2,3 +2,6 @@ CONFIG_IDF_TARGET="esp32" CONFIG_LV_COLOR_16_SWAP=y CONFIG_LV_USE_USER_DATA=y CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=2 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"