Boot splash and more (#98)

* Boot splash and more

- Added developer sdkconfig
- Refactored the way FreeRTOS includes are included
- Improved Gui/Loader logic
- Implemented boot app with splash screen

* Updated naming for Gui and Loader services

* Renamed Screenshot service methods

* Renames

* Service renames
This commit is contained in:
Ken Van Hoeylandt
2024-11-30 15:37:16 +01:00
committed by GitHub
parent 3f62ec2efa
commit 0188ce721c
60 changed files with 726 additions and 307 deletions
+9 -14
View File
@@ -1,14 +1,9 @@
#include "Tactility.h"
#include "service/gui/Gui_i.h"
#include "service/loader/Loader.h"
#include "service/loader/Loader_i.h"
#include "lvgl/LvglKeypad.h"
#include "lvgl/LvglSync.h"
#ifdef ESP_PLATFORM
#include "freertos/FreeRTOS.h"
#else
#include "FreeRTOS.h"
#endif
#include "RtosCompat.h"
namespace tt::service::gui {
@@ -25,9 +20,9 @@ void loader_callback(const void* message, TT_UNUSED void* context) {
if (event->type == loader::LoaderEventTypeApplicationShowing) {
app::App& app = event->app_showing.app;
const app::Manifest& app_manifest = app.getManifest();
show_app(app, app_manifest.onShow, app_manifest.onHide);
showApp(app, app_manifest.onShow, app_manifest.onHide);
} else if (event->type == loader::LoaderEventTypeApplicationHiding) {
hide_app();
hideApp();
}
}
@@ -43,7 +38,7 @@ Gui* gui_alloc() {
);
instance->mutex = tt_mutex_alloc(MutexTypeRecursive);
instance->keyboard = nullptr;
instance->loader_pubsub_subscription = tt_pubsub_subscribe(loader::get_pubsub(), &loader_callback, instance);
instance->loader_pubsub_subscription = tt_pubsub_subscribe(loader::getPubsub(), &loader_callback, instance);
tt_check(lvgl::lock(1000 / portTICK_PERIOD_MS));
instance->keyboard_group = lv_group_create();
instance->lvgl_parent = lv_scr_act();
@@ -76,21 +71,21 @@ void unlock() {
tt_check(tt_mutex_release(gui->mutex) == TtStatusOk);
}
void request_draw() {
void requestDraw() {
tt_assert(gui);
ThreadId thread_id = gui->thread->getId();
thread_flags_set(thread_id, GUI_THREAD_FLAG_DRAW);
}
void show_app(app::App& app, ViewPortShowCallback on_show, ViewPortHideCallback on_hide) {
void showApp(app::App& app, ViewPortShowCallback on_show, ViewPortHideCallback on_hide) {
lock();
tt_check(gui->app_view_port == nullptr);
gui->app_view_port = view_port_alloc(app, on_show, on_hide);
unlock();
request_draw();
requestDraw();
}
void hide_app() {
void hideApp() {
lock();
ViewPort* view_port = gui->app_view_port;
tt_check(view_port != nullptr);
+6 -6
View File
@@ -14,26 +14,26 @@ typedef struct Gui Gui;
* @param on_show
* @param on_hide
*/
void show_app(app::App& app, ViewPortShowCallback on_show, ViewPortHideCallback on_hide);
void showApp(app::App& app, ViewPortShowCallback on_show, ViewPortHideCallback on_hide);
/**
* Hide the current app's viewport.
* Does not request a re-draw because after hiding the current app,
* we always show the previous app, and there is always at least 1 app running.
*/
void hide_app();
void hideApp();
/**
* Show the on-screen keyboard.
* @param textarea the textarea to focus the input for
*/
void keyboard_show(lv_obj_t* textarea);
void keyboardShow(lv_obj_t* textarea);
/**
* Hide the on-screen keyboard.
* Has no effect when the keyboard is not visible.
*/
void keyboard_hide();
void keyboardHide();
/**
* The on-screen keyboard is only shown when both of these conditions are true:
@@ -41,7 +41,7 @@ void keyboard_hide();
* - TT_CONFIG_FORCE_ONSCREEN_KEYBOARD is set to true in tactility_config.h
* @return if we should show a on-screen keyboard for text input inside our apps
*/
bool keyboard_is_enabled();
bool keyboardIsEnabled();
/**
* Glue code for the on-screen keyboard and the hardware keyboard:
@@ -49,6 +49,6 @@ bool keyboard_is_enabled();
* - Registers the textarea to the default lv_group_t for hardware keyboards.
* @param textarea
*/
void keyboard_add_textarea(lv_obj_t* textarea);
void keyboardAddTextArea(lv_obj_t* textarea);
} // namespace
+1 -1
View File
@@ -28,7 +28,7 @@ static lv_obj_t* create_app_views(Gui* gui, lv_obj_t* parent, app::App& app) {
lv_obj_set_width(child_container, LV_PCT(100));
lv_obj_set_flex_grow(child_container, 1);
if (keyboard_is_enabled()) {
if (keyboardIsEnabled()) {
gui->keyboard = lv_keyboard_create(vertical_container);
lv_obj_add_flag(gui->keyboard, LV_OBJ_FLAG_HIDDEN);
} else {
+7 -7
View File
@@ -10,19 +10,19 @@ extern Gui* gui;
static void show_keyboard(lv_event_t* event) {
lv_obj_t* target = lv_event_get_current_target_obj(event);
keyboard_show(target);
keyboardShow(target);
lv_obj_scroll_to_view(target, LV_ANIM_ON);
}
static void hide_keyboard(TT_UNUSED lv_event_t* event) {
keyboard_hide();
keyboardHide();
}
bool keyboard_is_enabled() {
bool keyboardIsEnabled() {
return !lvgl::keypad_is_available() || TT_CONFIG_FORCE_ONSCREEN_KEYBOARD;
}
void keyboard_show(lv_obj_t* textarea) {
void keyboardShow(lv_obj_t* textarea) {
lock();
if (gui->keyboard) {
@@ -33,7 +33,7 @@ void keyboard_show(lv_obj_t* textarea) {
unlock();
}
void keyboard_hide() {
void keyboardHide() {
lock();
if (gui->keyboard) {
@@ -43,11 +43,11 @@ void keyboard_hide() {
unlock();
}
void keyboard_add_textarea(lv_obj_t* textarea) {
void keyboardAddTextArea(lv_obj_t* textarea) {
lock();
tt_check(lvgl::lock(0), "lvgl should already be locked before calling this method");
if (keyboard_is_enabled()) {
if (keyboardIsEnabled()) {
lv_obj_add_event_cb(textarea, show_keyboard, LV_EVENT_FOCUSED, nullptr);
lv_obj_add_event_cb(textarea, hide_keyboard, LV_EVENT_DEFOCUSED, nullptr);
lv_obj_add_event_cb(textarea, hide_keyboard, LV_EVENT_READY, nullptr);
+42 -37
View File
@@ -5,14 +5,12 @@
#include "service/Manifest.h"
#include "service/gui/Gui.h"
#include "service/loader/Loader_i.h"
#include "RtosCompat.h"
#ifdef ESP_PLATFORM
#include "esp_heap_caps.h"
#include "freertos/FreeRTOS.h"
#else
#include "FreeRTOS.h"
#include "lvgl/LvglSync.h"
#endif
namespace tt::service::loader {
@@ -65,7 +63,7 @@ static void loader_unlock() {
tt_check(tt_mutex_release(loader_singleton->mutex) == TtStatusOk);
}
LoaderStatus start_app(const std::string& id, bool blocking, const Bundle& bundle) {
LoaderStatus startApp(const std::string& id, bool blocking, const Bundle& arguments) {
TT_LOG_I(TAG, "Start app %s", id.c_str());
tt_assert(loader_singleton);
@@ -73,7 +71,7 @@ LoaderStatus start_app(const std::string& id, bool blocking, const Bundle& bundl
.value = LoaderStatusOk
};
auto* start_message = new LoaderMessageAppStart(id, bundle);
auto* start_message = new LoaderMessageAppStart(id, arguments);
LoaderMessage message(start_message, result);
EventFlag* event_flag = blocking ? new EventFlag() : nullptr;
@@ -94,14 +92,14 @@ LoaderStatus start_app(const std::string& id, bool blocking, const Bundle& bundl
return result.value;
}
void stop_app() {
void stopApp() {
TT_LOG_I(TAG, "Stop app");
tt_check(loader_singleton);
LoaderMessage message(LoaderMessageTypeAppStop);
loader_singleton->queue.put(&message, TtWaitForever);
}
app::App* _Nullable get_current_app() {
app::App* _Nullable getCurrentApp() {
tt_assert(loader_singleton);
loader_lock();
app::AppInstance* app = loader_singleton->app_stack.top();
@@ -109,7 +107,7 @@ app::App* _Nullable get_current_app() {
return dynamic_cast<app::App*>(app);
}
PubSub* get_pubsub() {
PubSub* getPubsub() {
tt_assert(loader_singleton);
// it's safe to return pubsub without locking
// because it's never freed and loader is never exited
@@ -160,7 +158,7 @@ static void app_transition_to_state(app::AppInstance& app, app::State state) {
LoaderEvent event_showing = {
.type = LoaderEventTypeApplicationShowing,
.app_showing = {
.app = dynamic_cast<app::App&>(app)
.app = app
}
};
tt_pubsub_publish(loader_singleton->pubsub_external, &event_showing);
@@ -171,7 +169,7 @@ static void app_transition_to_state(app::AppInstance& app, app::State state) {
LoaderEvent event_hiding = {
.type = LoaderEventTypeApplicationHiding,
.app_hiding = {
.app = dynamic_cast<app::App&>(app)
.app = app
}
};
tt_pubsub_publish(loader_singleton->pubsub_external, &event_hiding);
@@ -198,6 +196,8 @@ static LoaderStatus loader_do_start_app_with_manifest(
auto previous_app = !loader_singleton->app_stack.empty() ? loader_singleton->app_stack.top() : nullptr;
auto new_app = new app::AppInstance(*manifest, bundle);
new_app->mutableFlags().showStatusbar = (manifest->type != app::TypeBoot);
loader_singleton->app_stack.push(new_app);
app_transition_to_state(*new_app, app::StateInitial);
app_transition_to_state(*new_app, app::StateStarted);
@@ -217,7 +217,7 @@ static LoaderStatus loader_do_start_app_with_manifest(
LoaderEvent event_external = {
.type = LoaderEventTypeApplicationStarted,
.app_started = {
.app = dynamic_cast<app::App&>(*new_app)
.app = *new_app
}
};
tt_pubsub_publish(loader_singleton->pubsub_external, &event_external);
@@ -233,6 +233,7 @@ static LoaderStatus do_start_by_id(
const app::Manifest* manifest = app::findAppById(id);
if (manifest == nullptr) {
TT_LOG_E(TAG, "App not found: %s", id.c_str());
return LoaderStatusErrorUnknownApp;
} else {
return loader_do_start_app_with_manifest(manifest, bundle);
@@ -251,14 +252,15 @@ static void do_stop_app() {
return;
}
if (original_stack_size == 1) {
// Stop current app
app::AppInstance* app_to_stop = loader_singleton->app_stack.top();
if (original_stack_size == 1 && app_to_stop->getManifest().type != app::TypeBoot) {
loader_unlock();
TT_LOG_E(TAG, "Stop app: can't stop root app");
return;
}
// Stop current app
app::AppInstance* app_to_stop = loader_singleton->app_stack.top();
std::unique_ptr<app::ResultHolder> result_holder = std::move(app_to_stop->getResult());
const app::Manifest& manifest = app_to_stop->getManifest();
@@ -272,35 +274,38 @@ static void do_stop_app() {
TT_LOG_I(TAG, "Free heap: %zu", heap_caps_get_free_size(MALLOC_CAP_INTERNAL));
#endif
app::AppInstance* app_to_resume = loader_singleton->app_stack.top();
tt_assert(app_to_resume);
app_transition_to_state(*app_to_resume, app::StateShowing);
// If there's a previous app, resume it
if (!loader_singleton->app_stack.empty()) {
app::AppInstance* app_to_resume = loader_singleton->app_stack.top();
tt_assert(app_to_resume);
app_transition_to_state(*app_to_resume, app::StateShowing);
auto on_result = app_to_resume->getManifest().onResult;
if (on_result != nullptr) {
if (result_holder != nullptr) {
Bundle* result_bundle = result_holder->resultData;
if (result_bundle != nullptr) {
on_result(
*app_to_resume,
result_holder->result,
*result_bundle
);
auto on_result = app_to_resume->getManifest().onResult;
if (on_result != nullptr) {
if (result_holder != nullptr) {
Bundle* result_bundle = result_holder->resultData;
if (result_bundle != nullptr) {
on_result(
*app_to_resume,
result_holder->result,
*result_bundle
);
} else {
const Bundle empty_bundle;
on_result(
*app_to_resume,
result_holder->result,
empty_bundle
);
}
} else {
const Bundle empty_bundle;
on_result(
*app_to_resume,
result_holder->result,
empty_bundle
*app_to_resume,
app::ResultCancelled,
empty_bundle
);
}
} else {
const Bundle empty_bundle;
on_result(
*app_to_resume,
app::ResultCancelled,
empty_bundle
);
}
}
+6 -38
View File
@@ -16,58 +16,26 @@ typedef enum {
LoaderStatusErrorInternal,
} LoaderStatus;
typedef enum {
LoaderEventTypeApplicationStarted,
LoaderEventTypeApplicationShowing,
LoaderEventTypeApplicationHiding,
LoaderEventTypeApplicationStopped
} LoaderEventType;
typedef struct {
app::App& app;
} LoaderEventAppStarted;
typedef struct {
app::App& app;
} LoaderEventAppShowing;
typedef struct {
app::App& app;
} LoaderEventAppHiding;
typedef struct {
const app::Manifest& manifest;
} LoaderEventAppStopped;
typedef struct {
LoaderEventType type;
union {
LoaderEventAppStarted app_started;
LoaderEventAppShowing app_showing;
LoaderEventAppHiding app_hiding;
LoaderEventAppStopped app_stopped;
};
} LoaderEvent;
/**
* @brief Start an app
* @param[in] id application name or id
* @param[in] blocking application arguments
* @param[in] bundle optional bundle. Ownership is transferred to Loader.
* @param[in] blocking whether this call is blocking or not. You cannot call this from an LVGL thread.
* @param[in] arguments optional parameters to pass onto the application
* @return LoaderStatus
*/
LoaderStatus start_app(const std::string& id, bool blocking, const Bundle& bundle);
LoaderStatus startApp(const std::string& id, bool blocking = false, const Bundle& arguments = Bundle());
/**
* @brief Stop the currently showing app. Show the previous app if any app was still running.
*/
void stop_app();
void stopApp();
app::App* _Nullable get_current_app();
app::App* _Nullable getCurrentApp();
/**
* @brief PubSub for LoaderEvent
*/
PubSub* get_pubsub();
PubSub* getPubsub();
} // namespace
@@ -15,8 +15,8 @@ extern const Manifest manifest;
typedef struct {
Mutex* mutex;
ScreenshotTask* task;
ScreenshotMode mode;
task::ScreenshotTask* task;
Mode mode;
} ServiceData;
static ServiceData* service_data_alloc() {
@@ -49,14 +49,14 @@ static void on_start(Service& service) {
static void on_stop(Service& service) {
auto* data = static_cast<ServiceData*>(service.getData());
if (data->task) {
task_free(data->task);
task::free(data->task);
data->task = nullptr;
}
tt_mutex_free(data->mutex);
service_data_free(data);
}
void start_apps(const char* path) {
void startApps(const char* path) {
_Nullable auto* service = findServiceById(manifest.id);
if (service == nullptr) {
TT_LOG_E(TAG, "Service not found");
@@ -66,16 +66,16 @@ void start_apps(const char* path) {
auto* data = static_cast<ServiceData*>(service->getData());
service_data_lock(data);
if (data->task == nullptr) {
data->task = task_alloc();
data->task = task::alloc();
data->mode = ScreenshotModeApps;
task_start_apps(data->task, path);
task::startApps(data->task, path);
} else {
TT_LOG_E(TAG, "Screenshot task already running");
}
service_data_unlock(data);
}
void start_timed(const char* path, uint8_t delay_in_seconds, uint8_t amount) {
void startTimed(const char* path, uint8_t delay_in_seconds, uint8_t amount) {
_Nullable auto* service = findServiceById(manifest.id);
if (service == nullptr) {
TT_LOG_E(TAG, "Service not found");
@@ -85,9 +85,9 @@ void start_timed(const char* path, uint8_t delay_in_seconds, uint8_t amount) {
auto* data = static_cast<ServiceData*>(service->getData());
service_data_lock(data);
if (data->task == nullptr) {
data->task = task_alloc();
data->task = task::alloc();
data->mode = ScreenshotModeTimed;
task_start_timed(data->task, path, delay_in_seconds, amount);
task::startTimed(data->task, path, delay_in_seconds, amount);
} else {
TT_LOG_E(TAG, "Screenshot task already running");
}
@@ -104,8 +104,8 @@ void stop() {
auto data = static_cast<ServiceData*>(service->getData());
service_data_lock(data);
if (data->task != nullptr) {
task_stop(data->task);
task_free(data->task);
task::stop(data->task);
task::free(data->task);
data->task = nullptr;
data->mode = ScreenshotModeNone;
} else {
@@ -114,7 +114,7 @@ void stop() {
service_data_unlock(data);
}
ScreenshotMode get_mode() {
Mode getMode() {
_Nullable auto* service = findServiceById(manifest.id);
if (service == nullptr) {
TT_LOG_E(TAG, "Service not found");
@@ -122,14 +122,14 @@ ScreenshotMode get_mode() {
} else {
auto* data = static_cast<ServiceData*>(service->getData());
service_data_lock(data);
ScreenshotMode mode = data->mode;
Mode mode = data->mode;
service_data_unlock(data);
return mode;
}
}
bool is_started() {
return get_mode() != ScreenshotModeNone;
bool isStarted() {
return getMode() != ScreenshotModeNone;
}
extern const Manifest manifest = {
@@ -8,24 +8,24 @@ typedef enum {
ScreenshotModeNone,
ScreenshotModeTimed,
ScreenshotModeApps
} ScreenshotMode;
} Mode;
/** @brief Starts taking screenshot with a timer
* @param path the path to store the screenshots in
* @param delay_in_seconds the delay before starting (and between successive screenshots)
* @param amount 0 = indefinite, >0 for a specific
*/
void start_timed(const char* path, uint8_t delay_in_seconds, uint8_t amount);
void startTimed(const char* path, uint8_t delay_in_seconds, uint8_t amount);
/** @brief Starts taking screenshot when an app is started
* @param path the path to store the screenshots in
*/
void start_apps(const char* path);
void startApps(const char* path);
void stop();
ScreenshotMode get_mode();
Mode getMode();
bool is_started();
bool isStarted();
} // namespace
@@ -8,7 +8,7 @@
#include "service/loader/Loader.h"
#include "lvgl/LvglSync.h"
namespace tt::service::screenshot {
namespace tt::service::screenshot::task {
#define TAG "screenshot_task"
@@ -39,7 +39,7 @@ static void task_unlock(ScreenshotTaskData* data) {
tt_check(tt_mutex_release(data->mutex) == TtStatusOk);
}
ScreenshotTask* task_alloc() {
ScreenshotTask* alloc() {
auto* data = static_cast<ScreenshotTaskData*>(malloc(sizeof(ScreenshotTaskData)));
*data = (ScreenshotTaskData) {
.thread = nullptr,
@@ -49,10 +49,10 @@ ScreenshotTask* task_alloc() {
return data;
}
void task_free(ScreenshotTask* task) {
void free(ScreenshotTask* task) {
auto* data = static_cast<ScreenshotTaskData*>(task);
if (data->thread) {
task_stop(data);
stop(data);
}
}
@@ -98,7 +98,7 @@ static int32_t screenshot_task(void* context) {
break; // Interrupted loop
}
} else if (data->work.type == TASK_WORK_TYPE_APPS) {
app::App* _Nullable app = loader::get_current_app();
app::App* _Nullable app = loader::getCurrentApp();
if (app) {
const app::Manifest& manifest = app->getManifest();
if (manifest.id != last_app_id) {
@@ -136,7 +136,7 @@ static void task_start(ScreenshotTaskData* data) {
task_unlock(data);
}
void task_start_apps(ScreenshotTask* task, const char* path) {
void startApps(ScreenshotTask* task, const char* path) {
tt_check(strlen(path) < (SCREENSHOT_PATH_LIMIT - 1));
auto* data = static_cast<ScreenshotTaskData*>(task);
task_lock(data);
@@ -151,7 +151,7 @@ void task_start_apps(ScreenshotTask* task, const char* path) {
task_unlock(data);
}
void task_start_timed(ScreenshotTask* task, const char* path, uint8_t delay_in_seconds, uint8_t amount) {
void startTimed(ScreenshotTask* task, const char* path, uint8_t delay_in_seconds, uint8_t amount) {
tt_check(strlen(path) < (SCREENSHOT_PATH_LIMIT - 1));
auto* data = static_cast<ScreenshotTaskData*>(task);
task_lock(data);
@@ -168,7 +168,7 @@ void task_start_timed(ScreenshotTask* task, const char* path, uint8_t delay_in_s
task_unlock(data);
}
void task_stop(ScreenshotTask* task) {
void stop(ScreenshotTask* task) {
auto* data = static_cast<ScreenshotTaskData*>(task);
if (data->thread != nullptr) {
task_lock(data);
@@ -2,13 +2,13 @@
#include <cstdint>
namespace tt::service::screenshot {
namespace tt::service::screenshot::task {
typedef void ScreenshotTask;
ScreenshotTask* task_alloc();
ScreenshotTask* alloc();
void task_free(ScreenshotTask* task);
void free(ScreenshotTask* task);
/** @brief Start taking screenshots after a certain delay
* @param task the screenshot task
@@ -16,17 +16,17 @@ void task_free(ScreenshotTask* task);
* @param delay_in_seconds the delay before starting (and between successive screenshots)
* @param amount 0 = indefinite, >0 for a specific
*/
void task_start_timed(ScreenshotTask* task, const char* path, uint8_t delay_in_seconds, uint8_t amount);
void startTimed(ScreenshotTask* task, const char* path, uint8_t delay_in_seconds, uint8_t amount);
/** @brief Start taking screenshot whenever an app is started
* @param task the screenshot task
* @param path the path to store the screenshots at
*/
void task_start_apps(ScreenshotTask* task, const char* path);
void startApps(ScreenshotTask* task, const char* path);
/** @brief Stop taking screenshots
* @param task the screenshot task
*/
void task_stop(ScreenshotTask* task);
void stop(ScreenshotTask* task);
}
@@ -25,7 +25,7 @@ typedef struct {
// region wifi
const char* get_status_icon_for_rssi(int rssi, bool secured) {
const char* getWifiStatusIconForRssi(int rssi, bool secured) {
if (rssi > 0) {
return TT_ASSETS_ICON_WIFI_CONNECTION_ISSUE;
} else if (rssi >= -30) {
@@ -52,16 +52,16 @@ static const char* wifi_get_status_icon(wifi::WifiRadioState state, bool secure)
case wifi::WIFI_RADIO_CONNECTION_PENDING:
return TT_ASSETS_ICON_WIFI_FIND;
case wifi::WIFI_RADIO_CONNECTION_ACTIVE:
rssi = wifi::get_rssi();
return get_status_icon_for_rssi(rssi, secure);
rssi = wifi::getRssi();
return getWifiStatusIconForRssi(rssi, secure);
default:
tt_crash("not implemented");
}
}
static void update_wifi_icon(ServiceData* data) {
wifi::WifiRadioState radio_state = wifi::get_radio_state();
bool is_secure = wifi::is_connection_secure();
wifi::WifiRadioState radio_state = wifi::getRadioState();
bool is_secure = wifi::isConnectionSecure();
const char* desired_icon = wifi_get_status_icon(radio_state, is_secure);
if (data->wifi_last_icon != desired_icon) {
lvgl::statusbar_icon_set_image(data->wifi_icon_id, desired_icon);
@@ -8,6 +8,6 @@ namespace tt::service::statusbar {
* @param secured whether the access point is a secured one (as in: not an open one)
* @return
*/
const char* get_status_icon_for_rssi(int rssi, bool secured);
const char* getWifiStatusIconForRssi(int rssi, bool secured);
} // namespace