Refactored events (#621)

Event handling:

- Added unified event handling for application, system, and Wi‑Fi events.
- Updated all apps to reflect event handling changes

Wi-Fi:

- Refactored event handling from listener interface to task & event group.
- Added direct Wi‑Fi radio controls and improved event subscriptions.
- Wi‑Fi screens now refresh asynchronously and handle unavailable devices more gracefully.
- Enabled Wi‑Fi by default on in dts files, but radio on/off is still done by code. The main reason was reliable event subscription and consistent devicetree states.
- Improved Wi‑Fi shutdown cleanup and radio-state handling.

Other:
- Renamed the Kernel Display app to Display.
This commit is contained in:
Ken Van Hoeylandt
2026-08-25 17:44:20 +02:00
committed by GitHub
parent db48dfe812
commit ab75d2022d
121 changed files with 2416 additions and 1381 deletions
+1 -3
View File
@@ -141,7 +141,6 @@ namespace app {
namespace boot { extern const ::AppManifest manifest; }
namespace development { extern const ::AppManifest manifest; }
namespace display { extern const ::AppManifest manifest; }
namespace kerneldisplay { extern const ::AppManifest manifest; }
namespace files { extern const ::AppManifest manifest; }
namespace fileselection { extern const ::AppManifest manifest; }
namespace gpssettings { extern const ::AppManifest manifest; }
@@ -206,7 +205,7 @@ static void registerInternalApps() {
app_manager_add(&app::audiosettings::manifest);
}
if (device_exists_of_type(&DISPLAY_TYPE)) {
app_manager_add(&app::kerneldisplay::manifest);
app_manager_add(&app::display::manifest);
}
app_manager_add(&app::files::manifest);
app_manager_add(&app::fileselection::manifest);
@@ -519,7 +518,6 @@ void run(Module* const dtsModules[], const DtsDevice dtsDevices[]) {
.on_start = onLvglStarted,
.on_stop = onLvglStopped,
.task_priority = THREAD_PRIORITY_HIGHER,
// TODO: Remove Wi-Fi driver callback mechanism and use subscribe/await from wifi app to be able to reduce callstack
.task_stack_size = 9120,
#ifdef ESP_PLATFORM
.task_affinity = getCpuAffinityConfiguration().graphics
+24 -16
View File
@@ -5,11 +5,13 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
#include <lvgl/icons/shared.h>
#include <lvgl/widgets/toolbar.h>
#include <tactility/check.h>
#include <tactility/drivers/uart_controller.h>
#include <tactility/log.h>
@@ -199,36 +201,42 @@ void createWidgets(lv_obj_t* parent, void* userData) {
// endregion
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
Context ctx {};
ctx.appInstanceId = appInstanceId;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false;
while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
case APP_EVENT_RESULT:
app_manager_stop(event.result.launch_id);
break;
default:
break;
while (app_event_poll(&sub, &event) == ERROR_NONE) {
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
case APP_EVENT_RESULT:
app_manager_stop(event.result.launch_id);
break;
default:
break;
}
if (shouldClose) break;
}
}
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0;
}
@@ -3,9 +3,11 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <tactility/log.h>
#include <lvgl.h>
@@ -97,29 +99,37 @@ void createWidgets(lv_obj_t* parent, void* userData) {
}
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
Context ctx { appInstanceId };
ctx.argc = argc;
ctx.argv = argv;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
while (true) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
bool shouldClose = false;
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
if (event.type == APP_EVENT_CLOSE) {
break;
while (app_event_poll(&sub, &event) == ERROR_NONE) {
if (event.type == APP_EVENT_CLOSE) {
shouldClose = true;
break;
}
}
if (shouldClose) break;
}
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return ctx.result;
}
+29 -21
View File
@@ -2,6 +2,7 @@
#include <app/manager.h>
#include <app/manifest.h>
#include <app/install.h>
#include <app/scheduler.h>
#include <format>
@@ -15,6 +16,7 @@
#include <Tactility/file/File.h>
#include <Tactility/lvgl/Style.h>
#include <tactility/check.h>
#include <tactility/log.h>
constexpr auto* TAG = "AppDetails";
@@ -104,7 +106,8 @@ void createWidgets(lv_obj_t* parent, void* userData) {
}
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
Context ctx {};
ctx.appInstanceId = appInstanceId;
@@ -114,38 +117,43 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
return 0;
}
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false;
while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
case APP_EVENT_RESULT:
if (event.result.launch_id == ctx.pendingUninstallDialogId) {
if (event.result.result == 0) { // 0 = Yes
app_uninstall(ctx.targetManifest.id);
shouldClose = true;
while (app_event_poll(&sub, &event) == ERROR_NONE) {
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
case APP_EVENT_RESULT:
if (event.result.launch_id == ctx.pendingUninstallDialogId) {
if (event.result.result == 0) { // 0 = Yes
app_uninstall(ctx.targetManifest.id);
shouldClose = true;
}
app_manager_stop(event.result.launch_id);
}
app_manager_stop(event.result.launch_id);
}
break;
default:
break;
break;
default:
break;
}
if (shouldClose) break;
}
}
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0;
}
+24 -16
View File
@@ -10,9 +10,11 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <tactility/log.h>
#include <lvgl/lvgl.h>
@@ -49,7 +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 = {} };
AppEvent closeEvent {.type = APP_EVENT_CLOSE, .timestamp = 0, .result = {}};
app_event_emit(ctx->appInstanceId, &closeEvent);
}
@@ -197,33 +199,39 @@ void destroyWidgets(void* userData) {
ctx->refreshButton = nullptr;
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
Context ctx;
ctx.appInstanceId = appInstanceId;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create_ext(appInstanceId, createWidgets, destroyWidgets, &ctx);
bool shouldClose = false;
while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
while (app_event_poll(&sub, &event) == ERROR_NONE) {
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
}
if (shouldClose) break;
}
}
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0;
}
@@ -234,7 +242,7 @@ extern const ::AppManifest manifest = {
.id = "tactility.apphub",
.name = "App Hub",
.category = APP_CATEGORY_SYSTEM,
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) }
.location = {APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain)}
};
} // namespace
} // namespace tt::app::apphub
@@ -11,12 +11,14 @@
#include <app/metadata.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
#include <lvgl/lvgl.h>
#include <lvgl/widgets/toolbar.h>
#include <tactility/check.h>
#include <tactility/log.h>
#include <atomic>
@@ -211,7 +213,8 @@ void createWidgets(lv_obj_t* parent, void* userData) {
updateViews(ctx);
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
// argv layout: [0]=appId, [1]=appVersionName, [2]=appVersionCode, [3]=appName,
// [4]=appDescription, [5]=targetSdk, [6]=file, [7..argc)=targetPlatforms.
@@ -230,41 +233,46 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
}
}
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false;
while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
case APP_EVENT_RESULT: {
bool confirmed = event.result.result == CONFIRMATION_BUTTON_INDEX;
if (event.result.launch_id == ctx.installDialogId && confirmed) {
installApp(&ctx);
} else if (event.result.launch_id == ctx.uninstallDialogId && confirmed) {
uninstallApp(&ctx);
} else if (event.result.launch_id == ctx.updateDialogId && confirmed) {
updateApp(&ctx);
while (app_event_poll(&sub, &event) == ERROR_NONE) {
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
case APP_EVENT_RESULT: {
bool confirmed = event.result.result == CONFIRMATION_BUTTON_INDEX;
if (event.result.launch_id == ctx.installDialogId && confirmed) {
installApp(&ctx);
} else if (event.result.launch_id == ctx.uninstallDialogId && confirmed) {
uninstallApp(&ctx);
} else if (event.result.launch_id == ctx.updateDialogId && confirmed) {
updateApp(&ctx);
}
app_manager_stop(event.result.launch_id);
break;
}
app_manager_stop(event.result.launch_id);
break;
default:
break;
}
default:
break;
if (shouldClose) break;
}
}
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0;
}
+20 -9
View File
@@ -1,9 +1,12 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <lvgl.h>
#include <algorithm>
#include <cstring>
@@ -78,27 +81,35 @@ void createWidgets(lv_obj_t* parent, void*) {
}
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
appListInstanceId = appInstanceId;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, nullptr);
while (true) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
bool shouldClose = false;
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
if (event.type == APP_EVENT_CLOSE) {
break;
while (app_event_poll(&sub, &event) == ERROR_NONE) {
if (event.type == APP_EVENT_CLOSE) {
shouldClose = true;
break;
}
}
if (shouldClose) break;
}
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0;
}
@@ -6,9 +6,12 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <lvgl/widgets/toolbar.h>
#include <lvgl.h>
#include <algorithm>
@@ -87,32 +90,38 @@ void createWidgets(lv_obj_t* parent, void*) {
}
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
appSettingsInstanceId = appInstanceId;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, nullptr);
bool shouldClose = false;
while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
while (app_event_poll(&sub, &event) == ERROR_NONE) {
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
}
if (shouldClose) break;
}
}
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0;
}
@@ -7,11 +7,13 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
#include <lvgl.h>
#include <lvgl/widgets/toolbar.h>
#include <tactility/check.h>
#include <tactility/log.h>
namespace tt::app::apwebserver {
@@ -121,29 +123,34 @@ void createWidgets(lv_obj_t* parent, void* userData) {
ctx->webServerEnabledChanged = true;
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
Context ctx {};
ctx.appInstanceId = appInstanceId;
ctx.wsSettings = settings::webserver::loadOrGetDefault();
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false;
while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
while (app_event_poll(&sub, &event) == ERROR_NONE) {
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
}
if (shouldClose) break;
}
}
@@ -167,7 +174,8 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
});
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0;
}
@@ -5,9 +5,12 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <lvgl/lvgl.h>
#include <lvgl/widgets/sliderbox.h>
#include <lvgl/widgets/toolbar.h>
@@ -211,28 +214,33 @@ void createWidgets(lv_obj_t* parent, void* userData) {
});
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
Context ctx {};
ctx.appInstanceId = appInstanceId;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false;
while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
while (app_event_poll(&sub, &event) == ERROR_NONE) {
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
}
if (shouldClose) break;
}
}
@@ -242,7 +250,8 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
}
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0;
}
+19 -9
View File
@@ -1,5 +1,6 @@
#include "tactility/system_event.h"
#include <tactility/check.h>
#include <tactility/delay.h>
#include <tactility/drivers/backlight.h>
#include <tactility/drivers/display.h>
@@ -9,6 +10,7 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
@@ -284,7 +286,8 @@ void runBootSequence(TickType_t startTime) {
system_event_emit(KERNEL_EVENT_BOOT_COMPLETED, nullptr, 0);
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
bootAppInstanceId = appInstanceId;
const auto start_time = get_ticks();
@@ -292,9 +295,11 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
isUsbBootSplash = hal::usb::isUsbBootMode();
sdCardMissing = false;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
bootWindowId = window_manager_create(appInstanceId, createSplashWidgets, nullptr);
@@ -304,19 +309,24 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
// startNextApp() above is what triggers that, via app-module's "save the previously active
// app" policy, unless sdCardMissing halted before it.
while (true) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
bool shouldClose = false;
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
if (event.type == APP_EVENT_CLOSE) {
break;
while (app_event_poll(&sub, &event) == ERROR_NONE) {
if (event.type == APP_EVENT_CLOSE) {
shouldClose = true;
break;
}
}
if (shouldClose) break;
}
if (bootWindowId != 0) {
window_manager_remove(bootWindowId);
}
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0;
}
+21 -13
View File
@@ -8,9 +8,11 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <tactility/log.h>
namespace tt::app::btmanage {
@@ -199,7 +201,8 @@ void createWidgets(lv_obj_t* parent, void* userData) {
ctx->unlock();
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
Context ctx;
ctx.appInstanceId = appInstanceId;
ctx.bindings = (Bindings) {
@@ -220,9 +223,11 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
ctx.state.updateScanResults();
ctx.state.updatePairedPeers();
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
@@ -243,16 +248,18 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
bool shouldClose = false;
while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
while (app_event_poll(&sub, &event) == ERROR_NONE) {
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
}
if (shouldClose) break;
}
}
@@ -270,7 +277,8 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
}
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0;
}
@@ -11,9 +11,11 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <tactility/device.h>
#include <tactility/drivers/bluetooth.h>
#include <tactility/log.h>
@@ -178,7 +180,8 @@ void createWidgets(lv_obj_t* parent, void* userData) {
updateViews(ctx);
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
Context ctx {};
ctx.appInstanceId = appInstanceId;
@@ -199,38 +202,42 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
device_put(btDevice);
}
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false;
while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
case APP_EVENT_RESULT:
if (event.result.result == 0) { // 0 = Yes
if (isCurrentlyConnected(&ctx)) {
if (ctx.profileId == BT_PROFILE_HID_HOST) {
bluetooth::hidHostDisconnect();
} else {
bluetooth::disconnect(ctx.addr, ctx.profileId);
}
}
bluetooth::unpair(ctx.addr);
while (app_event_poll(&sub, &event) == ERROR_NONE) {
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
}
app_manager_stop(event.result.launch_id);
break;
default:
break;
break;
case APP_EVENT_RESULT:
if (event.result.result == 0) { // 0 = Yes
if (isCurrentlyConnected(&ctx)) {
if (ctx.profileId == BT_PROFILE_HID_HOST) {
bluetooth::hidHostDisconnect();
} else {
bluetooth::disconnect(ctx.addr, ctx.profileId);
}
}
bluetooth::unpair(ctx.addr);
shouldClose = true;
}
app_manager_stop(event.result.launch_id);
break;
default:
break;
}
if (shouldClose) break;
}
}
@@ -240,7 +247,8 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
}
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0;
}
+29 -22
View File
@@ -10,9 +10,11 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <tactility/log.h>
#include <lvgl/lvgl.h>
@@ -167,7 +169,8 @@ void switchChannel(Context* ctx, const std::string& chatChannel) {
namespace {
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
Context ctx {};
ctx.appInstanceId = appInstanceId;
ctx.isFirstLaunch = !settingsFileExists();
@@ -178,40 +181,44 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
}
enableEspNow(&ctx);
ctx.receiveSubscription = service::espnow::subscribeReceiver(
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
auto espnow_subscription = service::espnow::subscribeReceiver(
[&ctx](const esp_now_recv_info_t* receiveInfo, const uint8_t* data, int length) {
onReceive(&ctx, receiveInfo, data, length);
}
);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false;
while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
while (app_event_poll(&sub, &event) == ERROR_NONE) {
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
}
if (shouldClose) break;
}
}
window_manager_remove(window);
app_event_unsubscribe(&sub);
service::espnow::unsubscribeReceiver(ctx.receiveSubscription);
service::espnow::unsubscribeReceiver(espnow_subscription);
disableEspNow(&ctx);
window_manager_remove(window);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0;
}
@@ -8,11 +8,13 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
#include <lvgl.h>
#include <qrcode.h>
#include <tactility/check.h>
#include <tactility/drivers/pointer.h>
#include <tactility/log.h>
@@ -149,35 +151,41 @@ void createWidgets(lv_obj_t* parent, void* userData) {
}
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
Context ctx {};
ctx.appInstanceId = appInstanceId;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
if (!ctx.hasFatalError) {
bool shouldClose = false;
while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
while (app_event_poll(&sub, &event) == ERROR_NONE) {
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
}
if (shouldClose) break;
}
}
}
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
bool continuePressed = ctx.continuePressed;
@@ -10,9 +10,11 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <tactility/log.h>
#include <lvgl.h>
@@ -162,7 +164,8 @@ void createWidgets(lv_obj_t* parent, void* userData) {
updateViewState(ctx);
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
Context ctx {};
ctx.appInstanceId = appInstanceId;
ctx.service = service::development::findService();
@@ -174,9 +177,11 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
return 0;
}
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
@@ -197,16 +202,18 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
bool shouldClose = false;
while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
while (app_event_poll(&sub, &event) == ERROR_NONE) {
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
}
if (shouldClose) break;
}
}
@@ -216,7 +223,8 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
lvgl_unlock();
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0;
}
@@ -1,4 +1,5 @@
#include <lvgl/lvgl.h>
#include <tactility/check.h>
#include <tactility/device.h>
#include <tactility/drivers/backlight.h>
#include <tactility/drivers/display.h>
@@ -12,8 +13,8 @@
#include <Tactility/settings/DisplaySettings.h>
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
@@ -25,11 +26,11 @@
#include <sdkconfig.h>
#endif
namespace tt::app::kerneldisplay {
namespace tt::app::display {
extern const ::AppManifest manifest;
constexpr auto* TAG = "KernelDisplay";
constexpr auto* TAG = "Display";
namespace {
@@ -304,34 +305,40 @@ void persistIfUpdated(Context& ctx) {
}
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
Context ctx {};
ctx.appInstanceId = appInstanceId;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false;
while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
switch (event.type) {
case APP_EVENT_CLOSE:
persistIfUpdated(ctx);
shouldClose = true;
break;
default:
break;
while (app_event_poll(&sub, &event) == ERROR_NONE) {
switch (event.type) {
case APP_EVENT_CLOSE:
persistIfUpdated(ctx);
shouldClose = true;
break;
default:
break;
}
if (shouldClose) break;
}
}
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0;
}
@@ -345,4 +352,4 @@ extern const ::AppManifest manifest = {
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) }
};
} // namespace tt::app::kerneldisplay
} // namespace tt::app::display
+26 -17
View File
@@ -4,9 +4,12 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <memory>
namespace tt::app::files {
@@ -25,39 +28,45 @@ void createWidgets(lv_obj_t* parent, void* userData) {
ctx->view->init(ctx->appInstanceId, parent);
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
auto state = std::make_shared<State>();
View view(state);
CreateContext createContext { &view, appInstanceId };
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, &createContext);
bool shouldClose = false;
while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
case APP_EVENT_RESULT:
view.onResult(event.result.launch_id, event.result.result);
app_manager_stop(event.result.launch_id);
break;
default:
break;
while (app_event_poll(&sub, &event) == ERROR_NONE) {
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
case APP_EVENT_RESULT:
view.onResult(event.result.launch_id, event.result.result);
app_manager_stop(event.result.launch_id);
break;
default:
break;
}
if (shouldClose) break;
}
}
view.deinit();
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0;
}
@@ -5,9 +5,12 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <memory>
#include <string>
@@ -43,7 +46,8 @@ void createWidgets(lv_obj_t* parent, void* userData) {
ctx->view->init(parent, ctx->mode);
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
// argv layout: [0]="existing" or "existing_or_new".
Context ctx {};
@@ -62,24 +66,31 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
app_event_emit(appInstanceId, &closeEvent);
});
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
while (true) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
bool shouldClose = false;
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
if (event.type == APP_EVENT_CLOSE) {
break;
while (app_event_poll(&sub, &event) == ERROR_NONE) {
if (event.type == APP_EVENT_CLOSE) {
shouldClose = true;
break;
}
}
if (shouldClose) break;
}
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return ctx.result;
}
@@ -9,9 +9,11 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <tactility/device.h>
#include <tactility/time.h>
@@ -257,7 +259,8 @@ void createWidgets(lv_obj_t* parent, void* userData) {
updateDeviceStates(ctx);
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
Context ctx {};
ctx.appInstanceId = appInstanceId;
@@ -267,51 +270,56 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
updateDeviceStates(&ctx);
});
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
ctx.timer->start();
bool shouldClose = false;
while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
case APP_EVENT_RESULT:
if (ctx.hasPendingDelete) {
ctx.hasPendingDelete = false;
if (event.result.result == 0) { // 0 = Yes
lvgl_lock();
std::erase_if(ctx.deviceRows, [&ctx](const DeviceRow& row) {
return row.device == ctx.pendingDeleteDevice;
});
lvgl_unlock();
while (app_event_poll(&sub, &event) == ERROR_NONE) {
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
case APP_EVENT_RESULT:
if (ctx.hasPendingDelete) {
ctx.hasPendingDelete = false;
if (event.result.result == 0) { // 0 = Yes
lvgl_lock();
std::erase_if(ctx.deviceRows, [&ctx](const DeviceRow& row) {
return row.device == ctx.pendingDeleteDevice;
});
lvgl_unlock();
gps_settings_remove_configuration_at(ctx.pendingDeleteIndex);
ctx.pendingDeleteDevice = nullptr;
gps_settings_remove_configuration_at(ctx.pendingDeleteIndex);
ctx.pendingDeleteDevice = nullptr;
lvgl_lock();
rebuildDeviceList(&ctx);
lvgl_unlock();
lvgl_lock();
rebuildDeviceList(&ctx);
lvgl_unlock();
}
}
}
app_manager_stop(event.result.launch_id);
break;
default:
break;
app_manager_stop(event.result.launch_id);
break;
default:
break;
}
if (shouldClose) break;
}
}
ctx.timer->stop();
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0;
}
@@ -2,12 +2,14 @@
#include <lvgl.h>
#include <tactility/check.h>
#include <tactility/device.h>
#include <tactility/drivers/grove.h>
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
@@ -89,33 +91,39 @@ void createWidgets(lv_obj_t* parent, void* userData) {
}
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
Context ctx {};
ctx.appInstanceId = appInstanceId;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false;
while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
while (app_event_poll(&sub, &event) == ERROR_NONE) {
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
}
if (shouldClose) break;
}
}
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0;
}
+22 -14
View File
@@ -9,9 +9,11 @@
#include <app/manager.h>
#include <app/manifest.h>
#include <app/paths.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <tactility/drivers/i2c_controller.h>
#include <tactility/log.h>
#include <tactility/preferences.h>
@@ -384,34 +386,40 @@ void stopScanningIfRunning(Context* ctx) {
}
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
Context ctx;
ctx.appInstanceId = appInstanceId;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false;
while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
switch (event.type) {
case APP_EVENT_CLOSE:
stopScanningIfRunning(&ctx);
shouldClose = true;
break;
default:
break;
while (app_event_poll(&sub, &event) == ERROR_NONE) {
switch (event.type) {
case APP_EVENT_CLOSE:
stopScanningIfRunning(&ctx);
shouldClose = true;
break;
default:
break;
}
if (shouldClose) break;
}
}
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0;
}
@@ -7,6 +7,7 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
@@ -82,36 +83,42 @@ void createWidgets(lv_obj_t* parent, void* userData) {
}
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
check(argc > 0, "Parameters not set");
Context ctx {};
ctx.appInstanceId = appInstanceId;
ctx.filePath = argv[0];
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false;
while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
while (app_event_poll(&sub, &event) == ERROR_NONE) {
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
}
if (shouldClose) break;
}
}
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0;
}
@@ -3,10 +3,12 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
#include <lvgl/widgets/toolbar.h>
#include <tactility/check.h>
#include <tactility/log.h>
#include <lvgl.h>
@@ -106,29 +108,37 @@ void createWidgets(lv_obj_t* parent, void* userData) {
createButton(ctx, button_wrapper, "Cancel", nullptr);
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
Context ctx { appInstanceId };
ctx.argc = argc;
ctx.argv = argv;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
while (true) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
bool shouldClose = false;
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
if (event.type == APP_EVENT_CLOSE) {
break;
while (app_event_poll(&sub, &event) == ERROR_NONE) {
if (event.type == APP_EVENT_CLOSE) {
shouldClose = true;
break;
}
}
if (shouldClose) break;
}
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return ctx.result;
}
@@ -7,9 +7,11 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <tactility/device.h>
#include <tactility/drivers/backlight.h>
@@ -205,34 +207,40 @@ void persistIfUpdated(Context& ctx) {
}
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
Context ctx {};
ctx.appInstanceId = appInstanceId;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false;
while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
switch (event.type) {
case APP_EVENT_CLOSE:
persistIfUpdated(ctx);
shouldClose = true;
break;
default:
break;
while (app_event_poll(&sub, &event) == ERROR_NONE) {
switch (event.type) {
case APP_EVENT_CLOSE:
persistIfUpdated(ctx);
shouldClose = true;
break;
default:
break;
}
if (shouldClose) break;
}
}
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0;
}
+19 -9
View File
@@ -4,6 +4,7 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <cstring>
@@ -14,6 +15,7 @@
#include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <tactility/device.h>
#include <tactility/drivers/power_supply.h>
#include <tactility/log.h>
@@ -231,29 +233,37 @@ void runAutoStart() {
}
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
runAutoStart();
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, nullptr);
// The launcher is meant to stay resident (it's the home screen) - it only gives up its
// thread when app-module's scheduler asks it to (e.g. another new-model app is started).
while (true) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
bool shouldClose = false;
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
if (event.type == APP_EVENT_CLOSE) {
break;
while (app_event_poll(&sub, &event) == ERROR_NONE) {
if (event.type == APP_EVENT_CLOSE) {
shouldClose = true;
break;
}
}
if (shouldClose) break;
}
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0;
}
@@ -9,9 +9,12 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <lvgl/widgets/toolbar.h>
#include <lvgl.h>
@@ -141,33 +144,39 @@ void createWidgets(lv_obj_t* parent, void* userData) {
lv_obj_add_event_cb(ctx->languageDropdown, onLanguageSet, LV_EVENT_VALUE_CHANGED, ctx);
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
Context ctx;
ctx.appInstanceId = appInstanceId;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false;
while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
while (app_event_poll(&sub, &event) == ERROR_NONE) {
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
}
if (shouldClose) break;
}
}
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0;
}
+41 -33
View File
@@ -5,12 +5,14 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
#include <lvgl.h>
#include <lvgl/lvgl.h>
#include <lvgl/widgets/toolbar.h>
#include <tactility/check.h>
#include <tactility/log.h>
namespace tt::app::notes {
@@ -182,7 +184,8 @@ void createWidgets(lv_obj_t* parent, void* userData) {
}
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
Context ctx {};
ctx.appInstanceId = appInstanceId;
@@ -190,51 +193,56 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
ctx.filePath = argv[0];
}
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false;
while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
case APP_EVENT_RESULT:
LOG_I(TAG, "Result for launch id %u", event.result.launch_id);
if (event.result.launch_id == ctx.loadFileLaunchId) {
ctx.loadFileLaunchId = 0;
if (event.result.result == 0 /* Ok */) {
auto path = fileselection::getLastPath();
if (!path.empty()) {
openFile(&ctx, path);
while (app_event_poll(&sub, &event) == ERROR_NONE) {
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
case APP_EVENT_RESULT:
LOG_I(TAG, "Result for launch id %u", event.result.launch_id);
if (event.result.launch_id == ctx.loadFileLaunchId) {
ctx.loadFileLaunchId = 0;
if (event.result.result == 0 /* Ok */) {
auto path = fileselection::getLastPath();
if (!path.empty()) {
openFile(&ctx, path);
}
}
} else if (event.result.launch_id == ctx.saveFileLaunchId) {
ctx.saveFileLaunchId = 0;
if (event.result.result == 0 /* Ok */) {
auto path = fileselection::getLastPath();
// Must re-open file, because the UI was cleared after opening the dialog.
if (!path.empty() && saveFile(&ctx, path)) {
openFile(&ctx, path);
}
}
}
} else if (event.result.launch_id == ctx.saveFileLaunchId) {
ctx.saveFileLaunchId = 0;
if (event.result.result == 0 /* Ok */) {
auto path = fileselection::getLastPath();
// Must re-open file, because the UI was cleared after opening the dialog.
if (!path.empty() && saveFile(&ctx, path)) {
openFile(&ctx, path);
}
}
}
app_manager_stop(event.result.launch_id);
break;
default:
break;
app_manager_stop(event.result.launch_id);
break;
default:
break;
}
if (shouldClose) break;
}
}
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0;
}
+21 -13
View File
@@ -4,9 +4,11 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <tactility/device.h>
#include <tactility/drivers/power_supply.h>
#include <tactility/time.h>
@@ -229,7 +231,8 @@ void createWidgets(lv_obj_t* parent, void* userData) {
}
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
Context ctx {};
ctx.appInstanceId = appInstanceId;
@@ -240,31 +243,36 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
updateUi(&ctx);
});
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
ctx.timer->start();
bool shouldClose = false;
while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
while (app_event_poll(&sub, &event) == ERROR_NONE) {
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
}
if (shouldClose) break;
}
}
ctx.timer->stop();
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0;
}
+15 -7
View File
@@ -4,11 +4,13 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
#include <lvgl.h>
#include <lvgl/fonts.h>
#include <tactility/check.h>
#include <tactility/device.h>
#include <tactility/drivers/power_supply.h>
@@ -132,22 +134,25 @@ void createWidgets(lv_obj_t* parent, void* userData) {
lv_obj_add_event_cb(no_button, onNoPressed, LV_EVENT_SHORT_CLICKED, ctx);
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
Context ctx {};
ctx.appInstanceId = appInstanceId;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false;
while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
while (app_event_poll(&sub, &event) == ERROR_NONE) {
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
@@ -155,10 +160,13 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
default:
break;
}
if (shouldClose) break;
}
}
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0;
}
+21 -13
View File
@@ -12,9 +12,11 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <tactility/log.h>
#include <lvgl.h>
@@ -240,7 +242,8 @@ void createWidgets(lv_obj_t* parent, void* userData) {
}
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
Context ctx {};
ctx.appInstanceId = appInstanceId;
ctx.updateTimer = std::make_unique<Timer>(Timer::Type::Periodic, 500 / portTICK_PERIOD_MS, [&ctx] {
@@ -250,24 +253,28 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
}
});
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false;
while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
while (app_event_poll(&sub, &event) == ERROR_NONE) {
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
}
if (shouldClose) break;
}
}
@@ -276,7 +283,8 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
}
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0;
}
@@ -3,9 +3,11 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <tactility/log.h>
#include <lvgl.h>
@@ -99,29 +101,37 @@ void createWidgets(lv_obj_t* parent, void* userData) {
}
}
int32_t appMain(AppInstanceId appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
AppInstanceId appInstanceId = app_scheduler_current_app_id();
Context ctx { appInstanceId };
ctx.argc = argc;
ctx.argv = argv;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
while (true) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
bool shouldClose = false;
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
if (event.type == APP_EVENT_CLOSE) {
break;
while (app_event_poll(&sub, &event) == ERROR_NONE) {
if (event.type == APP_EVENT_CLOSE) {
shouldClose = true;
break;
}
}
if (shouldClose) break;
}
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return ctx.result;
}
+18 -9
View File
@@ -1,6 +1,7 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
@@ -76,27 +77,35 @@ void createWidgets(lv_obj_t* parent, void*) {
}
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
settingsInstanceId = appInstanceId;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, nullptr);
while (true) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
bool shouldClose = false;
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
if (event.type == APP_EVENT_CLOSE) {
break;
while (app_event_poll(&sub, &event) == ERROR_NONE) {
if (event.type == APP_EVENT_CLOSE) {
shouldClose = true;
break;
}
}
if (shouldClose) break;
}
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0;
}
+29 -21
View File
@@ -11,9 +11,11 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <tactility/log.h>
#include <lvgl/fonts.h>
@@ -159,7 +161,7 @@ void onContinueClicked(lv_event_t* event) {
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_await()), which closes by returning.
// 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);
break;
@@ -203,7 +205,8 @@ void createWidgets(lv_obj_t* parent, void* userData) {
renderCurrent(ctx);
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
Context ctx {};
ctx.appInstanceId = appInstanceId;
ctx.steps = {
@@ -229,36 +232,41 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
}
};
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false;
while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
case APP_EVENT_RESULT:
if (event.result.launch_id == ctx.pendingStepDialogId) {
ctx.pendingStepDialogId = 0;
advanceTo(&ctx, ctx.stepIndex + 1);
}
app_manager_stop(event.result.launch_id);
break;
default:
break;
while (app_event_poll(&sub, &event) == ERROR_NONE) {
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
case APP_EVENT_RESULT:
if (event.result.launch_id == ctx.pendingStepDialogId) {
ctx.pendingStepDialogId = 0;
advanceTo(&ctx, ctx.stepIndex + 1);
}
app_manager_stop(event.result.launch_id);
break;
default:
break;
}
if (shouldClose) break;
}
}
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0;
}
+21 -13
View File
@@ -1,3 +1,4 @@
#include <tactility/check.h>
#include "tactility/time.h"
#include <Tactility/DeprecatedPaths.h>
@@ -8,6 +9,7 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
@@ -399,7 +401,8 @@ void createWidgets(lv_obj_t* parent, void* userData) {
updateTasks(ctx);
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
Context ctx {};
ctx.appInstanceId = appInstanceId;
@@ -417,9 +420,11 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
lvgl_unlock();
});
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
ctx.memoryTimer->start(); // Memory: every 10s
@@ -427,23 +432,26 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
bool shouldClose = false;
while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
while (app_event_poll(&sub, &event) == ERROR_NONE) {
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
}
if (shouldClose) break;
}
}
ctx.memoryTimer->stop();
ctx.tasksTimer->stop();
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0;
}
@@ -6,9 +6,11 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <tactility/log.h>
#include <lvgl/lvgl.h>
@@ -153,48 +155,54 @@ void createWidgets(lv_obj_t* parent, void* userData) {
lv_label_set_text(ctx->timeZoneLabel, timeZoneName.c_str());
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
Context ctx {};
ctx.appInstanceId = appInstanceId;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false;
while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
case APP_EVENT_RESULT:
if (event.result.launch_id == ctx.pendingTimeZoneDialogId) {
ctx.pendingTimeZoneDialogId = 0;
if (event.result.result == 0 /* Ok */) {
const auto name = timezone::getLastName();
LOG_I(TAG, "Result name=%s code=%s", name.c_str(), timezone::getLastCode().c_str());
if (!name.empty()) {
lvgl_lock();
lv_label_set_text(ctx.timeZoneLabel, name.c_str());
lvgl_unlock();
while (app_event_poll(&sub, &event) == ERROR_NONE) {
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
case APP_EVENT_RESULT:
if (event.result.launch_id == ctx.pendingTimeZoneDialogId) {
ctx.pendingTimeZoneDialogId = 0;
if (event.result.result == 0 /* Ok */) {
const auto name = timezone::getLastName();
LOG_I(TAG, "Result name=%s code=%s", name.c_str(), timezone::getLastCode().c_str());
if (!name.empty()) {
lvgl_lock();
lv_label_set_text(ctx.timeZoneLabel, name.c_str());
lvgl_unlock();
}
}
}
}
app_manager_stop(event.result.launch_id);
break;
default:
break;
app_manager_stop(event.result.launch_id);
break;
default:
break;
}
if (shouldClose) break;
}
}
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0;
}
+19 -9
View File
@@ -9,9 +9,11 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <tactility/log.h>
#include <lvgl/lvgl.h>
@@ -229,7 +231,8 @@ void createWidgets(lv_obj_t* parent, void* userData) {
ctx->listWidget = list;
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
// argv layout: [0]="1"/"0" (saveTimeZone).
Context ctx;
@@ -240,26 +243,33 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
updateList(&ctx);
});
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
ctx.updateTimer->start();
while (true) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
bool shouldClose = false;
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
if (event.type == APP_EVENT_CLOSE) {
break;
while (app_event_poll(&sub, &event) == ERROR_NONE) {
if (event.type == APP_EVENT_CLOSE) {
shouldClose = true;
break;
}
}
if (shouldClose) break;
}
ctx.updateTimer->stop();
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return ctx.result;
}
@@ -8,9 +8,11 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <tactility/log.h>
#include <lvgl/lvgl.h>
#include <lvgl/devices/pointer.h>
@@ -174,7 +176,7 @@ void onPress(lv_event_t* event) {
// 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_await() below), which closes by returning. The result (Ok/Error)
// (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);
@@ -221,7 +223,8 @@ void createWidgets(lv_obj_t* parent, void* userData) {
updateUi(ctx);
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
Context ctx {};
ctx.appInstanceId = appInstanceId;
@@ -233,29 +236,34 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
}
lvgl_unlock();
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false;
while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
while (app_event_poll(&sub, &event) == ERROR_NONE) {
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
}
if (shouldClose) break;
}
}
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
// finishCalibration() already applied a new calibration on success. On cancel/failure,
// restore whatever calibration was on disk before the block above cleared it.
@@ -5,6 +5,7 @@
#include <lvgl/lvgl.h>
#include <lvgl/widgets/toolbar.h>
#include <tactility/check.h>
#include <tactility/drivers/trackball.h>
#include <Tactility/Assets.h>
@@ -14,6 +15,7 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
@@ -268,34 +270,40 @@ void persistIfUpdated(Context& ctx) {
}
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
Context ctx {};
ctx.appInstanceId = appInstanceId;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false;
while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
switch (event.type) {
case APP_EVENT_CLOSE:
persistIfUpdated(ctx);
shouldClose = true;
break;
default:
break;
while (app_event_poll(&sub, &event) == ERROR_NONE) {
switch (event.type) {
case APP_EVENT_CLOSE:
persistIfUpdated(ctx);
shouldClose = true;
break;
default:
break;
}
if (shouldClose) break;
}
}
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0;
}
@@ -3,9 +3,12 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <lvgl.h>
#include <lvgl/widgets/toolbar.h>
@@ -81,33 +84,39 @@ void createWidgets(lv_obj_t* parent, void* userData) {
}
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
Context ctx {};
ctx.appInstanceId = appInstanceId;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false;
while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
while (app_event_poll(&sub, &event) == ERROR_NONE) {
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
}
if (shouldClose) break;
}
}
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0;
}
@@ -8,9 +8,11 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <tactility/log.h>
#include <lvgl.h>
@@ -354,7 +356,8 @@ void createWidgets(lv_obj_t* parent, void* userData) {
"AP mode uses the password configured above.");
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
Context ctx {};
ctx.appInstanceId = appInstanceId;
ctx.wsSettings = settings::webserver::loadOrGetDefault();
@@ -362,24 +365,28 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
ctx.wsSettings.webServerEnabled = service::webserver::isWebServerEnabled();
ctx.originalSettings = ctx.wsSettings;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false;
while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
while (app_event_poll(&sub, &event) == ERROR_NONE) {
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
}
if (shouldClose) break;
}
}
@@ -422,7 +429,8 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
}
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0;
}
@@ -6,12 +6,15 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
#include <lvgl/lvgl.h>
#include <lvgl/widgets/toolbar.h>
#include <tactility/check.h>
#include <tactility/device.h>
#include <tactility/log.h>
namespace tt::app::wifiapsettings {
@@ -29,9 +32,14 @@ struct Context {
lv_obj_t* busySpinner = nullptr;
lv_obj_t* connectButton = nullptr;
lv_obj_t* disconnectButton = nullptr;
lv_obj_t* autoConnectSwitch = nullptr;
lv_obj_t* forgetButton = nullptr;
lv_obj_t* autoConnectWrapper = nullptr;
uint32_t forgetDialogId = 0;
PubSub<service::wifi::WifiEvent>::SubscriptionHandle wifiSubscription = nullptr;
// Set once in appMain() before subscribing, left null if this device has no WiFi driver
Device* wifiDevice = nullptr;
WifiEventSubscription wifiEventSub {};
};
@@ -41,8 +49,7 @@ 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.
// 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);
}
@@ -104,13 +111,34 @@ void updateBusySpinner(Context* ctx) {
}
}
// Touches the filesystem (service::wifi::settings::load()) - callers must not run this on the
// LVGL task (see updateViews()'s callers).
void updateAutoConnectSection(Context* ctx) {
service::wifi::settings::WifiApSettings settings;
if (service::wifi::settings::load(ctx->ssid.c_str(), settings)) {
if (settings.autoConnect) {
lv_obj_add_state(ctx->autoConnectSwitch, LV_STATE_CHECKED);
} else {
lv_obj_remove_state(ctx->autoConnectSwitch, LV_STATE_CHECKED);
}
lv_obj_remove_flag(ctx->forgetButton, LV_OBJ_FLAG_HIDDEN);
lv_obj_remove_flag(ctx->autoConnectWrapper, LV_OBJ_FLAG_HIDDEN);
} else {
LOG_W(TAG, "No settings found");
lv_obj_add_flag(ctx->forgetButton, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(ctx->autoConnectWrapper, LV_OBJ_FLAG_HIDDEN);
}
}
// Runs on whichever thread calls it, not necessarily the LVGL task
void updateViews(Context* ctx) {
if (ctx->connectButton == nullptr) {
// Buried (e.g. the forget confirmation dialog opened on top) - see destroyWidgets().
// Buried (e.g. the forget confirmation dialog opened on top)
return;
}
updateConnectButton(ctx);
updateBusySpinner(ctx);
updateAutoConnectSection(ctx);
}
void requestViewUpdate(Context* ctx) {
@@ -119,13 +147,14 @@ void requestViewUpdate(Context* ctx) {
lvgl_unlock();
}
// Runs with the LVGL lock already held, possibly on another app's thread - see
// WindowDestroyWidgetsFn's warnings. Must stay lock-free: only nulls pointers.
void destroyWidgets(void* userData) {
auto* ctx = static_cast<Context*>(userData);
ctx->busySpinner = nullptr;
ctx->connectButton = nullptr;
ctx->disconnectButton = nullptr;
ctx->autoConnectSwitch = nullptr;
ctx->forgetButton = nullptr;
ctx->autoConnectWrapper = nullptr;
}
void createWidgets(lv_obj_t* parent, void* userData) {
@@ -162,101 +191,127 @@ void createWidgets(lv_obj_t* parent, void* userData) {
// Forget
auto* forget_button = lv_button_create(wrapper);
lv_obj_set_width(forget_button, LV_PCT(100));
lv_obj_add_event_cb(forget_button, onPressForget, LV_EVENT_SHORT_CLICKED, ctx);
auto* forget_button_label = lv_label_create(forget_button);
ctx->forgetButton = lv_button_create(wrapper);
lv_obj_set_width(ctx->forgetButton, LV_PCT(100));
lv_obj_add_event_cb(ctx->forgetButton, onPressForget, LV_EVENT_SHORT_CLICKED, ctx);
auto* forget_button_label = lv_label_create(ctx->forgetButton);
lv_obj_align(forget_button_label, LV_ALIGN_CENTER, 0, 0);
lv_label_set_text(forget_button_label, "Forget");
lv_obj_add_flag(ctx->forgetButton, LV_OBJ_FLAG_HIDDEN); // shown by updateAutoConnectSection()
// Auto-connect
auto* auto_connect_wrapper = lv_obj_create(wrapper);
lv_obj_set_size(auto_connect_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
lvgl::obj_set_style_bg_invisible(auto_connect_wrapper);
lv_obj_set_style_pad_all(auto_connect_wrapper, 0, LV_STATE_DEFAULT);
lv_obj_set_style_border_width(auto_connect_wrapper, 0, LV_STATE_DEFAULT);
ctx->autoConnectWrapper = lv_obj_create(wrapper);
lv_obj_set_size(ctx->autoConnectWrapper, LV_PCT(100), LV_SIZE_CONTENT);
lvgl::obj_set_style_bg_invisible(ctx->autoConnectWrapper);
lv_obj_set_style_pad_all(ctx->autoConnectWrapper, 0, LV_STATE_DEFAULT);
lv_obj_set_style_border_width(ctx->autoConnectWrapper, 0, LV_STATE_DEFAULT);
lv_obj_add_flag(ctx->autoConnectWrapper, LV_OBJ_FLAG_HIDDEN); // shown by updateAutoConnectSection()
auto* auto_connect_label = lv_label_create(auto_connect_wrapper);
auto* auto_connect_label = lv_label_create(ctx->autoConnectWrapper);
lv_label_set_text(auto_connect_label, "Auto-connect");
lv_obj_align(auto_connect_label, LV_ALIGN_LEFT_MID, 0, 0);
auto* auto_connect_switch = lv_switch_create(auto_connect_wrapper);
lv_obj_add_event_cb(auto_connect_switch, onToggleAutoConnect, LV_EVENT_VALUE_CHANGED, ctx);
lv_obj_align(auto_connect_switch, LV_ALIGN_RIGHT_MID, 0, 0);
service::wifi::settings::WifiApSettings settings;
if (service::wifi::settings::load(ctx->ssid.c_str(), settings)) {
if (settings.autoConnect) {
lv_obj_add_state(auto_connect_switch, LV_STATE_CHECKED);
} else {
lv_obj_remove_state(auto_connect_switch, LV_STATE_CHECKED);
}
} else {
LOG_W(TAG, "No settings found");
lv_obj_add_flag(forget_button, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(auto_connect_wrapper, LV_OBJ_FLAG_HIDDEN);
}
updateViews(ctx);
ctx->autoConnectSwitch = lv_switch_create(ctx->autoConnectWrapper);
lv_obj_add_event_cb(ctx->autoConnectSwitch, onToggleAutoConnect, LV_EVENT_VALUE_CHANGED, ctx);
lv_obj_align(ctx->autoConnectSwitch, LV_ALIGN_RIGHT_MID, 0, 0);
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
Context ctx {};
ctx.appInstanceId = appInstanceId;
ctx.ssid = (argc > 0) ? argv[0] : std::string();
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
// Subscribed once here, not in createWidgets(): that callback re-runs on every
// burial/resurface rebuild, and re-subscribing there would leak the previous subscription
// (and its captured ctx pointer) every time, only the last of which shutdown ever cleans up.
ctx.wifiSubscription = service::wifi::getPubsub()->subscribe([&ctx](auto) {
requestViewUpdate(&ctx);
});
AppEventSubscription sub {};
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
Device* wifi_device = nullptr;
if (device_get_first_by_type(&WIFI_TYPE, &wifi_device) == ERROR_NONE) {
if (wifi_event_subscribe(wifi_device, &ctx.wifiEventSub, &event_group) == ERROR_NONE) {
ctx.wifiDevice = wifi_device;
} else {
LOG_W(TAG, "Failed to subscribe to WiFi events");
device_put(wifi_device);
}
} else {
LOG_W(TAG, "No WiFi device found");
}
WindowId window = window_manager_create_ext(appInstanceId, createWidgets, destroyWidgets, &ctx);
// The file-I/O-touching part of the view (updateAutoConnectSection()'s settings::load())
// runs here, on this app's own task, not the LVGL task createWidgets()
requestViewUpdate(&ctx);
bool shouldClose = false;
while (!shouldClose) {
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
}
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
case APP_EVENT_RESULT:
if (event.result.launch_id == ctx.forgetDialogId && event.result.result == 0) { // 0 = Yes
if (!service::wifi::settings::remove(ctx.ssid.c_str())) {
LOG_E(TAG, "Failed to remove SSID");
} else {
LOG_I(TAG, "Removed SSID");
if (
service::wifi::getRadioState() == service::wifi::RadioState::ConnectionActive &&
service::wifi::getConnectionTarget() == ctx.ssid
) {
service::wifi::disconnect();
}
shouldClose = true;
}
TickType_t wait_timeout = (ctx.wifiDevice == nullptr) ? pdMS_TO_TICKS(500) : portMAX_DELAY;
task_event_group_wait_any(&event_group, nullptr, wait_timeout);
if (ctx.wifiDevice == nullptr) {
Device* retry_device = nullptr;
if (device_get_first_by_type(&WIFI_TYPE, &retry_device) == ERROR_NONE) {
if (wifi_event_subscribe(retry_device, &ctx.wifiEventSub, &event_group) == ERROR_NONE) {
ctx.wifiDevice = retry_device;
} else {
device_put(retry_device);
}
app_manager_stop(event.result.launch_id);
break;
default:
break;
}
}
AppEvent event {};
while (app_event_poll(&sub, &event) == ERROR_NONE) {
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
case APP_EVENT_RESULT:
if (event.result.launch_id == ctx.forgetDialogId && event.result.result == 0) { // 0 = Yes
if (!service::wifi::settings::remove(ctx.ssid.c_str())) {
LOG_E(TAG, "Failed to remove SSID");
} else {
LOG_I(TAG, "Removed SSID");
if (
service::wifi::getRadioState() == service::wifi::RadioState::ConnectionActive &&
service::wifi::getConnectionTarget() == ctx.ssid
) {
service::wifi::disconnect();
}
shouldClose = true;
}
}
app_manager_stop(event.result.launch_id);
break;
default:
break;
}
if (shouldClose) break;
}
if (ctx.wifiDevice != nullptr) {
WifiEvent wifi_event {};
bool wifi_event_received = false;
while (wifi_event_poll(&ctx.wifiEventSub, &wifi_event) == ERROR_NONE) {
wifi_event_received = true;
}
if (wifi_event_received) {
requestViewUpdate(&ctx);
}
}
}
if (ctx.wifiSubscription != nullptr) {
service::wifi::getPubsub()->unsubscribe(ctx.wifiSubscription);
if (ctx.wifiDevice != nullptr) {
wifi_event_unsubscribe(ctx.wifiDevice, &ctx.wifiEventSub);
device_put(ctx.wifiDevice);
}
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0;
}
@@ -7,6 +7,7 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
@@ -14,6 +15,8 @@
#include <lvgl/widgets/spinner.h>
#include <lvgl/widgets/toolbar.h>
#include <tactility/check.h>
#include <tactility/device.h>
#include <tactility/log.h>
#include <lvgl.h>
@@ -33,10 +36,6 @@ struct Context {
std::string initialSsid;
std::string initialPassword;
// Touched only from the LVGL task: directly by onConnectPressed() (an LVGL event callback,
// which already runs with the LVGL lock held), and by onWifiEvent() (a wifi-pubsub
// callback running on some other thread) which explicitly wraps its touches in
// lvgl_lock()/lvgl_unlock() - see WifiApSettings.cpp for the same convention.
bool connecting = false;
bool connectionError = false;
@@ -49,7 +48,9 @@ struct Context {
lv_obj_t* connecting_spinner = nullptr;
lv_obj_t* connection_error = nullptr;
PubSub<service::wifi::WifiEvent>::SubscriptionHandle wifiSubscription = nullptr;
// Set once in appMain() before subscribing, left null if this device has no WiFi driver
Device* wifiDevice = nullptr;
WifiEventSubscription wifiEventSub {};
};
@@ -61,14 +62,12 @@ 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.
// 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);
}
// Runs on the wifi service's pubsub thread, not the LVGL task or this app's own thread.
void onWifiEvent(Context* ctx, service::wifi::WifiEvent event) {
void onWifiEvent(Context* ctx, WifiEvent event) {
bool shouldClose = false;
lvgl_lock();
@@ -90,8 +89,6 @@ void onWifiEvent(Context* ctx, service::wifi::WifiEvent event) {
lvgl_unlock();
if (shouldClose) {
// Async, non-blocking - same reasoning as onBackPressed() (must not call
// app_manager_stop() on ourselves); safe to call from any thread.
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(ctx->appInstanceId, &closeEvent);
}
@@ -121,8 +118,7 @@ void setLoading(Context* ctx, bool loading) {
void updateView(Context* ctx) {
if (ctx->connect_button == nullptr) {
// Buried (e.g. this window's own connecting state closed it, or a future dialog opens
// on top) - see destroyWidgets().
// Buried (e.g. this window's own connecting state closed it, or a future dialog opens on top)
return;
}
if (ctx->connectionError) {
@@ -306,46 +302,77 @@ void createWidgets(lv_obj_t* parent, void* userData) {
}
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
Context ctx {};
ctx.appInstanceId = appInstanceId;
ctx.initialSsid = (argc > 0) ? argv[0] : std::string();
ctx.initialPassword = (argc > 1) ? argv[1] : std::string();
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
// Subscribed once here, not in createWidgets(): that callback re-runs on every
// burial/resurface rebuild, and re-subscribing there would leak the previous subscription
// (and its captured ctx pointer) every time, only the last of which shutdown ever cleans up.
ctx.wifiSubscription = service::wifi::getPubsub()->subscribe([&ctx](auto event) {
onWifiEvent(&ctx, event);
});
AppEventSubscription sub {};
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
Device* wifi_device = nullptr;
if (device_get_first_by_type(&WIFI_TYPE, &wifi_device) == ERROR_NONE) {
if (wifi_event_subscribe(wifi_device, &ctx.wifiEventSub, &event_group) == ERROR_NONE) {
ctx.wifiDevice = wifi_device;
} else {
LOG_W(TAG, "Failed to subscribe to WiFi events");
device_put(wifi_device);
}
} else {
LOG_W(TAG, "No WiFi device found");
}
WindowId window = window_manager_create_ext(appInstanceId, createWidgets, destroyWidgets, &ctx);
bool shouldClose = false;
while (!shouldClose) {
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
TickType_t wait_timeout = (ctx.wifiDevice == nullptr) ? pdMS_TO_TICKS(500) : portMAX_DELAY;
task_event_group_wait_any(&event_group, nullptr, wait_timeout);
if (ctx.wifiDevice == nullptr) {
Device* retry_device = nullptr;
if (device_get_first_by_type(&WIFI_TYPE, &retry_device) == ERROR_NONE) {
if (wifi_event_subscribe(retry_device, &ctx.wifiEventSub, &event_group) == ERROR_NONE) {
ctx.wifiDevice = retry_device;
} else {
device_put(retry_device);
}
}
}
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
AppEvent event {};
while (app_event_poll(&sub, &event) == ERROR_NONE) {
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
}
if (shouldClose) break;
}
if (ctx.wifiDevice != nullptr) {
WifiEvent wifi_event {};
while (wifi_event_poll(&ctx.wifiEventSub, &wifi_event) == ERROR_NONE) {
onWifiEvent(&ctx, wifi_event);
}
}
}
if (ctx.wifiSubscription != nullptr) {
service::wifi::getPubsub()->unsubscribe(ctx.wifiSubscription);
if (ctx.wifiDevice != nullptr) {
wifi_event_unsubscribe(ctx.wifiDevice, &ctx.wifiEventSub);
device_put(ctx.wifiDevice);
}
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0;
}
+90 -32
View File
@@ -7,13 +7,18 @@
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <tactility/device.h>
#include <tactility/log.h>
#include <lvgl/lvgl.h>
#include <atomic>
namespace tt::app::wifimanage {
constexpr auto* TAG = "WifiManage";
@@ -24,12 +29,18 @@ namespace {
struct Context {
uint32_t appInstanceId;
PubSub<service::wifi::WifiEvent>::SubscriptionHandle wifiSubscription = nullptr;
// Set once in appMain() before subscribing, left null if this device has no WiFi driver
Device* wifiDevice = nullptr;
WifiEventSubscription wifiEventSub {};
Mutex mutex;
Bindings bindings {};
State state;
View view = View(&bindings, &state);
TaskEventGroup* eventGroup = nullptr;
uint32_t refreshBit = 0;
std::atomic<bool> needsRefresh {false};
void lock() { mutex.lock(); }
void unlock() { mutex.unlock(); }
};
@@ -62,17 +73,18 @@ static void onConnectToHidden() {
wificonnect::start();
}
void requestViewUpdate(Context* ctx) {
ctx->lock();
void updateView(Context* ctx) {
// Same lock order as createWidgets() (called with the LVGL lock already held, per the
// window-manager's WindowCreateWidgetsFn contract, then acquiring ctx->mutex) - acquiring
// these in the opposite order here would deadlock against a concurrent createWidgets() call.
lvgl_lock();
// Safe even while buried (e.g. WifiApSettings/WifiConnect opened on top): destroyWidgets()
// nulls the view's widget pointers before they're deleted, and update() no-ops on that.
ctx->lock();
ctx->view.update();
lvgl_unlock();
ctx->unlock();
lvgl_unlock();
}
void onWifiEvent(Context* ctx, service::wifi::WifiEvent event) {
void onWifiEvent(Context* ctx, WifiEvent event) {
auto radio_state = service::wifi::getRadioState();
LOG_I(TAG, "Update with state %s", service::wifi::radioStateToString(radio_state));
ctx->state.setRadioState(radio_state);
@@ -93,7 +105,7 @@ void onWifiEvent(Context* ctx, service::wifi::WifiEvent event) {
break;
}
requestViewUpdate(ctx);
updateView(ctx);
}
void createWidgets(lv_obj_t* parent, void* userData) {
@@ -101,18 +113,18 @@ void createWidgets(lv_obj_t* parent, void* userData) {
ctx->lock();
ctx->state.setConnectSsid("Connected"); // TODO update with proper SSID
ctx->view.init(ctx->appInstanceId, parent);
ctx->view.update();
ctx->unlock();
ctx->needsRefresh = true;
task_event_group_signal(ctx->eventGroup, ctx->refreshBit);
}
// Runs with the LVGL lock already held, possibly on another app's thread - see
// WindowDestroyWidgetsFn's warnings. Must stay lock-free: View::reset() only nulls pointers.
void destroyWidgets(void* userData) {
auto* ctx = static_cast<Context*>(userData);
ctx->view.reset();
}
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
Context ctx;
ctx.appInstanceId = appInstanceId;
ctx.bindings = (Bindings) {
@@ -123,18 +135,32 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
.onConnectToHidden = onConnectToHidden
};
ctx.wifiSubscription = service::wifi::getPubsub()->subscribe([&ctx](auto event) {
onWifiEvent(&ctx, event);
});
// State update (it has its own locking)
ctx.state.setRadioState(service::wifi::getRadioState());
ctx.state.setScanning(service::wifi::isScanning());
ctx.state.updateApRecords();
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
ctx.eventGroup = &event_group;
if (task_event_group_claim_bit(&event_group, &ctx.refreshBit) != ERROR_NONE) {
LOG_W(TAG, "Failed to claim a refresh bit; resurfacing after burial won't repopulate the view");
}
AppEventSubscription sub {};
sub.app_instance_id = appInstanceId;
app_event_subscribe(&sub);
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
Device* wifi_device = nullptr;
if (device_get_first_by_type(&WIFI_TYPE, &wifi_device) == ERROR_NONE) {
if (wifi_event_subscribe(wifi_device, &ctx.wifiEventSub, &event_group) == ERROR_NONE) {
ctx.wifiDevice = wifi_device;
} else {
LOG_W(TAG, "Failed to subscribe to WiFi events");
device_put(wifi_device);
}
} else {
LOG_W(TAG, "No WiFi device found");
}
WindowId window = window_manager_create_ext(appInstanceId, createWidgets, destroyWidgets, &ctx);
@@ -154,26 +180,58 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
bool shouldClose = false;
while (!shouldClose) {
AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) {
break;
// If the wifi device wasn't started yet when this app opened (boot-order race),
// ctx.wifiDevice is still null and there's no wifi-driven wake source to learn
// "it's ready now" from, so poll for it on a bounded timeout instead of blocking
// indefinitely. Once subscribed, this reverts to
// portMAX_DELAY - task_event_group_wait_any() still returns immediately for app_event
// and (once live) wifi_event, this timeout only matters while neither has fired yet.
TickType_t wait_timeout = (ctx.wifiDevice == nullptr) ? pdMS_TO_TICKS(500) : portMAX_DELAY;
task_event_group_wait_any(&event_group, nullptr, wait_timeout);
if (ctx.wifiDevice == nullptr) {
Device* retry_device = nullptr;
if (device_get_first_by_type(&WIFI_TYPE, &retry_device) == ERROR_NONE) {
if (wifi_event_subscribe(retry_device, &ctx.wifiEventSub, &event_group) == ERROR_NONE) {
ctx.wifiDevice = retry_device;
} else {
device_put(retry_device);
}
}
}
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
AppEvent event {};
while (app_event_poll(&sub, &event) == ERROR_NONE) {
switch (event.type) {
case APP_EVENT_CLOSE:
shouldClose = true;
break;
default:
break;
}
if (shouldClose) break;
}
if (ctx.wifiDevice != nullptr) {
WifiEvent wifi_event {};
while (wifi_event_poll(&ctx.wifiEventSub, &wifi_event) == ERROR_NONE) {
onWifiEvent(&ctx, wifi_event);
}
}
if (ctx.needsRefresh.exchange(false)) {
updateView(&ctx);
}
}
ctx.lock();
service::wifi::getPubsub()->unsubscribe(ctx.wifiSubscription);
ctx.wifiSubscription = nullptr;
ctx.unlock();
if (ctx.wifiDevice != nullptr) {
wifi_event_unsubscribe(ctx.wifiDevice, &ctx.wifiEventSub);
device_put(ctx.wifiDevice);
}
window_manager_remove(window);
app_event_unsubscribe(&sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0;
}
@@ -6,6 +6,7 @@
#include <Tactility/service/espnow/EspNowHostedTransport.h>
#include <tactility/device.h>
#include <tactility/drivers/wifi.h>
namespace tt::service::espnow::backend {
@@ -16,16 +17,19 @@ namespace tt::service::espnow::backend {
// truth shared with any other caller (e.g. an external OTA app), instead of being duplicated
// here.
bool waitForHostedTransport(uint32_t timeoutMs) {
Device* device = wifi_find_first_registered_device();
if (device == nullptr) {
Device* device = nullptr;
if (device_get_first_by_type(&WIFI_TYPE, &device) != ERROR_NONE) {
return false;
}
const FirmwareOps* ops = nullptr;
void* ctx = nullptr;
if (wifi_get_firmware_ops(device, &ops, &ctx) != ERROR_NONE) {
device_put(device);
return false;
}
return ops->wait_ready(ctx, timeoutMs);
bool ready = ops->wait_ready(ctx, timeoutMs);
device_put(device);
return ready;
}
}
+97 -62
View File
@@ -4,6 +4,7 @@
#include <Tactility/LogMessages.h>
#include <Tactility/RecursiveMutex.h>
#include <Tactility/Tactility.h>
#include <Tactility/Thread.h>
#include <Tactility/Timer.h>
#include <Tactility/service/Service.h>
#include <Tactility/service/ServiceManifest.h>
@@ -62,7 +63,6 @@ namespace {
/** State lives for the entire process; only ever (re)initialized by onStart(). */
struct WifiServiceState {
Device* device = nullptr;
std::shared_ptr<PubSub<WifiEvent>> pubsub = std::make_shared<PubSub<WifiEvent>>();
RecursiveMutex mutex;
bool secureConnection = false;
// Internal: set by connect()/disconnect() while a manual attempt is in flight, cleared on
@@ -79,24 +79,32 @@ struct WifiServiceState {
TickType_t lastScanTime = MAX_TICKS;
std::unique_ptr<Timer> autoConnectTimer;
bool bootEventSubscribed = false;
// Dedicated consumer for WifiEvents, alive for the service's whole lifetime (started in
// onStart(), stopped in onStop() - see dispatchSetEnabled()'s comment on why this outlives
// radio on/off toggles): runs onWifiDeviceEvent() on its own stack instead of the ESP-IDF
// esp_event task's, by blocking in task_event_group_wait_any() rather than being called back
// directly from fire_event().
TaskEventGroup wifiEventGroup {};
WifiEventSubscription wifiEventSub {};
Thread* wifiEventThread = nullptr;
std::atomic<bool> wifiEventThreadRunning {false};
};
WifiServiceState state;
bool started = false;
void onWifiDeviceEvent(Device* device, void* context, ::WifiEvent event);
void onWifiDeviceEvent(Device* device, ::WifiEvent event);
// ---- Helpers ----
void publish(WifiEvent event) {
state.pubsub->publish(event);
}
void publishRadioState(WifiRadioState radio_state) {
WifiEvent event = {};
event.type = WIFI_EVENT_TYPE_RADIO_STATE_CHANGED;
event.radio_state = radio_state;
publish(event);
// state.device is started (bookkeeping allocated) for the service's entire lifetime now - see
// dispatchSetEnabled()'s comment - so device_is_ready() no longer tracks radio-on state; query
// the driver directly instead.
bool isRadioOn() {
if (state.device == nullptr) return false;
WifiRadioState radio = WIFI_RADIO_STATE_OFF;
return wifi_get_radio_state(state.device, &radio) == ERROR_NONE && radio == WIFI_RADIO_STATE_ON;
}
RadioState combineRadioState(WifiRadioState radio, WifiStationState station) {
@@ -114,56 +122,88 @@ RadioState combineRadioState(WifiRadioState radio, WifiStationState station) {
return RadioState::Off;
}
// ---- WifiEvent consumer thread ----
// Runs onWifiDeviceEvent() on its own stack (see WifiServiceState::wifiEventGroup's comment).
constexpr configSTACK_DEPTH_TYPE WIFI_EVENT_THREAD_STACK_SIZE = 4096;
int32_t wifiEventThreadMain() {
// The 250ms timeout only bounds how promptly a stop request (wifiEventThreadRunning going
// false) is noticed; a real event still wakes this immediately regardless, since
// task_event_group_wait_any() returns as soon as the bit is signalled, whichever comes first.
while (state.wifiEventThreadRunning.load()) {
task_event_group_wait_any(&state.wifiEventGroup, nullptr, pdMS_TO_TICKS(250));
WifiEvent event {};
while (wifi_event_poll(&state.wifiEventSub, &event) == ERROR_NONE) {
onWifiDeviceEvent(state.device, event);
}
}
return 0;
}
bool startWifiEventThread() {
task_event_group_construct(&state.wifiEventGroup);
if (wifi_event_subscribe(state.device, &state.wifiEventSub, &state.wifiEventGroup) != ERROR_NONE) {
task_event_group_destruct(&state.wifiEventGroup);
return false;
}
state.wifiEventThreadRunning = true;
state.wifiEventThread = new Thread("wifi-events", WIFI_EVENT_THREAD_STACK_SIZE, [] { return wifiEventThreadMain(); });
state.wifiEventThread->start();
return true;
}
void stopWifiEventThread() {
if (state.wifiEventThread == nullptr) return;
state.wifiEventThreadRunning = false;
state.wifiEventThread->join();
delete state.wifiEventThread;
state.wifiEventThread = nullptr;
wifi_event_unsubscribe(state.device, &state.wifiEventSub);
task_event_group_destruct(&state.wifiEventGroup);
}
// ---- Dispatched work (runs on the main task) ----
// state.device is started (device_start()) once, in onStart(), and never stopped until onStop() -
// this only toggles the radio itself, so the wifi-events thread (and any app subscribed directly
// to the driver) stays subscribed across on/off toggles instead of having to resubscribe.
void dispatchSetEnabled(bool enabled) {
LOG_I(TAG, "dispatchSetEnabled(%d)", (int)enabled);
if (!started || state.device == nullptr) return;
bool ready = device_is_ready(state.device);
if (enabled == ready) {
if (enabled == isRadioOn()) {
LOG_W(TAG, "Can't enable/disable from current state");
return;
}
if (enabled) {
publishRadioState(WIFI_RADIO_STATE_ON_PENDING);
if (device_start(state.device) != ERROR_NONE) {
LOG_E(TAG, "Failed to start WiFi device");
publishRadioState(WIFI_RADIO_STATE_OFF);
return;
}
if (wifi_add_event_callback(state.device, nullptr, onWifiDeviceEvent) != ERROR_NONE) {
LOG_E(TAG, "Failed to register WiFi event callback");
device_stop(state.device);
publishRadioState(WIFI_RADIO_STATE_OFF);
if (wifi_set_radio_on(state.device) != ERROR_NONE) {
LOG_E(TAG, "Failed to enable WiFi radio");
return;
}
state.pauseAutoConnect = false;
state.lastScanTime = 0;
publishRadioState(WIFI_RADIO_STATE_ON);
} else {
publishRadioState(WIFI_RADIO_STATE_OFF_PENDING);
if (device_stop(state.device) != ERROR_NONE) {
LOG_E(TAG, "Failed to stop WiFi device");
publishRadioState(WIFI_RADIO_STATE_ON);
if (wifi_set_radio_off(state.device) != ERROR_NONE) {
LOG_E(TAG, "Failed to disable WiFi radio");
return;
}
wifi_remove_event_callback(state.device, onWifiDeviceEvent);
state.secureConnection = false;
publishRadioState(WIFI_RADIO_STATE_OFF);
}
}
void dispatchScan() {
LOG_I(TAG, "dispatchScan()");
if (!started || state.device == nullptr || !device_is_ready(state.device)) return;
if (!started || state.device == nullptr || !isRadioOn()) return;
state.lastScanTime = get_ticks();
@@ -189,18 +229,7 @@ void dispatchConnect() {
LOG_I(TAG, "Connecting to %s", target.ssid.c_str());
error_t result = wifi_station_connect(state.device, target.ssid.c_str(), target.password.c_str(), target.channel);
if (result != ERROR_NONE) {
LOG_E(TAG, "Failed to connect to %s (%s)", target.ssid.c_str(), error_to_string(result));
WifiEvent event = {};
event.type = WIFI_EVENT_TYPE_STATION_CONNECTION_RESULT;
// The driver couldn't even initiate the connection attempt; there's no
// more specific WifiStationConnectionError for that.
event.connection_error = WIFI_STATION_CONNECTION_ERROR_TIMEOUT;
publish(event);
}
// On success, WIFI_EVENT_TYPE_STATION_STATE_CHANGED / _CONNECTION_RESULT arrive
// asynchronously via onWifiDeviceEvent().
wifi_station_connect(state.device, target.ssid.c_str(), target.password.c_str(), target.channel);
}
void dispatchDisconnect() {
@@ -233,12 +262,14 @@ bool findAutoConnectAp(settings::WifiApSettings& out) {
void dispatchAutoConnect() {
LOG_I(TAG, "dispatchAutoConnect()");
if (state.pauseAutoConnect || state.externalScanPause.load()) {
if (state.pauseAutoConnect || state.externalScanPause.load() || !isRadioOn()) {
// A manual disconnect() or an in-progress manual connect() has paused
// auto-connect, or a caller (e.g. AutoScanPauseGuard) has externally paused it.
// This is called on every SCAN_FINISHED, not just the auto-connect timer's own
// scans (e.g. WifiManage re-scans on show), so it must honor the pause instead of
// reconnecting unconditionally.
// reconnecting unconditionally. The radio-off check matters because a scan that was
// already in flight can finish after the user turns the radio off. Without it,
// connect() would call dispatchSetEnabled(true) and turn the radio back on.
return;
}
RadioState radio_state = getRadioState();
@@ -278,7 +309,7 @@ void onAutoConnectTimer() {
// ---- Kernel driver event bridge ----
void onWifiDeviceEvent(Device* device, void* /*context*/, ::WifiEvent event) {
void onWifiDeviceEvent(Device* device, ::WifiEvent event) {
switch (event.type) {
case WIFI_EVENT_TYPE_SCAN_FINISHED:
getMainDispatcher().dispatch([] { dispatchAutoConnect(); });
@@ -333,10 +364,6 @@ void onWifiDeviceEvent(Device* device, void* /*context*/, ::WifiEvent event) {
default:
break;
}
// Forward the event as-is: subscribers inspect event.type and the
// relevant union field directly, same as this function does.
publish(event);
}
void autoScanSetPaused(bool paused) {
@@ -348,12 +375,8 @@ void autoScanSetPaused(bool paused) {
// region Public functions
std::shared_ptr<PubSub<WifiEvent>> getPubsub() {
return state.pubsub;
}
RadioState getRadioState() {
if (!started || state.device == nullptr || !device_is_ready(state.device)) {
if (!started || state.device == nullptr) {
return RadioState::Off;
}
@@ -403,7 +426,7 @@ void connect(const settings::WifiApSettings& ap, bool remember) {
state.pauseAutoConnect = true;
state.connectionTarget = ap;
state.connectionTargetRemember = remember;
radio_off = !device_is_ready(state.device);
radio_off = !isRadioOn();
}
getMainDispatcher().dispatch([radio_off] {
@@ -505,9 +528,17 @@ public:
wifi_auto_scan_set_paused_function(autoScanSetPaused);
state.device = wifi_find_first_registered_device();
if (state.device == nullptr) {
Device* wifi_device = nullptr;
if (device_get_first_by_type(&WIFI_TYPE, &wifi_device) != ERROR_NONE) {
LOG_W(TAG, "No WiFi device found");
} else if (device_start(wifi_device) != ERROR_NONE) {
LOG_E(TAG, "Failed to start WiFi device");
device_put(wifi_device);
} else {
state.device = wifi_device;
if (!startWifiEventThread()) {
LOG_E(TAG, "Failed to subscribe to WiFi events");
}
}
if (system_event_callback_add(KERNEL_EVENT_BOOT_COMPLETED, onBootCompleted, nullptr) == ERROR_NONE) {
@@ -535,9 +566,13 @@ public:
state.bootEventSubscribed = false;
}
if (state.device != nullptr && device_is_ready(state.device)) {
wifi_remove_event_callback(state.device, onWifiDeviceEvent);
if (state.device != nullptr) {
if (isRadioOn()) {
wifi_set_radio_off(state.device);
}
stopWifiEventThread();
device_stop(state.device);
device_put(state.device);
}
state.secureConnection = false;