added data partitions and app loading logic
This commit is contained in:
parent
b9427d4eba
commit
c0824af966
@ -46,6 +46,7 @@ Predefined configurations are available for:
|
|||||||
- SSD1963
|
- SSD1963
|
||||||
- ST7262E43
|
- ST7262E43
|
||||||
- ST7789
|
- ST7789
|
||||||
|
- ST7796
|
||||||
|
|
||||||
**Touch** (see [Espressif Registry](https://components.espressif.com/components?q=esp_lcd_touch)):
|
**Touch** (see [Espressif Registry](https://components.espressif.com/components?q=esp_lcd_touch)):
|
||||||
- CST8xx
|
- CST8xx
|
||||||
|
|||||||
@ -5,40 +5,9 @@
|
|||||||
|
|
||||||
#define TAG "app"
|
#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* furi_app_alloc(const AppManifest* _Nonnull manifest) {
|
||||||
App app = {
|
App app = {
|
||||||
.manifest = manifest,
|
.manifest = manifest,
|
||||||
.thread = NULL,
|
|
||||||
.ep_thread_args = NULL
|
.ep_thread_args = NULL
|
||||||
};
|
};
|
||||||
App* app_ptr = malloc(sizeof(App));
|
App* app_ptr = malloc(sizeof(App));
|
||||||
@ -48,11 +17,6 @@ App* furi_app_alloc(const AppManifest* _Nonnull manifest) {
|
|||||||
void furi_app_free(App* app) {
|
void furi_app_free(App* app) {
|
||||||
furi_assert(app);
|
furi_assert(app);
|
||||||
|
|
||||||
if(app->thread) {
|
|
||||||
furi_thread_join(app->thread);
|
|
||||||
furi_thread_free(app->thread);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (app->ep_thread_args) {
|
if (app->ep_thread_args) {
|
||||||
free(app->ep_thread_args);
|
free(app->ep_thread_args);
|
||||||
app->ep_thread_args = NULL;
|
app->ep_thread_args = NULL;
|
||||||
@ -60,43 +24,3 @@ void furi_app_free(App* app) {
|
|||||||
|
|
||||||
free(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;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -8,7 +8,6 @@ extern "C" {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
FuriThread* thread;
|
|
||||||
const AppManifest* manifest;
|
const AppManifest* manifest;
|
||||||
void* ep_thread_args;
|
void* ep_thread_args;
|
||||||
} App;
|
} App;
|
||||||
|
|||||||
@ -6,6 +6,9 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Forward declarations
|
||||||
|
typedef struct _lv_obj_t lv_obj_t;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
AppTypeService,
|
AppTypeService,
|
||||||
AppTypeSystem,
|
AppTypeSystem,
|
||||||
@ -16,14 +19,49 @@ typedef enum {
|
|||||||
AppStackSizeNormal = 2048
|
AppStackSizeNormal = 2048
|
||||||
} AppStackSize;
|
} 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 {
|
typedef struct {
|
||||||
|
/**
|
||||||
|
* The identifier by which the app is launched by the system and other apps.
|
||||||
|
*/
|
||||||
const char* _Nonnull id;
|
const char* _Nonnull id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The user-readable name of the app. Used in UI.
|
||||||
|
*/
|
||||||
const char* _Nonnull name;
|
const char* _Nonnull name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional icon.
|
||||||
|
*/
|
||||||
const char* _Nullable icon;
|
const char* _Nullable icon;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* App type affects launch behaviour.
|
||||||
|
*/
|
||||||
const AppType type;
|
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;
|
const AppStackSize stack_size;
|
||||||
} AppManifest;
|
} AppManifest;
|
||||||
|
|
||||||
|
|||||||
@ -5,14 +5,11 @@
|
|||||||
#include <freertos/FreeRTOS.h>
|
#include <freertos/FreeRTOS.h>
|
||||||
#include <freertos/queue.h>
|
#include <freertos/queue.h>
|
||||||
|
|
||||||
static bool scheduler_was_running = false;
|
|
||||||
|
|
||||||
void furi_init() {
|
void furi_init() {
|
||||||
furi_assert(!furi_kernel_is_irq());
|
furi_assert(!furi_kernel_is_irq());
|
||||||
|
|
||||||
if (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) {
|
if (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) {
|
||||||
vTaskSuspendAll();
|
vTaskSuspendAll();
|
||||||
scheduler_was_running = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
furi_record_init();
|
furi_record_init();
|
||||||
|
|||||||
@ -5,5 +5,11 @@ idf_component_register(
|
|||||||
"src/apps/services/loader"
|
"src/apps/services/loader"
|
||||||
"src/apps/services/gui"
|
"src/apps/services/gui"
|
||||||
INCLUDE_DIRS "src"
|
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)
|
||||||
|
|||||||
0
components/nanobake/assets/placeholder.txt
Normal file
0
components/nanobake/assets/placeholder.txt
Normal file
0
components/nanobake/config/placeholder.txt
Normal file
0
components/nanobake/config/placeholder.txt
Normal file
@ -1,10 +1,9 @@
|
|||||||
#include "desktop.h"
|
#include "desktop.h"
|
||||||
#include "furi_extra_defines.h"
|
#include "furi_extra_defines.h"
|
||||||
|
|
||||||
static int32_t prv_desktop_main(void* param) {
|
static void desktop_start(void* param) {
|
||||||
UNUSED(param);
|
UNUSED(param);
|
||||||
printf("desktop app init\n");
|
printf("desktop app init\n");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const AppManifest desktop_app = {
|
const AppManifest desktop_app = {
|
||||||
@ -12,6 +11,8 @@ const AppManifest desktop_app = {
|
|||||||
.name = "Desktop",
|
.name = "Desktop",
|
||||||
.icon = NULL,
|
.icon = NULL,
|
||||||
.type = AppTypeService,
|
.type = AppTypeService,
|
||||||
.entry_point = &prv_desktop_main,
|
.on_start = &desktop_start,
|
||||||
|
.on_stop = NULL,
|
||||||
|
.on_show = NULL,
|
||||||
.stack_size = AppStackSizeNormal
|
.stack_size = AppStackSizeNormal
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,13 +2,15 @@
|
|||||||
#include "esp_lvgl_port.h"
|
#include "esp_lvgl_port.h"
|
||||||
#include "furi_extra_defines.h"
|
#include "furi_extra_defines.h"
|
||||||
#include "gui_i.h"
|
#include "gui_i.h"
|
||||||
|
#include "log.h"
|
||||||
#include "record.h"
|
#include "record.h"
|
||||||
|
|
||||||
#define TAG "gui"
|
#define TAG "gui"
|
||||||
|
|
||||||
// Forward declarations from gui_draw.c
|
// Forward declarations from gui_draw.c
|
||||||
bool gui_redraw_fs(Gui* gui);
|
bool gui_redraw_fs(Gui*);
|
||||||
void gui_redraw(Gui* gui);
|
void gui_redraw(Gui*);
|
||||||
|
static int32_t gui_main(void*);
|
||||||
|
|
||||||
ViewPort* gui_view_port_find_enabled(ViewPortArray_t array) {
|
ViewPort* gui_view_port_find_enabled(ViewPortArray_t array) {
|
||||||
// Iterating backward
|
// Iterating backward
|
||||||
@ -17,6 +19,7 @@ ViewPort* gui_view_port_find_enabled(ViewPortArray_t array) {
|
|||||||
while (!ViewPortArray_end_p(it)) {
|
while (!ViewPortArray_end_p(it)) {
|
||||||
ViewPort* view_port = *ViewPortArray_ref(it);
|
ViewPort* view_port = *ViewPortArray_ref(it);
|
||||||
if (view_port_is_enabled(view_port)) {
|
if (view_port_is_enabled(view_port)) {
|
||||||
|
ViewPort* view_port = *ViewPortArray_ref(it);
|
||||||
return view_port;
|
return view_port;
|
||||||
}
|
}
|
||||||
ViewPortArray_previous(it);
|
ViewPortArray_previous(it);
|
||||||
@ -45,9 +48,10 @@ size_t gui_active_view_port_count(Gui* gui, GuiLayer layer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void gui_update(Gui* gui) {
|
void gui_update(Gui* gui) {
|
||||||
ESP_LOGI(TAG, "gui_update");
|
|
||||||
furi_assert(gui);
|
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) {
|
void gui_lock(Gui* gui) {
|
||||||
@ -170,28 +174,20 @@ void gui_view_port_send_to_back(Gui* gui, ViewPort* view_port) {
|
|||||||
gui_update(gui);
|
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_alloc() {
|
||||||
Gui* gui = malloc(sizeof(Gui));
|
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);
|
gui->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||||
|
|
||||||
furi_check(lvgl_port_lock(100));
|
furi_check(lvgl_port_lock(100));
|
||||||
gui->lvgl_parent = lv_scr_act();
|
gui->lvgl_parent = lv_scr_act();
|
||||||
lvgl_port_unlock();
|
lvgl_port_unlock();
|
||||||
|
|
||||||
gui->lockdown = false;
|
|
||||||
furi_check(gui->mutex);
|
|
||||||
for (size_t i = 0; i < GuiLayerMAX; i++) {
|
for (size_t i = 0; i < GuiLayerMAX; i++) {
|
||||||
ViewPortArray_init(gui->layers[i]);
|
ViewPortArray_init(gui->layers[i]);
|
||||||
}
|
}
|
||||||
@ -207,11 +203,22 @@ Gui* gui_alloc() {
|
|||||||
return gui;
|
return gui;
|
||||||
}
|
}
|
||||||
|
|
||||||
__attribute((__noreturn__)) int32_t prv_gui_main(void* parameter) {
|
void gui_free(Gui* gui) {
|
||||||
UNUSED(parameter);
|
furi_thread_free(gui->thread);
|
||||||
Gui* gui = gui_alloc();
|
|
||||||
|
|
||||||
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) {
|
while (1) {
|
||||||
uint32_t flags = furi_thread_flags_wait(
|
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
|
// Process and dispatch draw call
|
||||||
if (flags & GUI_THREAD_FLAG_DRAW) {
|
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);
|
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 = {
|
const AppManifest gui_app = {
|
||||||
@ -241,6 +282,8 @@ const AppManifest gui_app = {
|
|||||||
.name = "GUI",
|
.name = "GUI",
|
||||||
.icon = NULL,
|
.icon = NULL,
|
||||||
.type = AppTypeService,
|
.type = AppTypeService,
|
||||||
.entry_point = &prv_gui_main,
|
.on_start = &gui_start,
|
||||||
|
.on_stop = &gui_stop,
|
||||||
|
.on_show = NULL,
|
||||||
.stack_size = AppStackSizeNormal
|
.stack_size = AppStackSizeNormal
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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);
|
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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
#include "check.h"
|
#include "check.h"
|
||||||
#include "gui_i.h"
|
#include "gui_i.h"
|
||||||
|
#include "esp_lvgl_port.h"
|
||||||
|
|
||||||
static void gui_redraw_status_bar(Gui* gui, bool need_attention) {
|
static void gui_redraw_status_bar(Gui* gui, bool need_attention) {
|
||||||
/*
|
/*
|
||||||
@ -188,24 +189,16 @@ void gui_redraw(Gui* gui) {
|
|||||||
furi_assert(gui);
|
furi_assert(gui);
|
||||||
gui_lock(gui);
|
gui_lock(gui);
|
||||||
|
|
||||||
|
furi_check(lvgl_port_lock(100));
|
||||||
lv_obj_clean(gui->lvgl_parent);
|
lv_obj_clean(gui->lvgl_parent);
|
||||||
|
lvgl_port_unlock();
|
||||||
|
|
||||||
if (gui->lockdown) {
|
gui_redraw_desktop(gui);
|
||||||
ESP_LOGI("gui", "gui_redraw with lockdown");
|
if (!gui_redraw_fs(gui)) {
|
||||||
gui_redraw_desktop(gui);
|
if (!gui_redraw_window(gui)) {
|
||||||
bool need_attention =
|
gui_redraw_desktop(gui);
|
||||||
(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_status_bar(gui, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
gui_unlock(gui);
|
gui_unlock(gui);
|
||||||
|
|||||||
@ -34,7 +34,8 @@
|
|||||||
|
|
||||||
#define GUI_THREAD_FLAG_DRAW (1 << 0)
|
#define GUI_THREAD_FLAG_DRAW (1 << 0)
|
||||||
#define GUI_THREAD_FLAG_INPUT (1 << 1)
|
#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);
|
ARRAY_DEF(ViewPortArray, ViewPort*, M_PTR_OPLIST);
|
||||||
|
|
||||||
@ -46,11 +47,10 @@ typedef struct {
|
|||||||
/** Gui structure */
|
/** Gui structure */
|
||||||
struct Gui {
|
struct Gui {
|
||||||
// Thread and lock
|
// Thread and lock
|
||||||
FuriThreadId thread_id;
|
FuriThread* thread;
|
||||||
FuriMutex* mutex;
|
FuriMutex* mutex;
|
||||||
|
|
||||||
// Layers and Canvas
|
// Layers and Canvas
|
||||||
bool lockdown;
|
|
||||||
ViewPortArray_t layers[GuiLayerMAX];
|
ViewPortArray_t layers[GuiLayerMAX];
|
||||||
lv_obj_t* lvgl_parent;
|
lv_obj_t* lvgl_parent;
|
||||||
|
|
||||||
|
|||||||
@ -89,11 +89,10 @@ void view_port_draw(ViewPort* view_port, lv_obj_t* parent) {
|
|||||||
furi_check(view_port->gui);
|
furi_check(view_port->gui);
|
||||||
|
|
||||||
if (view_port->draw_callback) {
|
if (view_port->draw_callback) {
|
||||||
furi_check(lvgl_port_lock(100));
|
furi_check(lvgl_port_lock(100)); // TODO: fail safely
|
||||||
lv_obj_clean(parent);
|
lv_obj_clean(parent);
|
||||||
lvgl_port_unlock();
|
|
||||||
|
|
||||||
view_port->draw_callback(parent, view_port->draw_callback_context);
|
view_port->draw_callback(parent, view_port->draw_callback_context);
|
||||||
|
lvgl_port_unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
furi_mutex_release(view_port->mutex);
|
furi_mutex_release(view_port->mutex);
|
||||||
|
|||||||
@ -5,11 +5,19 @@
|
|||||||
#include "loader_i.h"
|
#include "loader_i.h"
|
||||||
#include <sys/cdefs.h>
|
#include <sys/cdefs.h>
|
||||||
#include "esp_heap_caps.h"
|
#include "esp_heap_caps.h"
|
||||||
|
#include "apps/services/gui/gui.h"
|
||||||
|
|
||||||
#define TAG "Loader"
|
#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;
|
LoaderMessage message;
|
||||||
LoaderMessageLoaderStatusResult result;
|
LoaderMessageLoaderStatusResult result;
|
||||||
|
|
||||||
@ -24,32 +32,23 @@ LoaderStatus loader_start(Loader* loader, const char* id, const char* args, Furi
|
|||||||
return result.value;
|
return result.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool loader_lock(Loader* loader) {
|
void loader_start_app_nonblocking(Loader* loader, const char* id, const char* args) {
|
||||||
LoaderMessage message;
|
LoaderMessage message;
|
||||||
LoaderMessageBoolResult result;
|
LoaderMessageLoaderStatusResult 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) {
|
message.type = LoaderMessageTypeStartByName;
|
||||||
LoaderMessage message;
|
message.start.id = id;
|
||||||
message.type = LoaderMessageTypeUnlock;
|
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);
|
furi_message_queue_put(loader->queue, &message, FuriWaitForever);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool loader_is_locked(Loader* loader) {
|
void loader_stop_app(Loader* loader) {
|
||||||
LoaderMessage message;
|
LoaderMessage message;
|
||||||
LoaderMessageBoolResult result;
|
message.type = LoaderMessageTypeAppStop;
|
||||||
message.type = LoaderMessageTypeIsLocked;
|
|
||||||
message.api_lock = api_lock_alloc_locked();
|
|
||||||
message.bool_value = &result;
|
|
||||||
furi_message_queue_put(loader->queue, &message, FuriWaitForever);
|
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) {
|
FuriPubSub* loader_get_pubsub(Loader* loader) {
|
||||||
@ -60,43 +59,28 @@ FuriPubSub* loader_get_pubsub(Loader* loader) {
|
|||||||
return loader->pubsub;
|
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
|
// implementation
|
||||||
|
|
||||||
static Loader* loader_alloc() {
|
static Loader* loader_alloc() {
|
||||||
Loader* loader = malloc(sizeof(Loader));
|
Loader* loader = malloc(sizeof(Loader));
|
||||||
loader->pubsub = furi_pubsub_alloc();
|
loader->pubsub = furi_pubsub_alloc();
|
||||||
loader->queue = furi_message_queue_alloc(1, sizeof(LoaderMessage));
|
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.args = NULL;
|
||||||
loader->app_data.thread = NULL;
|
|
||||||
loader->app_data.app = NULL;
|
loader->app_data.app = NULL;
|
||||||
return loader;
|
return loader;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void loader_start_app_thread(Loader* loader) {
|
static void loader_free(Loader* loader) {
|
||||||
// setup thread state callbacks
|
furi_pubsub_free(loader->pubsub);
|
||||||
furi_thread_set_state_context(loader->app_data.thread, loader);
|
furi_message_queue_free(loader->queue);
|
||||||
furi_thread_set_state_callback(loader->app_data.thread, loader_thread_state_callback);
|
furi_thread_free(loader->thread);
|
||||||
|
free(loader);
|
||||||
// start app thread
|
|
||||||
furi_thread_start(loader->app_data.thread);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void loader_log_status_error(
|
static void loader_log_status_error(
|
||||||
@ -115,7 +99,7 @@ static void loader_log_status_error(
|
|||||||
|
|
||||||
static LoaderStatus loader_make_status_error(
|
static LoaderStatus loader_make_status_error(
|
||||||
LoaderStatus status,
|
LoaderStatus status,
|
||||||
FuriString* error_message,
|
FuriString* _Nullable error_message,
|
||||||
const char* format,
|
const char* format,
|
||||||
...
|
...
|
||||||
) {
|
) {
|
||||||
@ -134,46 +118,41 @@ static LoaderStatus loader_make_success_status(FuriString* error_message) {
|
|||||||
return LoaderStatusOk;
|
return LoaderStatusOk;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void loader_start_app(
|
static void loader_start_app_with_manifest(
|
||||||
Loader* loader,
|
Loader* loader,
|
||||||
const AppManifest* _Nonnull manifest,
|
const AppManifest* _Nonnull manifest,
|
||||||
const char* args
|
const char* args
|
||||||
) {
|
) {
|
||||||
FURI_LOG_I(TAG, "Starting %s", manifest->id);
|
|
||||||
|
|
||||||
App* _Nonnull app = furi_app_alloc(manifest);
|
App* _Nonnull app = furi_app_alloc(manifest);
|
||||||
loader->app_data.app = app;
|
loader->app_data.app = app;
|
||||||
|
loader->app_data.args = (void*)args;
|
||||||
|
|
||||||
FuriThread* thread = furi_app_alloc_thread(loader->app_data.app, args);
|
if (manifest->on_start != NULL) {
|
||||||
loader->app_data.app->thread = thread;
|
manifest->on_start((void*)args);
|
||||||
loader->app_data.thread = thread;
|
}
|
||||||
|
|
||||||
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
|
// process messages
|
||||||
|
|
||||||
static bool loader_do_is_locked(Loader* loader) {
|
|
||||||
return loader->app_data.thread != NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static LoaderStatus loader_do_start_by_id(
|
static LoaderStatus loader_do_start_by_id(
|
||||||
Loader* loader,
|
Loader* loader,
|
||||||
const char* id,
|
const char* id,
|
||||||
const char* args,
|
const char* args,
|
||||||
FuriString* error_message
|
FuriString* _Nullable error_message
|
||||||
) {
|
) {
|
||||||
// check lock
|
FURI_LOG_I(TAG, "loader start by id %s", id);
|
||||||
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);
|
const AppManifest* manifest = app_manifest_registry_find_by_id(id);
|
||||||
if (manifest == NULL) {
|
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);
|
return loader_make_success_status(error_message);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool loader_do_lock(Loader* loader) {
|
static void loader_do_stop_app(Loader* loader) {
|
||||||
if (loader->app_data.thread) {
|
App* app = loader->app_data.app;
|
||||||
return false;
|
if (app == NULL) {
|
||||||
|
FURI_LOG_W(TAG, "Stop app: no app running");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
loader->app_data.thread = (FuriThread*)LOADER_MAGIC_THREAD_VALUE;
|
FURI_LOG_I(TAG, "Stopping %s", app->manifest->id);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void loader_do_unlock(Loader* loader) {
|
ViewPort* view_port = loader->app_data.view_port;
|
||||||
furi_check(loader->app_data.thread == (FuriThread*)LOADER_MAGIC_THREAD_VALUE);
|
if (view_port) {
|
||||||
loader->app_data.thread = NULL;
|
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) {
|
if (app->manifest->on_stop) {
|
||||||
furi_assert(loader->app_data.thread);
|
app->manifest->on_stop();
|
||||||
|
}
|
||||||
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) {
|
if (loader->app_data.args) {
|
||||||
free(loader->app_data.args);
|
free(loader->app_data.args);
|
||||||
loader->app_data.args = NULL;
|
loader->app_data.args = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loader->app_data.app) {
|
furi_app_free(loader->app_data.app);
|
||||||
furi_app_free(loader->app_data.app);
|
loader->app_data.app = NULL;
|
||||||
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(
|
FURI_LOG_I(
|
||||||
TAG,
|
TAG,
|
||||||
"Application stopped. Free heap: %zu",
|
"Application stopped. Free heap: %zu",
|
||||||
heap_caps_get_free_size(MALLOC_CAP_DEFAULT)
|
heap_caps_get_free_size(MALLOC_CAP_INTERNAL)
|
||||||
);
|
);
|
||||||
|
|
||||||
LoaderEvent event;
|
LoaderEvent event;
|
||||||
@ -242,42 +211,76 @@ static void loader_do_app_closed(Loader* loader) {
|
|||||||
|
|
||||||
// app
|
// app
|
||||||
|
|
||||||
_Noreturn int32_t loader_main(void* p) {
|
static int32_t loader_main(void* p) {
|
||||||
UNUSED(p);
|
UNUSED(p);
|
||||||
|
|
||||||
Loader* loader = loader_alloc();
|
FuriMessageQueue* queue = NULL;
|
||||||
furi_record_create(RECORD_LOADER, loader);
|
FURI_RECORD_TRANSACTION(RECORD_LOADER, Loader*, loader, {
|
||||||
|
queue = loader->queue;
|
||||||
|
})
|
||||||
|
furi_check(queue != NULL);
|
||||||
|
|
||||||
LoaderMessage message;
|
LoaderMessage message;
|
||||||
while (true) {
|
bool exit_requested = false;
|
||||||
if (furi_message_queue_get(loader->queue, &message, FuriWaitForever) == FuriStatusOk) {
|
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) {
|
switch (message.type) {
|
||||||
case LoaderMessageTypeStartByName:
|
case LoaderMessageTypeStartByName:
|
||||||
message.status_value->value = loader_do_start_by_id(
|
FURI_RECORD_TRANSACTION(RECORD_LOADER, Loader*, loader, {
|
||||||
loader,
|
if (loader->app_data.app) {
|
||||||
message.start.id,
|
loader_do_stop_app(loader);
|
||||||
message.start.args,
|
}
|
||||||
message.start.error_message
|
message.status_value->value = loader_do_start_by_id(
|
||||||
);
|
loader,
|
||||||
api_lock_unlock(message.api_lock);
|
message.start.id,
|
||||||
|
message.start.args,
|
||||||
|
message.start.error_message
|
||||||
|
);
|
||||||
|
if (message.api_lock) {
|
||||||
|
api_lock_unlock(message.api_lock);
|
||||||
|
}
|
||||||
|
})
|
||||||
break;
|
break;
|
||||||
case LoaderMessageTypeIsLocked:
|
case LoaderMessageTypeAppStop:
|
||||||
message.bool_value->value = loader_do_is_locked(loader);
|
FURI_RECORD_TRANSACTION(RECORD_LOADER, Loader*, loader, {
|
||||||
api_lock_unlock(message.api_lock);
|
loader_do_stop_app(loader);
|
||||||
|
})
|
||||||
break;
|
break;
|
||||||
case LoaderMessageTypeAppClosed:
|
case LoaderMessageTypeExit:
|
||||||
loader_do_app_closed(loader);
|
exit_requested = true;
|
||||||
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;
|
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 = {
|
const AppManifest loader_app = {
|
||||||
@ -285,6 +288,8 @@ const AppManifest loader_app = {
|
|||||||
.name = "Loader",
|
.name = "Loader",
|
||||||
.icon = NULL,
|
.icon = NULL,
|
||||||
.type = AppTypeService,
|
.type = AppTypeService,
|
||||||
.entry_point = &loader_main,
|
.on_start = &loader_start,
|
||||||
|
.on_stop = &loader_stop,
|
||||||
|
.on_show = NULL,
|
||||||
.stack_size = AppStackSizeNormal
|
.stack_size = AppStackSizeNormal
|
||||||
};
|
};
|
||||||
|
|||||||
@ -28,14 +28,24 @@ typedef struct {
|
|||||||
} LoaderEvent;
|
} LoaderEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Start application
|
* @brief Close any running app, then start new one. Blocking.
|
||||||
* @param[in] instance loader instance
|
* @param[in] loader loader instance
|
||||||
* @param[in] id application name or id
|
* @param[in] id application name or id
|
||||||
* @param[in] args application arguments
|
* @param[in] args application arguments
|
||||||
* @param[out] error_message detailed error message, can be NULL
|
* @param[out] error_message detailed error message, can be NULL
|
||||||
* @return LoaderStatus
|
* @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
|
* @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);
|
//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
|
* @brief Show loader menu
|
||||||
* @param[in] instance loader instance
|
* @param[in] instance loader instance
|
||||||
|
|||||||
@ -5,14 +5,16 @@
|
|||||||
#include "message_queue.h"
|
#include "message_queue.h"
|
||||||
#include "pubsub.h"
|
#include "pubsub.h"
|
||||||
#include "thread.h"
|
#include "thread.h"
|
||||||
|
#include "apps/services/gui/view_port.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char* args;
|
char* args;
|
||||||
FuriThread* thread;
|
|
||||||
App* app;
|
App* app;
|
||||||
|
ViewPort* view_port;
|
||||||
} LoaderAppData;
|
} LoaderAppData;
|
||||||
|
|
||||||
struct Loader {
|
struct Loader {
|
||||||
|
FuriThread* thread;
|
||||||
FuriPubSub* pubsub;
|
FuriPubSub* pubsub;
|
||||||
FuriMessageQueue* queue;
|
FuriMessageQueue* queue;
|
||||||
LoaderAppData app_data;
|
LoaderAppData app_data;
|
||||||
@ -20,10 +22,8 @@ struct Loader {
|
|||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
LoaderMessageTypeStartByName,
|
LoaderMessageTypeStartByName,
|
||||||
LoaderMessageTypeAppClosed,
|
LoaderMessageTypeAppStop,
|
||||||
LoaderMessageTypeLock,
|
LoaderMessageTypeExit,
|
||||||
LoaderMessageTypeUnlock,
|
|
||||||
LoaderMessageTypeIsLocked,
|
|
||||||
} LoaderMessageType;
|
} LoaderMessageType;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@ -41,6 +41,8 @@ typedef struct {
|
|||||||
} LoaderMessageBoolResult;
|
} LoaderMessageBoolResult;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
// This lock blocks anyone from starting an app as long
|
||||||
|
// as an app is already running via loader_start()
|
||||||
FuriApiLock api_lock;
|
FuriApiLock api_lock;
|
||||||
LoaderMessageType type;
|
LoaderMessageType type;
|
||||||
|
|
||||||
|
|||||||
@ -1,17 +1,50 @@
|
|||||||
#include "system_info.h"
|
#include "system_info.h"
|
||||||
#include "furi_extra_defines.h"
|
#include "furi_extra_defines.h"
|
||||||
#include "thread.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);
|
UNUSED(param);
|
||||||
|
|
||||||
printf(
|
printf(
|
||||||
"Heap memory available: %d / %d\n",
|
"Heap memory available: %d / %d\n",
|
||||||
heap_caps_get_free_size(MALLOC_CAP_DEFAULT),
|
heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
|
||||||
heap_caps_get_total_size(MALLOC_CAP_DEFAULT)
|
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 = {
|
AppManifest system_info_app = {
|
||||||
@ -19,6 +52,8 @@ AppManifest system_info_app = {
|
|||||||
.name = "System Info",
|
.name = "System Info",
|
||||||
.icon = NULL,
|
.icon = NULL,
|
||||||
.type = AppTypeSystem,
|
.type = AppTypeSystem,
|
||||||
.entry_point = &system_info_entry_point,
|
.on_start = NULL,
|
||||||
|
.on_stop = NULL,
|
||||||
|
.on_show = app_show,
|
||||||
.stack_size = AppStackSizeNormal
|
.stack_size = AppStackSizeNormal
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
#include "devices_i.h"
|
#include "devices_i.h"
|
||||||
#include "furi.h"
|
#include "furi.h"
|
||||||
#include "graphics_i.h"
|
#include "graphics_i.h"
|
||||||
|
#include "partitions.h"
|
||||||
|
|
||||||
#define TAG "nanobake"
|
#define TAG "nanobake"
|
||||||
|
|
||||||
@ -18,15 +19,7 @@ extern const AppManifest system_info_app;
|
|||||||
void start_service(const AppManifest* _Nonnull manifest) {
|
void start_service(const AppManifest* _Nonnull manifest) {
|
||||||
// TODO: keep track of running services
|
// TODO: keep track of running services
|
||||||
FURI_LOG_I(TAG, "Starting service %s", manifest->name);
|
FURI_LOG_I(TAG, "Starting service %s", manifest->name);
|
||||||
FuriThread* thread = furi_thread_alloc_ex(
|
manifest->on_start(NULL);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void register_apps(Config* _Nonnull config) {
|
static void register_apps(Config* _Nonnull config) {
|
||||||
@ -51,6 +44,8 @@ static void start_services() {
|
|||||||
__attribute__((unused)) extern void nanobake_start(Config* _Nonnull config) {
|
__attribute__((unused)) extern void nanobake_start(Config* _Nonnull config) {
|
||||||
furi_init();
|
furi_init();
|
||||||
|
|
||||||
|
nb_partitions_init();
|
||||||
|
|
||||||
Devices hardware = nb_devices_create(config);
|
Devices hardware = nb_devices_create(config);
|
||||||
/*NbLvgl lvgl =*/nb_graphics_init(&hardware);
|
/*NbLvgl lvgl =*/nb_graphics_init(&hardware);
|
||||||
|
|
||||||
|
|||||||
66
components/nanobake/src/partitions.c
Normal file
66
components/nanobake/src/partitions.c
Normal file
@ -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;
|
||||||
|
}
|
||||||
8
components/nanobake/src/partitions.h
Normal file
8
components/nanobake/src/partitions.h
Normal file
@ -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();
|
||||||
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
idf_component_register(
|
idf_component_register(
|
||||||
SRC_DIRS "src"
|
SRC_DIRS "src"
|
||||||
"src/hello_world"
|
"src/hello_world"
|
||||||
|
|||||||
@ -1,21 +1,16 @@
|
|||||||
#include "hello_world.h"
|
#include "hello_world.h"
|
||||||
#include "furi.h"
|
#include "furi.h"
|
||||||
#include "apps/services/gui/gui.h"
|
#include "apps/services/gui/gui.h"
|
||||||
#include "esp_lvgl_port.h"
|
#include "apps/services/loader/loader.h"
|
||||||
|
|
||||||
static const char* TAG = "app_hello_world";
|
|
||||||
|
|
||||||
ViewPort* view_port = NULL;
|
|
||||||
FuriSemaphore* quit_lock = NULL;
|
|
||||||
|
|
||||||
static void on_button_click(lv_event_t _Nonnull* event) {
|
static void on_button_click(lv_event_t _Nonnull* event) {
|
||||||
ESP_LOGI(TAG, "button clicked");
|
FURI_RECORD_TRANSACTION(RECORD_LOADER, Loader*, loader, {
|
||||||
furi_semaphore_give(quit_lock);
|
loader_start_app_nonblocking(loader, "systeminfo", NULL);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main entry point for LVGL widget creation
|
static void app_show(lv_obj_t* parent, void* context) {
|
||||||
static void app_lvgl(lv_obj_t* parent, void* context) {
|
UNUSED(context);
|
||||||
lvgl_port_lock(0);
|
|
||||||
|
|
||||||
lv_obj_t* label = lv_label_create(parent);
|
lv_obj_t* label = lv_label_create(parent);
|
||||||
lv_label_set_recolor(label, true);
|
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);
|
lv_obj_t* btn = lv_btn_create(parent);
|
||||||
label = lv_label_create(btn);
|
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_align(btn, LV_ALIGN_CENTER, 0, 30);
|
||||||
lv_obj_add_event_cb(btn, on_button_click, LV_EVENT_CLICKED, NULL);
|
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 = {
|
const AppManifest hello_world_app = {
|
||||||
@ -68,6 +31,8 @@ const AppManifest hello_world_app = {
|
|||||||
.name = "Hello World",
|
.name = "Hello World",
|
||||||
.icon = NULL,
|
.icon = NULL,
|
||||||
.type = AppTypeUser,
|
.type = AppTypeUser,
|
||||||
.entry_point = &app_main,
|
.on_start = NULL,
|
||||||
|
.on_stop = NULL,
|
||||||
|
.on_show = &app_show,
|
||||||
.stack_size = AppStackSizeNormal,
|
.stack_size = AppStackSizeNormal,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -22,8 +22,9 @@ __attribute__((unused)) void app_main(void) {
|
|||||||
|
|
||||||
FURI_RECORD_TRANSACTION(RECORD_LOADER, Loader*, loader, {
|
FURI_RECORD_TRANSACTION(RECORD_LOADER, Loader*, loader, {
|
||||||
FuriString* error_message = furi_string_alloc();
|
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_LOG_E(hello_world_app.id, "%s\r\n", furi_string_get_cstr(error_message));
|
||||||
}
|
}
|
||||||
|
furi_string_free(error_message);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
7
partitions.csv
Normal file
7
partitions.csv
Normal file
@ -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,
|
||||||
|
@ -2,3 +2,6 @@ CONFIG_IDF_TARGET="esp32"
|
|||||||
CONFIG_LV_COLOR_16_SWAP=y
|
CONFIG_LV_COLOR_16_SWAP=y
|
||||||
CONFIG_LV_USE_USER_DATA=y
|
CONFIG_LV_USE_USER_DATA=y
|
||||||
CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=2
|
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"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user