app-module events & callstack config, crash diagnostics (#630)
- Apps can specify task stack depth and preferred memory placement in their manifests. - App identifiers are validated against length and character requirements. - Crash diagnostics now show the crash cause, reason, fault address, call stack, and program-counter details, with logs saved for review. - App closing is more consistent across built-in screens. There's now a dedicated function, and the old _emit() function is made private. - Crash diagnostics no longer display a QR code without a call stack. - Improved memory allocation for unrestricted requests.
This commit is contained in:
committed by
GitHub
parent
c656ee9ffd
commit
92ca046681
@@ -6,6 +6,7 @@
|
||||
|
||||
#define CRASH_DATA_CALLSTACK_LIMIT 64
|
||||
#define CRASH_DATA_INCLUDES_SP false
|
||||
#define CRASH_DATA_REASON_LENGTH 128
|
||||
|
||||
/** Represents a single frame on the callstack. */
|
||||
struct CallstackFrame {
|
||||
@@ -15,11 +16,25 @@ struct CallstackFrame {
|
||||
#endif
|
||||
};
|
||||
|
||||
/** Broad category of what caused the panic (mirrors ESP-IDF's panic_exception_t). */
|
||||
enum class CrashCause : uint8_t {
|
||||
Unknown,
|
||||
Debug,
|
||||
WatchdogInterrupt,
|
||||
WatchdogTask,
|
||||
Abort,
|
||||
Fault,
|
||||
};
|
||||
|
||||
/** Callstack-related crash data. */
|
||||
struct CrashData {
|
||||
bool callstackCorrupted = false;
|
||||
uint8_t callstackLength = 0;
|
||||
CallstackFrame callstack[CRASH_DATA_CALLSTACK_LIMIT];
|
||||
|
||||
CrashCause cause = CrashCause::Unknown;
|
||||
uint32_t faultAddress = 0;
|
||||
char reason[CRASH_DATA_REASON_LENGTH] = { 0 };
|
||||
};
|
||||
|
||||
/** @return the crash data */
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <Tactility/PanicHandler.h>
|
||||
|
||||
std::string getUrlFromCrashData();
|
||||
std::string getUrlFromCrashData(const CrashData& data);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <Tactility/DeprecatedPaths.h>
|
||||
|
||||
#include "../../Modules/app-module/private/app/private/app_metadata_parsing_internal.h"
|
||||
#include <app/manifest.h>
|
||||
|
||||
#include <Tactility/MountPoints.h>
|
||||
|
||||
@@ -72,12 +72,12 @@ std::string getUserHomePath() {
|
||||
}
|
||||
|
||||
std::string getAppInstallPath(const std::string& appId) {
|
||||
assert(app_metadata_is_valid_id(appId.c_str()));
|
||||
assert(app_id_is_valid(appId.c_str()));
|
||||
return std::format("{}/{}", getAppInstallPath(), appId);
|
||||
}
|
||||
|
||||
std::string getAppUserPath(const std::string& appId) {
|
||||
assert(app_metadata_is_valid_id(appId.c_str()));
|
||||
assert(app_id_is_valid(appId.c_str()));
|
||||
return std::format("{}/app/{}", getUserHomePath(), appId);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
#if defined(ESP_PLATFORM)
|
||||
#include <sdkconfig.h>
|
||||
#endif
|
||||
|
||||
#if defined(ESP_PLATFORM) && defined(CONFIG_IDF_TARGET_ARCH_XTENSA)
|
||||
|
||||
#include "Tactility/kernel/PanicHandler.h"
|
||||
#include <Tactility/PanicHandler.h>
|
||||
|
||||
#include <esp_debug_helpers.h>
|
||||
#include <esp_attr.h>
|
||||
#include <esp_memory_utils.h>
|
||||
#include <esp_cpu.h>
|
||||
#include <esp_cpu_utils.h>
|
||||
#include <esp_debug_helpers.h>
|
||||
#include <esp_memory_utils.h>
|
||||
#include <esp_private/panic_internal.h>
|
||||
#include <xtensa/xtruntime.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
extern "C" {
|
||||
|
||||
/**
|
||||
@@ -28,7 +35,34 @@ void __wrap_esp_panic_handler(void* info) {
|
||||
.exc_frame = nullptr
|
||||
};
|
||||
|
||||
const auto* panic_info = static_cast<const panic_info_t*>(info);
|
||||
|
||||
switch (panic_info->exception) {
|
||||
// Watchdag timer issues are not consider real crashes: they trigger relatively often
|
||||
// and could cause a previous real crash to be overwritten by a watchdog timer warning during reboot.
|
||||
case PANIC_EXCEPTION_IWDT: crashData.cause = CrashCause::WatchdogInterrupt; return;
|
||||
case PANIC_EXCEPTION_TWDT: crashData.cause = CrashCause::WatchdogTask; return;
|
||||
// We also don't care about debugger errors:
|
||||
case PANIC_EXCEPTION_DEBUG: crashData.cause = CrashCause::Debug; return;
|
||||
// We only care about 'real' crashes:
|
||||
case PANIC_EXCEPTION_ABORT: crashData.cause = CrashCause::Abort; break;
|
||||
case PANIC_EXCEPTION_FAULT:
|
||||
default: crashData.cause = CrashCause::Fault; break;
|
||||
}
|
||||
|
||||
crashData.callstackLength = 0;
|
||||
crashData.faultAddress = reinterpret_cast<uint32_t>(panic_info->addr);
|
||||
|
||||
// g_panic_abort_details carries the actual assert()/abort() message when present; panic_info->reason
|
||||
// is ESP-IDF's generic description otherwise (e.g. "IllegalInstruction").
|
||||
const char* reason = (panic_info->exception == PANIC_EXCEPTION_ABORT && g_panic_abort_details != nullptr)
|
||||
? g_panic_abort_details
|
||||
: panic_info->reason;
|
||||
crashData.reason[0] = '\0';
|
||||
if (reason != nullptr) {
|
||||
strncpy(crashData.reason, reason, sizeof(crashData.reason) - 1);
|
||||
crashData.reason[sizeof(crashData.reason) - 1] = '\0';
|
||||
}
|
||||
|
||||
esp_backtrace_get_start(&frame.pc, &frame.sp, &frame.next_pc);
|
||||
crashData.callstack[0].pc = frame.pc;
|
||||
|
||||
@@ -355,12 +355,8 @@ static void stopAppFromToolbar(lv_event_t*) {
|
||||
// every not-yet-converted app's toolbar still relies on).
|
||||
AppInstanceId topmost = 0;
|
||||
check(app_manager_get_topmost_instance_id(&topmost) == ERROR_NONE);
|
||||
// Async, non-blocking - must NOT call app_manager_stop() directly here: that
|
||||
// bound-waits (thread_join) for the app's own thread to finish, which needs the LVGL
|
||||
// lock to clean up - but this callback runs ON the LVGL task, which would deadlock
|
||||
// against itself.
|
||||
AppEvent event { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(topmost, &event);
|
||||
|
||||
app_event_emit_close(topmost);
|
||||
}
|
||||
|
||||
// The on-screen keyboard widget itself, constructed during windowManagerScreenInit
|
||||
|
||||
@@ -54,12 +54,7 @@ std::vector<std::string> getModelNames() {
|
||||
|
||||
void onBackPressed(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
|
||||
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
|
||||
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
|
||||
// deadlock against itself.
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(ctx->appInstanceId, &closeEvent);
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void onAddGpsPressed(lv_event_t* event) {
|
||||
|
||||
@@ -51,13 +51,7 @@ void onButtonPressed(lv_event_t* e) {
|
||||
auto* btnCtx = static_cast<ButtonContext*>(lv_event_get_user_data(e));
|
||||
LOG_I(TAG, "Selected item at index %d", (int)btnCtx->index);
|
||||
btnCtx->ctx->result = btnCtx->index;
|
||||
// Async, non-blocking - just wakes this dialog's own thread. Must NOT call
|
||||
// app_manager_stop() here: that bound-waits (thread_join) for the dialog's thread to
|
||||
// finish, which needs the LVGL lock (window_manager_remove()) - but this callback is
|
||||
// running ON the LVGL task, which would deadlock against itself. The caller reaps this
|
||||
// instance via app_manager_stop() after it receives the APP_EVENT_RESULT instead.
|
||||
AppEvent event { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(btnCtx->ctx->appInstanceId, &event);
|
||||
app_event_emit_close(btnCtx->ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void createButton(Context* ctx, lv_obj_t* parent, const std::string& text, int32_t index) {
|
||||
|
||||
@@ -50,12 +50,7 @@ void onPressUninstall(lv_event_t* event) {
|
||||
|
||||
void onBackPressed(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
|
||||
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
|
||||
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
|
||||
// deadlock against itself.
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(ctx->appInstanceId, &closeEvent);
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void createWidgets(lv_obj_t* parent, void* userData) {
|
||||
|
||||
@@ -51,8 +51,7 @@ void refresh(Context* ctx);
|
||||
|
||||
void onBackPressed(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
AppEvent closeEvent {.type = APP_EVENT_CLOSE, .timestamp = 0, .result = {}};
|
||||
app_event_emit(ctx->appInstanceId, &closeEvent);
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void onAppPressed(lv_event_t* e) {
|
||||
|
||||
@@ -63,8 +63,7 @@ uint32_t showConfirmDialog(Context* ctx, const char* action) {
|
||||
|
||||
void onBackPressed(lv_event_t* e) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(e));
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(ctx->appInstanceId, &closeEvent);
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void onInstallPressed(lv_event_t* e) {
|
||||
|
||||
@@ -20,7 +20,9 @@ namespace tt::app::applist {
|
||||
|
||||
namespace {
|
||||
|
||||
uint32_t appListInstanceId = 0;
|
||||
struct Context {
|
||||
uint32_t appInstanceId;
|
||||
};
|
||||
|
||||
void onAppPressed(lv_event_t* e) {
|
||||
// Fire-and-forget top-level navigation, same as Launcher's own app-launch buttons.
|
||||
@@ -29,15 +31,9 @@ void onAppPressed(lv_event_t* e) {
|
||||
app_manager_start(manifest->id, &instanceId);
|
||||
}
|
||||
|
||||
void onBackPressed(lv_event_t*) {
|
||||
// The global toolbar nav callback (ToolbarConfig.nav_action_callback, set once in
|
||||
// Tactility.cpp) only knows how to stop old-model apps, so this new-model app overrides
|
||||
// its own toolbar's nav action to close itself instead. Async, non-blocking - must NOT
|
||||
// call app_manager_stop() directly here: that bound-waits (thread_join) for this app's
|
||||
// own thread to finish, which needs the LVGL lock (window_manager_remove()) - but this
|
||||
// callback runs ON the LVGL task, which would deadlock against itself.
|
||||
AppEvent event { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(appListInstanceId, &event);
|
||||
void onBackPressed(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void createAppWidget(const ::AppManifest* manifest, lv_obj_t* list) {
|
||||
@@ -54,9 +50,11 @@ void collectManifest(const ::AppManifest* manifest, void* context) {
|
||||
manifests->push_back(manifest);
|
||||
}
|
||||
|
||||
void createWidgets(lv_obj_t* parent, void*) {
|
||||
void createWidgets(lv_obj_t* parent, void* userData) {
|
||||
auto* ctx = static_cast<Context*>(userData);
|
||||
|
||||
auto* toolbar = lvgl_toolbar_create(parent, "Apps");
|
||||
lvgl_toolbar_set_nav_action(toolbar, LV_SYMBOL_CLOSE, onBackPressed, nullptr);
|
||||
lvgl_toolbar_set_nav_action(toolbar, LV_SYMBOL_CLOSE, onBackPressed, ctx);
|
||||
lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0);
|
||||
|
||||
lv_obj_t* list = lv_list_create(parent);
|
||||
@@ -83,7 +81,7 @@ void createWidgets(lv_obj_t* parent, void*) {
|
||||
|
||||
int32_t appMain(int argc, char* argv[]) {
|
||||
uint32_t appInstanceId = app_scheduler_current_app_id();
|
||||
appListInstanceId = appInstanceId;
|
||||
Context ctx { appInstanceId };
|
||||
|
||||
TaskEventGroup event_group {};
|
||||
task_event_group_construct(&event_group);
|
||||
@@ -91,7 +89,7 @@ int32_t appMain(int argc, char* argv[]) {
|
||||
AppEventSubscription sub {};
|
||||
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
|
||||
|
||||
WindowId window = window_manager_create(appInstanceId, createWidgets, nullptr);
|
||||
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
|
||||
|
||||
while (true) {
|
||||
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
|
||||
@@ -119,8 +117,9 @@ extern const ::AppManifest manifest = {
|
||||
.id = "tactility.applist",
|
||||
.name = "Apps",
|
||||
.category = APP_CATEGORY_SYSTEM,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) },
|
||||
.location = { .type = APP_LOCATION_MEMORY, .location = reinterpret_cast<void*>(appMain) },
|
||||
.flags = APP_MANIFEST_FLAG_HIDDEN,
|
||||
.stack = { .depth = 2400, .desired_memory_capability = 0 },
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -24,21 +24,18 @@ extern const ::AppManifest manifest;
|
||||
|
||||
namespace {
|
||||
|
||||
// Set by appMain() right before window_manager_create(), read by onBackPressed().
|
||||
uint32_t appSettingsInstanceId = 0;
|
||||
struct Context {
|
||||
uint32_t appInstanceId;
|
||||
};
|
||||
|
||||
void onAppPressed(lv_event_t* e) {
|
||||
const auto* target_manifest = static_cast<const ::AppManifest*>(lv_event_get_user_data(e));
|
||||
appdetails::start(target_manifest->id);
|
||||
}
|
||||
|
||||
void onBackPressed(lv_event_t*) {
|
||||
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
|
||||
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
|
||||
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
|
||||
// deadlock against itself.
|
||||
AppEvent event { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(appSettingsInstanceId, &event);
|
||||
void onBackPressed(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void createAppWidget(const ::AppManifest* target_manifest, lv_obj_t* list) {
|
||||
@@ -55,10 +52,12 @@ void collectManifest(const ::AppManifest* manifest, void* context) {
|
||||
manifests->push_back(manifest);
|
||||
}
|
||||
|
||||
void createWidgets(lv_obj_t* parent, void*) {
|
||||
void createWidgets(lv_obj_t* parent, void* userData) {
|
||||
auto* ctx = static_cast<Context*>(userData);
|
||||
|
||||
auto* toolbar = lvgl_toolbar_create(parent, "Installed Apps");
|
||||
// The global toolbar nav callback only knows how to stop old-model apps.
|
||||
lvgl_toolbar_set_nav_action(toolbar, LV_SYMBOL_CLOSE, onBackPressed, nullptr);
|
||||
lvgl_toolbar_set_nav_action(toolbar, LV_SYMBOL_CLOSE, onBackPressed, ctx);
|
||||
lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0);
|
||||
|
||||
lv_obj_t* list = lv_list_create(parent);
|
||||
@@ -92,7 +91,7 @@ void createWidgets(lv_obj_t* parent, void*) {
|
||||
|
||||
int32_t appMain(int argc, char* argv[]) {
|
||||
uint32_t appInstanceId = app_scheduler_current_app_id();
|
||||
appSettingsInstanceId = appInstanceId;
|
||||
Context ctx { appInstanceId };
|
||||
|
||||
TaskEventGroup event_group {};
|
||||
task_event_group_construct(&event_group);
|
||||
@@ -100,7 +99,7 @@ int32_t appMain(int argc, char* argv[]) {
|
||||
AppEventSubscription sub {};
|
||||
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
|
||||
|
||||
WindowId window = window_manager_create(appInstanceId, createWidgets, nullptr);
|
||||
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
|
||||
|
||||
bool shouldClose = false;
|
||||
while (!shouldClose) {
|
||||
@@ -132,7 +131,9 @@ extern const ::AppManifest manifest = {
|
||||
.id = "tactility.appsettings",
|
||||
.name = "Apps",
|
||||
.category = APP_CATEGORY_SETTINGS,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) }
|
||||
.location = { .type = APP_LOCATION_MEMORY, .location = reinterpret_cast<void*>(appMain) },
|
||||
.flags = 0,
|
||||
.stack = { .depth = 2400, .desired_memory_capability = 0 },
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -37,12 +37,7 @@ struct Context {
|
||||
|
||||
void onBackPressed(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
|
||||
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
|
||||
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
|
||||
// deadlock against itself.
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(ctx->appInstanceId, &closeEvent);
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void createWidgets(lv_obj_t* parent, void* userData) {
|
||||
|
||||
@@ -37,12 +37,7 @@ struct Context {
|
||||
|
||||
void onBackPressed(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
|
||||
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
|
||||
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
|
||||
// deadlock against itself.
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(ctx->appInstanceId, &closeEvent);
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void onInputEnabledSwitch(lv_event_t* event) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "tactility/memory.h"
|
||||
#include "tactility/system_event.h"
|
||||
|
||||
#include <tactility/check.h>
|
||||
@@ -274,16 +275,27 @@ void runBootSequence(TickType_t startTime) {
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!setupUsbBootMode()) {
|
||||
registerApps();
|
||||
waitForMinimalSplashDuration(startTime);
|
||||
startNextApp();
|
||||
if (setupUsbBootMode()) {
|
||||
// Stay open: the splash's "Return to OS" button is this app's only way to leave mass
|
||||
// storage mode, so it must not self-close here like the normal boot path does below.
|
||||
return;
|
||||
}
|
||||
|
||||
registerApps();
|
||||
waitForMinimalSplashDuration(startTime);
|
||||
startNextApp();
|
||||
|
||||
if (sdCardMissing) {
|
||||
// Stay open: the error screen's "Reboot" button is this app's only way to leave here.
|
||||
return;
|
||||
}
|
||||
|
||||
// This event will likely block as other systems are initialized
|
||||
// e.g. Wi-Fi reads AP configs from SD card
|
||||
LOG_I(TAG, "Publish event");
|
||||
system_event_emit(KERNEL_EVENT_BOOT_COMPLETED, nullptr, 0);
|
||||
|
||||
app_event_emit_close(app_scheduler_current_app_id());
|
||||
}
|
||||
|
||||
int32_t appMain(int argc, char* argv[]) {
|
||||
@@ -337,8 +349,9 @@ extern const ::AppManifest manifest = {
|
||||
.id = "tactility.boot",
|
||||
.name = "Boot",
|
||||
.category = APP_CATEGORY_SYSTEM,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) },
|
||||
.location = { .type = APP_LOCATION_MEMORY, .location = reinterpret_cast<void*>(appMain) },
|
||||
.flags = APP_MANIFEST_FLAG_HIDDEN,
|
||||
.stack = { .depth = 4096, .desired_memory_capability = MEMORY_CAPABILITY_INTERNAL }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -133,8 +133,7 @@ void onBtEvent(Context* ctx, const BtEvent& event) {
|
||||
|
||||
void onBackPressed(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(ctx->appInstanceId, &closeEvent);
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void createWidgets(lv_obj_t* parent, void* userData) {
|
||||
|
||||
@@ -20,12 +20,7 @@ namespace tt::app::btmanage {
|
||||
|
||||
static void onBackPressed(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
|
||||
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
|
||||
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
|
||||
// deadlock against itself.
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(ctx->appInstanceId, &closeEvent);
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}
|
||||
|
||||
static void onEnableSwitchChanged(lv_event_t* event) {
|
||||
|
||||
@@ -93,12 +93,7 @@ void onPressForget(lv_event_t* event) {
|
||||
|
||||
void onBackPressed(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
|
||||
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
|
||||
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
|
||||
// deadlock against itself.
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(ctx->appInstanceId, &closeEvent);
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void onToggleAutoConnect(lv_event_t* event) {
|
||||
|
||||
@@ -148,12 +148,7 @@ void ChatView::createChannelPanel(lv_obj_t* parent) {
|
||||
|
||||
void ChatView::onBackPressed(lv_event_t* e) {
|
||||
auto* self = static_cast<ChatView*>(lv_event_get_user_data(e));
|
||||
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
|
||||
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
|
||||
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
|
||||
// deadlock against itself.
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(self->app->appInstanceId, &closeEvent);
|
||||
app_event_emit_close(self->app->appInstanceId);
|
||||
}
|
||||
|
||||
void ChatView::init(lv_obj_t* parent) {
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
#ifdef ESP_PLATFORM
|
||||
|
||||
#include "Tactility/PanicHandler.h"
|
||||
|
||||
|
||||
#include <Tactility/app/crashdiagnostics/QrHelpers.h>
|
||||
#include <Tactility/app/crashdiagnostics/QrUrl.h>
|
||||
#include <Tactility/app/launcher/Launcher.h>
|
||||
#include <Tactility/file/File.h>
|
||||
#include <Tactility/lvgl/Statusbar.h>
|
||||
|
||||
#include <app/event.h>
|
||||
@@ -17,8 +21,19 @@
|
||||
#include <tactility/check.h>
|
||||
#include <tactility/drivers/pointer.h>
|
||||
#include <tactility/log.h>
|
||||
#include <tactility/paths.h>
|
||||
|
||||
#if CONFIG_IDF_TARGET_ARCH_XTENSA
|
||||
#include <esp_cpu_utils.h>
|
||||
#else
|
||||
#include <esp_cpu.h>
|
||||
#endif
|
||||
|
||||
#include <sdkconfig.h>
|
||||
|
||||
#include <iomanip>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
|
||||
namespace tt::app::crashdiagnostics {
|
||||
|
||||
@@ -41,16 +56,70 @@ struct Context {
|
||||
};
|
||||
|
||||
|
||||
const char* crashCauseToString(CrashCause cause) {
|
||||
switch (cause) {
|
||||
case CrashCause::Debug: return "Debug";
|
||||
case CrashCause::WatchdogInterrupt: return "Watchdog (interrupt)";
|
||||
case CrashCause::WatchdogTask: return "Watchdog (task)";
|
||||
case CrashCause::Abort: return "Abort";
|
||||
case CrashCause::Fault: return "Fault";
|
||||
case CrashCause::Unknown:
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
std::string formatCrashData(const CrashData& crashData) {
|
||||
std::stringstream stream;
|
||||
|
||||
stream << "Cause: " << crashCauseToString(crashData.cause) << "\n";
|
||||
|
||||
stream << "Reason: ";
|
||||
if (crashData.reason[0] != '\0') {
|
||||
stream << crashData.reason;
|
||||
} else {
|
||||
stream << "unknown";
|
||||
}
|
||||
stream << "\n";
|
||||
|
||||
stream << "Fault address: " << std::hex << std::setw(8) << std::setfill('0') << crashData.faultAddress << std::dec << "\n";
|
||||
|
||||
stream << "Callstack" << (crashData.callstackCorrupted ? " (corrupted)" : "") << ":";
|
||||
if (crashData.callstackLength > 0) {
|
||||
stream << "\n";
|
||||
for (uint8_t i = 0; i < crashData.callstackLength; i++) {
|
||||
#if CONFIG_IDF_TARGET_ARCH_XTENSA
|
||||
uint32_t pc = esp_cpu_process_stack_pc(crashData.callstack[i].pc);
|
||||
#else
|
||||
uint32_t pc = crashData.callstack[i].pc; // No processing needed on RISC-V
|
||||
#endif
|
||||
stream << std::hex << std::setw(8) << std::setfill('0') << pc << std::dec << " ";
|
||||
}
|
||||
} else {
|
||||
stream << " empty" << "\n";
|
||||
}
|
||||
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
// Best-effort: crash.txt is a convenience for offline inspection, not required for the app to work.
|
||||
void writeCrashLogFile(const CrashData& crashData) {
|
||||
char root[128];
|
||||
if (paths_get_data_path(root, sizeof(root)) != ERROR_NONE) {
|
||||
LOG_E(TAG, "Failed to resolve data path for crash.txt");
|
||||
return;
|
||||
}
|
||||
|
||||
std::string path = std::string(root) + "/crash.txt";
|
||||
file::FileMutexGuard guard(path);
|
||||
if (!file::writeString(path, formatCrashData(crashData))) {
|
||||
LOG_E(TAG, "Failed to write %s", path.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void onContinuePressed(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
ctx->continuePressed = true;
|
||||
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
|
||||
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
|
||||
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
|
||||
// deadlock against itself. launcher::start() is deferred to appMain(), after this app's
|
||||
// own thread has finished cleaning up.
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(ctx->appInstanceId, &closeEvent);
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void createWidgets(lv_obj_t* parent, void* userData) {
|
||||
@@ -72,7 +141,9 @@ void createWidgets(lv_obj_t* parent, void* userData) {
|
||||
}
|
||||
lv_obj_align(bottom_label, LV_ALIGN_BOTTOM_MID, 0, -2);
|
||||
|
||||
std::string url = getUrlFromCrashData();
|
||||
const auto& crash_data = getRtcCrashData();
|
||||
|
||||
std::string url = getUrlFromCrashData(crash_data);
|
||||
LOG_I(TAG, "%s", url.c_str());
|
||||
size_t url_length = url.length();
|
||||
|
||||
@@ -156,6 +227,8 @@ int32_t appMain(int argc, char* argv[]) {
|
||||
Context ctx {};
|
||||
ctx.appInstanceId = appInstanceId;
|
||||
|
||||
writeCrashLogFile(getRtcCrashData());
|
||||
|
||||
TaskEventGroup event_group {};
|
||||
task_event_group_construct(&event_group);
|
||||
|
||||
@@ -207,7 +280,7 @@ extern const ::AppManifest manifest = {
|
||||
.id = "tactility.crashdiagnostics",
|
||||
.name = "Crash Diagnostics",
|
||||
.category = APP_CATEGORY_SYSTEM,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) },
|
||||
.location = { .type = APP_LOCATION_MEMORY, .location = reinterpret_cast<void*>(appMain) },
|
||||
.flags = APP_MANIFEST_FLAG_HIDDEN,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#ifdef ESP_PLATFORM
|
||||
|
||||
#include <Tactility/app/crashdiagnostics/QrUrl.h>
|
||||
#include <Tactility/PanicHandler.h>
|
||||
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
@@ -14,8 +13,7 @@
|
||||
|
||||
#include <sdkconfig.h>
|
||||
|
||||
std::string getUrlFromCrashData() {
|
||||
auto crash_data = getRtcCrashData();
|
||||
std::string getUrlFromCrashData(const CrashData& crash_data) {
|
||||
std::vector<uint32_t> stack_buffer(crash_data.callstackLength * 2);
|
||||
for (int i = 0; i < crash_data.callstackLength; ++i) {
|
||||
const CallstackFrame&frame = crash_data.callstack[i];
|
||||
|
||||
@@ -46,12 +46,7 @@ void updateViewState(Context* ctx);
|
||||
|
||||
void onBackPressed(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
|
||||
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
|
||||
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
|
||||
// deadlock against itself.
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(ctx->appInstanceId, &closeEvent);
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void onEnableSwitchChanged(lv_event_t* event) {
|
||||
|
||||
@@ -61,12 +61,7 @@ Device* getBacklightDevice() {
|
||||
|
||||
void onBackPressed(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
|
||||
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
|
||||
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
|
||||
// deadlock against itself.
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(ctx->appInstanceId, &closeEvent);
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void onBacklightSliderEvent(lv_event_t* event) {
|
||||
|
||||
@@ -376,12 +376,7 @@ void View::createDirEntryWidget(lv_obj_t* list, dirent& dir_entry) {
|
||||
}
|
||||
|
||||
void View::onBackPressed() {
|
||||
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
|
||||
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
|
||||
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
|
||||
// deadlock against itself.
|
||||
AppEvent event { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(appInstanceId, &event);
|
||||
app_event_emit_close(appInstanceId);
|
||||
}
|
||||
|
||||
void View::onNavigateUpPressed() {
|
||||
|
||||
@@ -62,8 +62,7 @@ int32_t appMain(int argc, char* argv[]) {
|
||||
// app_manager_stop() after it receives the APP_EVENT_RESULT instead.
|
||||
lastPath = path;
|
||||
ctx.result = 0;
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(appInstanceId, &closeEvent);
|
||||
app_event_emit_close(appInstanceId);
|
||||
});
|
||||
|
||||
TaskEventGroup event_group {};
|
||||
|
||||
@@ -41,12 +41,7 @@ static void onNavigateUpPressedCallback(lv_event_t* event) {
|
||||
|
||||
void View::onBackPressedCallback(lv_event_t* event) {
|
||||
auto* view = static_cast<View*>(lv_event_get_user_data(event));
|
||||
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
|
||||
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
|
||||
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
|
||||
// deadlock against itself.
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(view->appInstanceId, &closeEvent);
|
||||
app_event_emit_close(view->appInstanceId);
|
||||
}
|
||||
|
||||
void View::onTapFile(const std::string& path, const std::string& filename) {
|
||||
|
||||
@@ -64,12 +64,7 @@ void createWidgets(lv_obj_t* parent, void* userData);
|
||||
|
||||
void onBackPressed(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
|
||||
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
|
||||
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
|
||||
// deadlock against itself.
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(ctx->appInstanceId, &closeEvent);
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void onAddGpsPressed(lv_event_t* event) {
|
||||
|
||||
@@ -45,12 +45,7 @@ void onModeChanged(lv_event_t* e) {
|
||||
|
||||
void onBackPressed(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
|
||||
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
|
||||
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
|
||||
// deadlock against itself.
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(ctx->appInstanceId, &closeEvent);
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void createWidgets(lv_obj_t* parent, void* userData) {
|
||||
|
||||
@@ -284,12 +284,7 @@ void selectBus(Context* ctx, int32_t selected) {
|
||||
|
||||
void onBackPressed(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
|
||||
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
|
||||
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
|
||||
// deadlock against itself.
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(ctx->appInstanceId, &closeEvent);
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void onSelectBus(lv_event_t* event) {
|
||||
|
||||
@@ -32,12 +32,7 @@ struct Context {
|
||||
|
||||
void onBackPressed(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
|
||||
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
|
||||
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
|
||||
// deadlock against itself.
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(ctx->appInstanceId, &closeEvent);
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void createWidgets(lv_obj_t* parent, void* userData) {
|
||||
|
||||
@@ -59,10 +59,7 @@ void onButtonPressed(lv_event_t* e) {
|
||||
LOG_I(TAG, "Cancel pressed");
|
||||
btnCtx->ctx->result = 1;
|
||||
}
|
||||
// Async, non-blocking - see AlertDialog.cpp's onButtonPressed() for why this must not
|
||||
// call app_manager_stop() directly (would deadlock against the LVGL lock).
|
||||
AppEvent event { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(btnCtx->ctx->appInstanceId, &event);
|
||||
app_event_emit_close(btnCtx->ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void createButton(Context* ctx, lv_obj_t* parent, const std::string& text, lv_obj_t* textarea) {
|
||||
|
||||
@@ -59,12 +59,7 @@ struct Context {
|
||||
|
||||
void onBackPressed(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
|
||||
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
|
||||
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
|
||||
// deadlock against itself.
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(ctx->appInstanceId, &closeEvent);
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void onBacklightSwitch(lv_event_t* e) {
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
#include "tactility/drivers/pointer.h"
|
||||
|
||||
|
||||
#include <app/event.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/manifest.h>
|
||||
@@ -17,8 +14,10 @@
|
||||
|
||||
#include <tactility/check.h>
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/drivers/pointer.h>
|
||||
#include <tactility/drivers/power_supply.h>
|
||||
#include <tactility/log.h>
|
||||
#include <tactility/memory.h>
|
||||
|
||||
#include <Tactility/app/setup/Setup.h>
|
||||
#include <Tactility/settings/BootSettings.h>
|
||||
@@ -273,8 +272,10 @@ extern const ::AppManifest manifest = {
|
||||
.id = "tactility.launcher",
|
||||
.name = "Launcher",
|
||||
.category = APP_CATEGORY_SYSTEM,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) },
|
||||
.location = { .type = APP_LOCATION_MEMORY, .location = reinterpret_cast<void*>(appMain) },
|
||||
.flags = APP_MANIFEST_FLAG_HIDDEN,
|
||||
// No file IO, so callstack can be in external RAM
|
||||
.stack = { .depth = 3072 , .desired_memory_capability = MEMORY_CAPABILITY_EXTERNAL }
|
||||
};
|
||||
|
||||
// Kept for Tactility/Private/Tactility/app/launcher/Launcher.h's existing declaration (still
|
||||
|
||||
@@ -99,12 +99,7 @@ void onLanguageSet(lv_event_t* event) {
|
||||
|
||||
void onBackPressed(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
|
||||
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
|
||||
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
|
||||
// deadlock against itself.
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(ctx->appInstanceId, &closeEvent);
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void createWidgets(lv_obj_t* parent, void* userData) {
|
||||
|
||||
@@ -104,12 +104,7 @@ void updateUi(Context* ctx) {
|
||||
|
||||
void onBackPressed(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
|
||||
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
|
||||
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
|
||||
// deadlock against itself.
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(ctx->appInstanceId, &closeEvent);
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void onPowerEnabledChanged(lv_event_t* event) {
|
||||
|
||||
@@ -99,12 +99,7 @@ void onYesPressed(lv_event_t* /*event*/) {
|
||||
|
||||
void onNoPressed(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
|
||||
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
|
||||
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
|
||||
// deadlock against itself.
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(ctx->appInstanceId, &closeEvent);
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void createWidgets(lv_obj_t* parent, void* userData) {
|
||||
|
||||
@@ -66,12 +66,7 @@ void updateScreenshotMode(Context* ctx) {
|
||||
|
||||
void onBackPressed(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
|
||||
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
|
||||
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
|
||||
// deadlock against itself.
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(ctx->appInstanceId, &closeEvent);
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void onStartPressed(lv_event_t* event) {
|
||||
|
||||
@@ -47,13 +47,7 @@ void onItemSelected(lv_event_t* e) {
|
||||
auto* itemCtx = static_cast<ItemContext*>(lv_event_get_user_data(e));
|
||||
LOG_I(TAG, "Selected item at index %d", (int)itemCtx->index);
|
||||
itemCtx->ctx->result = itemCtx->index;
|
||||
// Async, non-blocking - just wakes this dialog's own thread. Must NOT call
|
||||
// app_manager_stop() here: that bound-waits (thread_join) for the dialog's thread to
|
||||
// finish, which needs the LVGL lock (window_manager_remove()) - but this callback is
|
||||
// running ON the LVGL task, which would deadlock against itself. The caller reaps this
|
||||
// instance via app_manager_stop() after it receives the APP_EVENT_RESULT instead.
|
||||
AppEvent event { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(itemCtx->ctx->appInstanceId, &event);
|
||||
app_event_emit_close(itemCtx->ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void createChoiceItem(Context* ctx, lv_obj_t* list, const std::string& title, int32_t index) {
|
||||
@@ -67,8 +61,7 @@ void createChoiceItem(Context* ctx, lv_obj_t* list, const std::string& title, in
|
||||
// mirrors the original's 0-items (error) and 1-item (auto-select) shortcuts.
|
||||
void closeWithResult(Context* ctx, int32_t result) {
|
||||
ctx->result = result;
|
||||
AppEvent event { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(ctx->appInstanceId, &event);
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void createWidgets(lv_obj_t* parent, void* userData) {
|
||||
|
||||
@@ -20,7 +20,9 @@ namespace tt::app::settings {
|
||||
|
||||
namespace {
|
||||
|
||||
uint32_t settingsInstanceId = 0;
|
||||
struct Context {
|
||||
uint32_t appInstanceId;
|
||||
};
|
||||
|
||||
void onAppPressed(lv_event_t* e) {
|
||||
// Fire-and-forget top-level navigation, same as AppList's own app-launch buttons.
|
||||
@@ -29,13 +31,9 @@ void onAppPressed(lv_event_t* e) {
|
||||
app_manager_start(manifest->id, &instanceId);
|
||||
}
|
||||
|
||||
void onBackPressed(lv_event_t*) {
|
||||
// The global toolbar nav callback only knows how to stop old-model apps, so this
|
||||
// new-model app overrides its own toolbar's nav action to close itself instead. Async,
|
||||
// non-blocking - see AppList.cpp's onBackPressed() for why this must not call
|
||||
// app_manager_stop() directly (would deadlock against the LVGL lock).
|
||||
AppEvent event { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(settingsInstanceId, &event);
|
||||
void onBackPressed(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void createWidget(const ::AppManifest* manifest, lv_obj_t* list) {
|
||||
@@ -53,12 +51,14 @@ void collectManifest(const ::AppManifest* manifest, void* context) {
|
||||
manifests->push_back(manifest);
|
||||
}
|
||||
|
||||
void createWidgets(lv_obj_t* parent, void*) {
|
||||
void createWidgets(lv_obj_t* parent, void* userData) {
|
||||
auto* ctx = static_cast<Context*>(userData);
|
||||
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
|
||||
|
||||
auto* toolbar = lvgl_toolbar_create(parent, "Settings");
|
||||
lvgl_toolbar_set_nav_action(toolbar, LV_SYMBOL_CLOSE, onBackPressed, nullptr);
|
||||
lvgl_toolbar_set_nav_action(toolbar, LV_SYMBOL_CLOSE, onBackPressed, ctx);
|
||||
|
||||
auto* list = lv_list_create(parent);
|
||||
lv_obj_set_width(list, LV_PCT(100));
|
||||
@@ -79,7 +79,7 @@ void createWidgets(lv_obj_t* parent, void*) {
|
||||
|
||||
int32_t appMain(int argc, char* argv[]) {
|
||||
uint32_t appInstanceId = app_scheduler_current_app_id();
|
||||
settingsInstanceId = appInstanceId;
|
||||
Context ctx { appInstanceId };
|
||||
|
||||
TaskEventGroup event_group {};
|
||||
task_event_group_construct(&event_group);
|
||||
@@ -87,7 +87,7 @@ int32_t appMain(int argc, char* argv[]) {
|
||||
AppEventSubscription sub {};
|
||||
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
|
||||
|
||||
WindowId window = window_manager_create(appInstanceId, createWidgets, nullptr);
|
||||
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
|
||||
|
||||
while (true) {
|
||||
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
|
||||
@@ -115,8 +115,9 @@ extern const ::AppManifest manifest = {
|
||||
.id = "tactility.settings",
|
||||
.name = "Settings",
|
||||
.category = APP_CATEGORY_SYSTEM,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) },
|
||||
.location = { .type = APP_LOCATION_MEMORY, .location = reinterpret_cast<void*>(appMain) },
|
||||
.flags = APP_MANIFEST_FLAG_HIDDEN,
|
||||
.stack = { .depth = 2400, .desired_memory_capability = 0 },
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -159,11 +159,7 @@ void onContinueClicked(lv_event_t* event) {
|
||||
break;
|
||||
case Phase::Done: {
|
||||
markCompleted();
|
||||
// Async, non-blocking - must NOT call app_manager_stop() directly here: this
|
||||
// callback runs ON the LVGL task, and app-lifecycle transitions must happen on this
|
||||
// app's own thread (woken via app_event_poll()), which closes by returning.
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(ctx->appInstanceId, &closeEvent);
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,12 +305,7 @@ void updateTasks(Context* ctx) {
|
||||
|
||||
void onBackPressed(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
|
||||
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
|
||||
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
|
||||
// deadlock against itself.
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(ctx->appInstanceId, &closeEvent);
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void createWidgets(lv_obj_t* parent, void* userData) {
|
||||
|
||||
@@ -34,12 +34,7 @@ struct Context {
|
||||
|
||||
void onBackPressed(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
|
||||
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
|
||||
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
|
||||
// deadlock against itself.
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(ctx->appInstanceId, &closeEvent);
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void onTimeFormatChanged(lv_event_t* event) {
|
||||
|
||||
@@ -109,8 +109,7 @@ void createListItem(Context* ctx, lv_obj_t* list, const std::string& title, size
|
||||
lastCode = entry.code;
|
||||
|
||||
ctx->result = 0; // Ok
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(ctx->appInstanceId, &closeEvent);
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}, LV_EVENT_SHORT_CLICKED, buttonCtx);
|
||||
}
|
||||
|
||||
@@ -186,12 +185,7 @@ void updateList(Context* ctx) {
|
||||
|
||||
void onBackPressed(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
|
||||
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
|
||||
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
|
||||
// deadlock against itself.
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(ctx->appInstanceId, &closeEvent);
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void createWidgets(lv_obj_t* parent, void* userData) {
|
||||
|
||||
@@ -174,12 +174,7 @@ void onPress(lv_event_t* event) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Async, non-blocking - must NOT call app_manager_stop() directly here: this callback runs
|
||||
// ON the LVGL task, and app-lifecycle transitions must happen on this app's own thread
|
||||
// (woken up via app_event_poll() below), which closes by returning. The result (Ok/Error)
|
||||
// is reported by appMain() itself when it returns, based on ctx.calibrationApplied.
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(ctx->appInstanceId, &closeEvent);
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void createWidgets(lv_obj_t* parent, void* userData) {
|
||||
|
||||
@@ -72,12 +72,7 @@ struct Context {
|
||||
|
||||
void onBackPressed(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
|
||||
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
|
||||
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
|
||||
// deadlock against itself.
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(ctx->appInstanceId, &closeEvent);
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void applyLive(Context* ctx) {
|
||||
|
||||
@@ -27,12 +27,7 @@ struct Context {
|
||||
|
||||
void onBackPressed(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
|
||||
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
|
||||
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
|
||||
// deadlock against itself.
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(ctx->appInstanceId, &closeEvent);
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void onRebootMassStorageSdmmc(lv_event_t* event) {
|
||||
|
||||
@@ -54,12 +54,7 @@ void createWidgets(lv_obj_t* parent, void* userData);
|
||||
|
||||
void onBackPressed(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
|
||||
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
|
||||
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
|
||||
// deadlock against itself.
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(ctx->appInstanceId, &closeEvent);
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void onWifiModeChanged(lv_event_t* e) {
|
||||
|
||||
@@ -47,11 +47,7 @@ void updateViews(Context* ctx);
|
||||
|
||||
void onBackPressed(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
|
||||
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
|
||||
// but this callback runs ON the LVGL task, which would deadlock against itself.
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(ctx->appInstanceId, &closeEvent);
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void onPressForget(lv_event_t* event) {
|
||||
|
||||
@@ -60,11 +60,7 @@ void setLoading(Context* ctx, bool loading);
|
||||
|
||||
void onBackPressed(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
|
||||
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
|
||||
// but this callback runs ON the LVGL task, which would deadlock against itself.
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(ctx->appInstanceId, &closeEvent);
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void onWifiEvent(Context* ctx, WifiEvent event) {
|
||||
@@ -89,8 +85,7 @@ void onWifiEvent(Context* ctx, WifiEvent event) {
|
||||
lvgl_unlock();
|
||||
|
||||
if (shouldClose) {
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(ctx->appInstanceId, &closeEvent);
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <Tactility/app/wifimanage/View.h>
|
||||
#include <Tactility/app/wifimanage/WifiManagePrivate.h>
|
||||
#include <Tactility/lvgl/Style.h>
|
||||
#include <Tactility/lvgl/Toolbar.h>
|
||||
#include <Tactility/service/wifi/Wifi.h>
|
||||
#include <Tactility/service/wifi/WifiSettings.h>
|
||||
#include <Tactility/Tactility.h>
|
||||
@@ -23,12 +22,7 @@ constexpr auto* TAG = "WifiManageView";
|
||||
|
||||
static void onBackPressed(lv_event_t* event) {
|
||||
auto* appInstanceId = static_cast<uint32_t*>(lv_event_get_user_data(event));
|
||||
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
|
||||
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
|
||||
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
|
||||
// deadlock against itself.
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(*appInstanceId, &closeEvent);
|
||||
app_event_emit_close(*appInstanceId);
|
||||
}
|
||||
|
||||
static uint8_t mapRssiToPercentage(int rssi) {
|
||||
@@ -40,7 +34,7 @@ static uint8_t mapRssiToPercentage(int rssi) {
|
||||
}
|
||||
|
||||
auto percentage = (float)(90U - abs_rssi) / 60.f * 100.f;
|
||||
return (uint8_t)percentage;
|
||||
return static_cast<uint8_t>(percentage);
|
||||
}
|
||||
|
||||
static void onEnableSwitchChanged(lv_event_t* event) {
|
||||
|
||||
Reference in New Issue
Block a user