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
+2 -2
View File
@@ -1,10 +1,10 @@
# Architecture: App Framework
Apps are event-driven, C API (`app-module`, `<app/*.h>`), not a C++ class. Each app has an `AppManifest` (`id`, `name`, `category`, `location`, `flags`) and a `main(app_instance_id, argc, argv)` entry point (`AppMainFn`), modelled on a C program's `main()`. Every app instance gets its own dedicated task for its whole lifetime, and blocks in that task until it returns.
Apps are event-driven, C API (`app-module`, `<app/*.h>`), not a C++ class. Each app has an `AppManifest` (`id`, `name`, `category`, `location`, `flags`) and a `main(argc, argv)` entry point (`AppMainFn`), modelled on a C program's `main()`. Every app instance gets its own dedicated task for its whole lifetime, and blocks in that task until it returns. Use `app_scheduler_current_app_id()` (`app/scheduler.h`) to identify the running instance - it's not passed as a parameter.
Lifecycle and inter-app communication go through `app_manager_*()` (`app/manager.h`) and `app_event_*()` (`app/event.h`):
- `app_manager_start()`/`app_manager_start_with_parameters()` launch a plain instance; `app_manager_start_for_result()` launches a modal child that reports back to a parent instance.
- An app subscribes with `app_event_subscribe()`/`app_event_await()` and reacts to `APP_EVENT_CLOSE` (terminate now) and `APP_EVENT_RESULT` (a child it started reported back).
- An app subscribes with `app_event_subscribe()` (registers for its own instance's events, no id argument needed), blocks via `task_event_group_wait()`/`task_event_group_wait_any()`, and drains with `app_event_poll()`, reacting to `APP_EVENT_CLOSE` (terminate now) and `APP_EVENT_RESULT` (a child it started reported back).
- An app closes itself by calling `app_manager_finish()` right before returning from `main()`; another instance is closed via `app_manager_stop()`.
Apps are registered at startup via `app_manager_add()`. External apps can be loaded from SD card via `manifest.properties` files, or side-loaded as ELF binaries on ESP32 (see `app/loader.h`'s `AppLoaderApi`).
@@ -18,7 +18,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 {
-1
View File
@@ -18,7 +18,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
gpio0 {
-1
View File
@@ -18,7 +18,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
gpio0 {
-1
View File
@@ -19,7 +19,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
gpio0 {
@@ -19,7 +19,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
gpio0 {
-1
View File
@@ -18,7 +18,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
gpio0 {
-1
View File
@@ -19,7 +19,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
gpio0 {
-1
View File
@@ -18,7 +18,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 {
-1
View File
@@ -19,7 +19,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 {
-1
View File
@@ -17,7 +17,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
gpio0 {
-1
View File
@@ -20,7 +20,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
gpio0 {
@@ -20,7 +20,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 {
@@ -20,7 +20,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 {
@@ -20,7 +20,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 {
@@ -18,7 +18,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
gpio0 {
@@ -18,7 +18,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
gpio0 {
@@ -20,7 +20,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 {
-1
View File
@@ -24,7 +24,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 {
-1
View File
@@ -11,7 +11,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
gpio0 {
@@ -11,7 +11,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
gpio0 {
@@ -11,7 +11,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
gpio0 {
@@ -19,7 +19,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
gpio0 {
@@ -21,7 +21,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 {
@@ -18,7 +18,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 {
@@ -26,7 +26,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 {
@@ -48,7 +48,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 {
@@ -23,7 +23,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 {
-1
View File
@@ -46,7 +46,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 {
@@ -19,7 +19,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 {
@@ -15,7 +15,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
gpio0 {
@@ -19,7 +19,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 {
-1
View File
@@ -23,7 +23,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 {
@@ -27,7 +27,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 {
@@ -27,7 +27,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 {
@@ -28,7 +28,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 {
-1
View File
@@ -24,7 +24,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
gpio0 {
@@ -31,7 +31,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 {
@@ -23,7 +23,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 {
@@ -31,7 +31,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 {
@@ -19,7 +19,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
gpio0 {
@@ -24,7 +24,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 {
-1
View File
@@ -24,7 +24,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 {
-1
View File
@@ -22,7 +22,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 {
@@ -21,7 +21,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 {
@@ -20,7 +20,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 {
@@ -21,7 +21,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 {
@@ -21,7 +21,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 {
@@ -20,7 +20,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 {
@@ -20,7 +20,6 @@
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 {
+7 -3
View File
@@ -11,11 +11,15 @@
## Higher Priority
- AppEventSubscription, SystemEventSubscription: should use TaskHandle_t notification. That way, there can be a single wait event for a task instead of X separate ones with each their timeout.
- lvgl file lock won't work with display vs sdcard when lvgl is stopped (external app bug risk)
- Apps should be able to specify stack size in their manifest
- Apps currently have a `Context` object with an `appInstanceId` in it, purely for being able to close the app.
Change it so that the app has its own termination signal that it waits for in the loop, it should subscribe to the event group.
- stopAppFromToolbar() in Tactility.cpp stops the top-most app. Change it so the toolbar knows for which app id it is created, so it can rely on that.
- Warn if file operations are done from prohibited tasks (e.g. lvgl task)
- AppHubApp: Prevent download callbacks from accessing a destroyed view.
- Move USB host task stacks to SPIRAM when available: esp32_usbhost*.cpp
- wifi: wifi_add_event_callback() and wifi_remove_event_callback() should be replaced by a subscribe/await pattern like system events.
When that's changed reduce LVGL callstack size in Tactility.cpp run()
- Get rid of WiFi service (Wifi.cpp/h) in Tactility.cpp
- Make it more clear to end-users that an SD card is required to run Tactility
- Make it possible to override stack size for an app via config file (loaded at boot), and make it possible to set preferred memory location (e.g. internal/external)
- Wrap file operations like fopen/fclose with file_mutex
+55 -21
View File
@@ -6,15 +6,14 @@
#include <stddef.h>
#include <stdint.h>
#include <tactility/concurrent/task_event_group.h>
#include <tactility/error.h>
#include <tactility/freertos/freertos.h>
#include <tactility/freertos/task.h>
#ifdef __cplusplus
extern "C" {
#endif
/** Identifies the kind of app-lifecycle event delivered through app_event_await(). */
/** Identifies the kind of app-lifecycle event delivered through app_event_poll(). */
enum AppEventType {
APP_EVENT_RESULT, // struct AppResultEventData
APP_EVENT_CLOSE, // no data - terminate now, permanently
@@ -51,30 +50,62 @@ struct AppEvent {
* Caller-owned subscription node. Unlike TactilityKernel's system_event poll subscription
* (which coalesces to the latest value), this queues events by value (FIFO) since dropping an
* APP_EVENT_RESULT would be unacceptable.
* @warning Fields other than `app_instance_id` are for internal use only; do not read or write
* them directly.
* @warning Fields other than `bit` are for internal use only; do not read or write them
* directly.
*/
struct AppEventSubscription {
/** The app instance this subscription receives events for; set by the caller before app_event_subscribe(). */
AppInstanceId app_instance_id;
/** Set by app_event_subscribe()/app_event_subscribe_with_app_id(). Read-only for the
* caller: OR it into a task_event_group_wait() mask (alongside other subscriptions sharing
* the same `event_group`) to block on this subscription and other event sources with one
* call. */
uint32_t bit;
TaskHandle_t task;
struct {
/** The app instance this subscription receives events for; set by
* app_event_subscribe()/app_event_subscribe_with_app_id(). */
AppInstanceId app_instance_id;
struct AppEvent queue[APP_EVENT_QUEUE_CAPACITY];
uint8_t head;
uint8_t count;
/** Caller-owned, borrowed; set by app_event_subscribe(). */
struct TaskEventGroup* event_group;
struct AppEventSubscription* next;
struct AppEvent queue[APP_EVENT_QUEUE_CAPACITY];
uint8_t head;
uint8_t count;
struct AppEventSubscription* next;
} internal;
};
/**
* Register a subscription for events addressed to @a sub->app_instance_id.
* @warning Does not work in ISR context.
* @param[in,out] sub subscription to register; caller sets @a sub->app_instance_id beforehand,
* owns the storage, and must keep it alive (and stationary) until unsubscribed
* @return ERROR_NONE on success
* Register a subscription for events addressed to the calling app's own instance (identified via
* app_scheduler_current_app_id()).
* @warning Does not work in ISR context. Must be called from the app's own task.
* @param[in,out] sub subscription to register; owns the storage, must stay alive (and
* stationary) until unsubscribed
* @param[in] event_group caller-owned group to wait on; must outlive @a sub (i.e. be
* destructed only after app_event_unsubscribe()). To block for an event, call
* task_event_group_wait()/task_event_group_wait_any() on this group (OR sub->bit into the mask,
* or use _wait_any() to include every subscription sharing it), then drain with app_event_poll().
* @retval ERROR_NONE on success
* @retval ERROR_RESOURCE @a event_group has no free bits left to claim; @a sub was not registered
* @retval ERROR_INVALID_STATE @a sub is already registered
*/
error_t app_event_subscribe(struct AppEventSubscription* sub);
error_t app_event_subscribe(struct AppEventSubscription* sub, struct TaskEventGroup* event_group);
/**
* Same as app_event_subscribe(), but for a caller that isn't running on @a app_instance_id's own
* task (e.g. test code simulating multiple distinct app instances from one thread). Production
* app code should use app_event_subscribe() instead.
* @warning Does not work in ISR context.
* @param[in,out] sub subscription to register; owns the storage, must stay alive (and
* stationary) until unsubscribed
* @param[in] event_group caller-owned group to wait on; same contract as app_event_subscribe()
* @param[in] app_instance_id the app instance this subscription receives events for
* @retval ERROR_NONE on success
* @retval ERROR_RESOURCE @a event_group has no free bits left to claim; @a sub was not registered
* @retval ERROR_INVALID_STATE @a sub is already registered
*/
error_t app_event_subscribe_with_app_id(struct AppEventSubscription* sub, struct TaskEventGroup* event_group, AppInstanceId app_instance_id);
/**
* Remove a previously registered subscription.
@@ -94,11 +125,14 @@ error_t app_event_unsubscribe(struct AppEventSubscription* sub);
error_t app_event_emit(AppInstanceId app_instance_id, const struct AppEvent* event);
/**
* Pop the next event for @a sub, blocking up to @a timeout if the queue is currently empty.
* Non-blocking: pop the next event for @a sub if one is already queued.
* @warning Never blocks. To wait for an event, block in task_event_group_wait()/
* task_event_group_wait_any() on @a sub's event group first (see app_event_subscribe()), then
* drain with this in a loop.
* @retval ERROR_NONE @a out_event was filled
* @retval ERROR_TIMEOUT no event arrived before the timeout elapsed
* @retval ERROR_TIMEOUT nothing queued right now
*/
error_t app_event_await(struct AppEventSubscription* sub, struct AppEvent* out_event, TickType_t timeout);
error_t app_event_poll(struct AppEventSubscription* sub, struct AppEvent* out_event);
#ifdef __cplusplus
}
+3 -3
View File
@@ -21,13 +21,13 @@ extern "C" {
/**
* Entry point signature for an APP_LOCATION_MEMORY app: a function linked directly into this
* firmware binary. Called on the dedicated task app-module's scheduler spawns for this instance,
* blocking for the app's whole lifetime - same contract as an external app's main(), plus
* @a app_instance_id identifying this running instance (use it with
* blocking for the app's whole lifetime - same contract as an external app's main(). Use
* app_scheduler_current_app_id() to identify this running instance (e.g. with
* app_event_subscribe()/window_manager_create()/etc.). The instance closes when this function
* returns - no separate call is needed.
* AppManifest::location.location holds this cast to void*.
*/
typedef int32_t (*AppMainFn)(uint32_t app_instance_id, int argc, char* argv[]);
typedef int32_t (*AppMainFn)(int argc, char* argv[]);
typedef void* AppRuntime;
@@ -16,9 +16,9 @@ error_t api_load(AppLocation location, AppRuntime* out_runtime) {
return ERROR_NONE;
}
int32_t api_run(AppRuntime runtime, uint32_t app_instance_id, int argc, char* argv[]) {
int32_t api_run(AppRuntime runtime, uint32_t /*app_instance_id*/, int argc, char* argv[]) {
auto entry = reinterpret_cast<AppMainFn>(runtime);
return entry(app_instance_id, argc, argv);
return entry(argc, argv);
}
void api_unload(AppRuntime /*unused*/) {
+44 -36
View File
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: Apache-2.0
#include <app/event.h>
#include <app/scheduler.h>
#include <tactility/concurrent/mutex.h>
#include <tactility/time.h>
@@ -7,7 +8,7 @@
/**
* Intrusive singly-linked list of subscriptions, keyed by app_instance_id.
* Guarded by a single coarse-grained mutex, notifying a subscriber here never invokes caller code
* (just a struct copy and an xTaskNotifyGive), so there is no reentrancy concern requiring a snapshot-then-unlock dance.
* (just a struct copy and a task_event_group_signal), so there is no reentrancy concern requiring a snapshot-then-unlock dance.
*/
static AppEventSubscription* subscriptions = nullptr;
@@ -21,32 +22,55 @@ static AppEventMutex subscriptions_mutex;
extern "C" {
error_t app_event_subscribe(AppEventSubscription* sub) {
sub->task = xTaskGetCurrentTaskHandle();
sub->head = 0;
sub->count = 0;
error_t app_event_subscribe_with_app_id(AppEventSubscription* sub, TaskEventGroup* event_group, AppInstanceId app_instance_id) {
uint32_t bit;
error_t claim_result = task_event_group_claim_bit(event_group, &bit);
if (claim_result != ERROR_NONE) {
return claim_result;
}
mutex_lock(&subscriptions_mutex.handle);
sub->next = subscriptions;
// Avoid cyclic subscription list that would loop forever
if (subscriptions == sub) {
mutex_unlock(&subscriptions_mutex.handle);
task_event_group_release_bit(event_group, bit);
return ERROR_INVALID_STATE;
}
sub->bit = bit;
sub->internal.app_instance_id = app_instance_id;
sub->internal.event_group = event_group;
sub->internal.head = 0;
sub->internal.count = 0;
sub->internal.next = subscriptions;
subscriptions = sub;
mutex_unlock(&subscriptions_mutex.handle);
return ERROR_NONE;
}
error_t app_event_subscribe(AppEventSubscription* sub, TaskEventGroup* event_group) {
return app_event_subscribe_with_app_id(sub, event_group, app_scheduler_current_app_id());
}
error_t app_event_unsubscribe(AppEventSubscription* sub) {
error_t result = ERROR_NOT_FOUND;
mutex_lock(&subscriptions_mutex.handle);
for (AppEventSubscription** link = &subscriptions; *link != nullptr; link = &(*link)->next) {
for (AppEventSubscription** link = &subscriptions; *link != nullptr; link = &(*link)->internal.next) {
if (*link == sub) {
*link = sub->next;
*link = sub->internal.next;
result = ERROR_NONE;
break;
}
}
mutex_unlock(&subscriptions_mutex.handle);
if (result == ERROR_NONE) {
task_event_group_release_bit(sub->internal.event_group, sub->bit);
}
return result;
}
@@ -57,23 +81,23 @@ error_t app_event_emit(AppInstanceId app_instance_id, const AppEvent* event) {
error_t result = ERROR_NOT_FOUND;
mutex_lock(&subscriptions_mutex.handle);
for (AppEventSubscription* sub = subscriptions; sub != nullptr; sub = sub->next) {
if (sub->app_instance_id != app_instance_id) {
for (AppEventSubscription* sub = subscriptions; sub != nullptr; sub = sub->internal.next) {
if (sub->internal.app_instance_id != app_instance_id) {
continue;
}
if (sub->count >= APP_EVENT_QUEUE_CAPACITY) {
if (sub->internal.count >= APP_EVENT_QUEUE_CAPACITY) {
result = ERROR_RESOURCE;
continue;
}
uint8_t tail = (sub->head + sub->count) % APP_EVENT_QUEUE_CAPACITY;
sub->queue[tail] = stamped_event;
sub->count++;
uint8_t tail = (sub->internal.head + sub->internal.count) % APP_EVENT_QUEUE_CAPACITY;
sub->internal.queue[tail] = stamped_event;
sub->internal.count++;
if (result != ERROR_RESOURCE) {
result = ERROR_NONE;
}
xTaskNotifyGive(sub->task);
task_event_group_signal(sub->internal.event_group, sub->bit);
}
mutex_unlock(&subscriptions_mutex.handle);
@@ -82,33 +106,17 @@ error_t app_event_emit(AppInstanceId app_instance_id, const AppEvent* event) {
static bool try_pop(AppEventSubscription* sub, AppEvent* out_event) {
mutex_lock(&subscriptions_mutex.handle);
bool has_event = sub->count > 0;
bool has_event = sub->internal.count > 0;
if (has_event) {
*out_event = sub->queue[sub->head];
sub->head = (sub->head + 1) % APP_EVENT_QUEUE_CAPACITY;
sub->count--;
*out_event = sub->internal.queue[sub->internal.head];
sub->internal.head = (sub->internal.head + 1) % APP_EVENT_QUEUE_CAPACITY;
sub->internal.count--;
}
mutex_unlock(&subscriptions_mutex.handle);
return has_event;
}
error_t app_event_await(AppEventSubscription* sub, AppEvent* out_event, TickType_t timeout) {
if (try_pop(sub, out_event)) {
// Drain any notification credit this (or an earlier) push accumulated on this task's
// FreeRTOS notification value: each app_event_emit() calls xTaskNotifyGive() regardless
// of whether the consumer takes this fast path or the blocking path below, so without
// this the credit would carry over and cause a future ulTaskNotifyTake() below to
// return immediately for a notification that was already accounted for here.
ulTaskNotifyTake(pdTRUE, 0);
return ERROR_NONE;
}
if (ulTaskNotifyTake(pdTRUE, timeout) == 0) {
return ERROR_TIMEOUT;
}
// Single-consumer by design (one task per subscription), so a wakeup implies the event
// this call was notified for is still there for us to pop.
error_t app_event_poll(AppEventSubscription* sub, AppEvent* out_event) {
return try_pop(sub, out_event) ? ERROR_NONE : ERROR_TIMEOUT;
}
+3 -1
View File
@@ -8,6 +8,7 @@
#include <service/manager.h>
#include <tactility/concurrent/task_event_group.h>
#include <tactility/error.h>
#include <tactility/module.h>
@@ -18,9 +19,10 @@ extern ServiceManifest app_internal_loader_service_manifest;
const ModuleSymbol app_module_symbols[] = {
// app/event
DEFINE_MODULE_SYMBOL(app_event_subscribe),
DEFINE_MODULE_SYMBOL(app_event_subscribe_with_app_id),
DEFINE_MODULE_SYMBOL(app_event_unsubscribe),
DEFINE_MODULE_SYMBOL(app_event_emit),
DEFINE_MODULE_SYMBOL(app_event_await),
DEFINE_MODULE_SYMBOL(app_event_poll),
// app/install
DEFINE_MODULE_SYMBOL(app_get_install_path),
DEFINE_MODULE_SYMBOL(app_install),
@@ -6,10 +6,12 @@
#include <tactility/delay.h>
#include <tactility/time.h>
TEST_CASE("app_event_subscribe/_await deliver events in FIFO order") {
TEST_CASE("app_event_subscribe/_poll deliver events in FIFO order") {
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = 1;
CHECK_EQ(app_event_subscribe(&sub), ERROR_NONE);
CHECK_EQ(app_event_subscribe_with_app_id(&sub, &event_group, 1), ERROR_NONE);
for (uint32_t i = 0; i < 3; i++) {
AppEvent event { .type = APP_EVENT_RESULT, .timestamp = 0, .result = { .launch_id = i, .result = 0 } };
@@ -18,32 +20,38 @@ TEST_CASE("app_event_subscribe/_await deliver events in FIFO order") {
for (uint32_t i = 0; i < 3; i++) {
AppEvent out {};
CHECK_EQ(app_event_await(&sub, &out, 0), ERROR_NONE);
CHECK_EQ(app_event_poll(&sub, &out), ERROR_NONE);
CHECK_EQ(out.type, APP_EVENT_RESULT);
CHECK_EQ(out.result.launch_id, i);
}
app_event_unsubscribe(&sub);
task_event_group_destruct(&event_group);
}
TEST_CASE("app_event_emit only delivers to subscriptions for that app_instance_id") {
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = 10;
app_event_subscribe(&sub);
app_event_subscribe_with_app_id(&sub, &event_group, 10);
AppEvent event { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
CHECK_EQ(app_event_emit(11, &event), ERROR_NOT_FOUND);
AppEvent out {};
CHECK_EQ(app_event_await(&sub, &out, 0), ERROR_TIMEOUT);
CHECK_EQ(app_event_poll(&sub, &out), ERROR_TIMEOUT);
app_event_unsubscribe(&sub);
task_event_group_destruct(&event_group);
}
TEST_CASE("app_event_emit returns ERROR_RESOURCE and drops the newest event once a subscription's queue is full") {
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = 20;
app_event_subscribe(&sub);
app_event_subscribe_with_app_id(&sub, &event_group, 20);
for (uint32_t i = 0; i < APP_EVENT_QUEUE_CAPACITY; i++) {
AppEvent event { .type = APP_EVENT_RESULT, .timestamp = 0, .result = { .launch_id = i, .result = 0 } };
@@ -57,42 +65,52 @@ TEST_CASE("app_event_emit returns ERROR_RESOURCE and drops the newest event once
// The already-queued events survive, in order, and the dropped one never arrives.
for (uint32_t i = 0; i < APP_EVENT_QUEUE_CAPACITY; i++) {
AppEvent out {};
CHECK_EQ(app_event_await(&sub, &out, 0), ERROR_NONE);
CHECK_EQ(app_event_poll(&sub, &out), ERROR_NONE);
CHECK_EQ(out.result.launch_id, i);
}
AppEvent out {};
CHECK_EQ(app_event_await(&sub, &out, 0), ERROR_TIMEOUT);
CHECK_EQ(app_event_poll(&sub, &out), ERROR_TIMEOUT);
app_event_unsubscribe(&sub);
task_event_group_destruct(&event_group);
}
TEST_CASE("app_event_unsubscribe stops further delivery") {
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = 30;
app_event_subscribe(&sub);
app_event_subscribe_with_app_id(&sub, &event_group, 30);
CHECK_EQ(app_event_unsubscribe(&sub), ERROR_NONE);
CHECK_EQ(app_event_unsubscribe(&sub), ERROR_NOT_FOUND);
AppEvent event { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
CHECK_EQ(app_event_emit(30, &event), ERROR_NOT_FOUND);
task_event_group_destruct(&event_group);
}
TEST_CASE("app_event_await times out when no event has arrived") {
TEST_CASE("app_event_poll times out when no event has arrived") {
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = 40;
app_event_subscribe(&sub);
app_event_subscribe_with_app_id(&sub, &event_group, 40);
AppEvent out {};
CHECK_EQ(app_event_await(&sub, &out, 0), ERROR_TIMEOUT);
CHECK_EQ(app_event_poll(&sub, &out), ERROR_TIMEOUT);
app_event_unsubscribe(&sub);
task_event_group_destruct(&event_group);
}
TEST_CASE("app_event_emit stamps the event with the current boot-relative time") {
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = 50;
app_event_subscribe(&sub);
app_event_subscribe_with_app_id(&sub, &event_group, 50);
auto before = static_cast<uint64_t>(get_micros_since_boot());
AppEvent event { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
@@ -100,17 +118,20 @@ TEST_CASE("app_event_emit stamps the event with the current boot-relative time")
auto after = static_cast<uint64_t>(get_micros_since_boot());
AppEvent out {};
REQUIRE_EQ(app_event_await(&sub, &out, 0), ERROR_NONE);
REQUIRE_EQ(app_event_poll(&sub, &out), ERROR_NONE);
CHECK_GE(out.timestamp, before);
CHECK_LE(out.timestamp, after);
app_event_unsubscribe(&sub);
task_event_group_destruct(&event_group);
}
TEST_CASE("app_event_await wakes when the event is emitted from another task") {
TEST_CASE("task_event_group_wait wakes when the event is emitted from another task") {
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = 60;
CHECK_EQ(app_event_subscribe(&sub), ERROR_NONE);
CHECK_EQ(app_event_subscribe_with_app_id(&sub, &event_group, 60), ERROR_NONE);
auto* thread = thread_alloc_full(
"app-event-emitter",
@@ -126,12 +147,14 @@ TEST_CASE("app_event_await wakes when the event is emitted from another task") {
);
CHECK_EQ(thread_start(thread), ERROR_NONE);
CHECK_EQ(task_event_group_wait(&event_group, sub.bit, false, nullptr, pdMS_TO_TICKS(2000)), ERROR_NONE);
AppEvent out {};
CHECK_EQ(app_event_await(&sub, &out, pdMS_TO_TICKS(2000)), ERROR_NONE);
CHECK_EQ(app_event_poll(&sub, &out), ERROR_NONE);
CHECK_EQ(out.type, APP_EVENT_CLOSE);
CHECK_EQ(thread_join(thread, pdMS_TO_TICKS(2000), 1), ERROR_NONE);
thread_free(thread);
app_event_unsubscribe(&sub);
task_event_group_destruct(&event_group);
}
@@ -3,6 +3,7 @@
#include <app/event.h>
#include <app/loader.h>
#include <app/manager.h>
#include <app/scheduler.h>
#include <service/manager.h>
@@ -55,7 +56,7 @@ void stash_received_arguments(int argc, char* argv[]) {
// parameter (app_manager_start_for_result()), acts as a modal dialog instead: returns the
// requested result (argv[0], parsed as an int) immediately (the app's own return value IS the
// delivered APP_EVENT_RESULT.result - see app_scheduler.cpp's thread_main()).
int32_t fake_run(void*, uint32_t app_instance_id, int argc, char* argv[]) {
int32_t fake_run(void*, uint32_t /*app_instance_id*/, int argc, char* argv[]) {
stash_received_arguments(argc, argv);
if (argc == 1) {
@@ -66,21 +67,30 @@ int32_t fake_run(void*, uint32_t app_instance_id, int argc, char* argv[]) {
return static_cast<int32_t>(strtol(argv[0], nullptr, 10));
}
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
sub.app_instance_id = app_instance_id;
app_event_subscribe(&sub);
app_event_subscribe(&sub, &event_group);
while (true) {
AppEvent event {};
if (app_event_await(&sub, &event, pdMS_TO_TICKS(5000)) != ERROR_NONE) {
if (task_event_group_wait_any(&event_group, nullptr, pdMS_TO_TICKS(5000)) != ERROR_NONE) {
break; // safety net so a bug here can't hang the test suite
}
if (event.type == APP_EVENT_CLOSE) {
break;
bool done = false;
AppEvent event {};
while (app_event_poll(&sub, &event) == ERROR_NONE) {
if (event.type == APP_EVENT_CLOSE) {
done = true;
break;
}
}
if (done) break;
}
app_event_unsubscribe(&sub);
task_event_group_destruct(&event_group);
return 0;
}
@@ -129,8 +139,8 @@ void ensure_memory_loader_registered() {
// Same subscribe-until-close contract as fake_run() above, but called directly as an AppMainFn -
// this is what a real internal app's entry point looks like.
int32_t fake_app_main(uint32_t app_instance_id, int argc, char* argv[]) {
return fake_run(nullptr, app_instance_id, argc, argv);
int32_t fake_app_main(int argc, char* argv[]) {
return fake_run(nullptr, app_scheduler_current_app_id(), argc, argv);
}
// Wraps app_manager_get_topmost_instance_id() for terse assertions: 0 if no app is Active.
@@ -329,9 +339,11 @@ TEST_CASE("app_manager_start_for_result delivers APP_EVENT_RESULT to the parent,
REQUIRE_EQ(app_manager_start("test.app.parent", &parent_id), ERROR_NONE);
CHECK(wait_for_state(parent_id, APP_INSTANCE_STATE_ACTIVE, 1000));
TaskEventGroup parent_event_group {};
task_event_group_construct(&parent_event_group);
AppEventSubscription parent_sub {};
parent_sub.app_instance_id = parent_id;
REQUIRE_EQ(app_event_subscribe(&parent_sub), ERROR_NONE);
REQUIRE_EQ(app_event_subscribe_with_app_id(&parent_sub, &parent_event_group, parent_id), ERROR_NONE);
const char* argv[] = { "42" };
uint32_t child_id = 0;
@@ -340,13 +352,15 @@ TEST_CASE("app_manager_start_for_result delivers APP_EVENT_RESULT to the parent,
// Launching a modal child never touches the parent's own task/state.
CHECK_EQ(app_manager_get_state(parent_id), APP_INSTANCE_STATE_ACTIVE);
REQUIRE_EQ(task_event_group_wait(&parent_event_group, parent_sub.bit, false, nullptr, pdMS_TO_TICKS(2000)), ERROR_NONE);
AppEvent event {};
REQUIRE_EQ(app_event_await(&parent_sub, &event, pdMS_TO_TICKS(2000)), ERROR_NONE);
REQUIRE_EQ(app_event_poll(&parent_sub, &event), ERROR_NONE);
CHECK_EQ(event.type, APP_EVENT_RESULT);
CHECK_EQ(event.result.launch_id, child_id);
CHECK_EQ(event.result.result, 42);
app_event_unsubscribe(&parent_sub);
task_event_group_destruct(&parent_event_group);
app_manager_stop(child_id);
app_manager_stop(parent_id);
app_manager_remove("test.app.parent");
@@ -365,9 +379,11 @@ TEST_CASE("app_manager_start_for_result delivers the child's own return value as
REQUIRE_EQ(app_manager_start("test.app.parent2", &parent_id), ERROR_NONE);
CHECK(wait_for_state(parent_id, APP_INSTANCE_STATE_ACTIVE, 1000));
TaskEventGroup parent_event_group {};
task_event_group_construct(&parent_event_group);
AppEventSubscription parent_sub {};
parent_sub.app_instance_id = parent_id;
REQUIRE_EQ(app_event_subscribe(&parent_sub), ERROR_NONE);
REQUIRE_EQ(app_event_subscribe_with_app_id(&parent_sub, &parent_event_group, parent_id), ERROR_NONE);
uint32_t child_id = 0;
// No parameters - fake_run falls through to its normal CLOSE loop instead of acting as a
@@ -377,13 +393,15 @@ TEST_CASE("app_manager_start_for_result delivers the child's own return value as
app_manager_stop(child_id); // force-close
REQUIRE_EQ(task_event_group_wait(&parent_event_group, parent_sub.bit, false, nullptr, pdMS_TO_TICKS(2000)), ERROR_NONE);
AppEvent event {};
REQUIRE_EQ(app_event_await(&parent_sub, &event, pdMS_TO_TICKS(2000)), ERROR_NONE);
REQUIRE_EQ(app_event_poll(&parent_sub, &event), ERROR_NONE);
CHECK_EQ(event.type, APP_EVENT_RESULT);
CHECK_EQ(event.result.launch_id, child_id);
CHECK_EQ(event.result.result, 0); // fake_run's CLOSE loop always returns 0
app_event_unsubscribe(&parent_sub);
task_event_group_destruct(&parent_event_group);
app_manager_stop(parent_id);
app_manager_remove("test.app.parent2");
app_manager_remove("test.app.child2");
@@ -0,0 +1,71 @@
#include "doctest.h"
#include <app/event.h>
#include <tactility/system_event.h>
// Regression coverage for the primary motivation behind TaskEventGroup: a task subscribed to
// both an app_event and a system_event must be able to block once and wake for either, without
// losing an event or regressing either subsystem's own delivery semantics (FIFO for app_event,
// coalescing for system_event).
TEST_CASE("a task can wait on app_event and system_event together via one TaskEventGroup") {
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription app_sub {};
CHECK_EQ(app_event_subscribe_with_app_id(&app_sub, &event_group, 100), ERROR_NONE);
SystemEventSubscription sys_sub {};
sys_sub.event.type = KERNEL_EVENT_BOOT_COMPLETED;
CHECK_EQ(system_event_subscribe(&sys_sub, &event_group), ERROR_NONE);
// Distinct bits, so a combined wait can tell (via out_flags) which source(s) fired.
CHECK_NE(app_sub.bit, sys_sub.bit);
uint32_t both_bits = app_sub.bit | sys_sub.bit;
// Only app_event fires: combined wait matches just that bit, and only app_event_poll()
// finds something to pop.
AppEvent emitted { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
CHECK_EQ(app_event_emit(100, &emitted), ERROR_NONE);
uint32_t out_flags = 0;
CHECK_EQ(task_event_group_wait(&event_group, both_bits, false, &out_flags, 0), ERROR_NONE);
CHECK_EQ(out_flags, app_sub.bit);
AppEvent app_out {};
CHECK_EQ(app_event_poll(&app_sub, &app_out), ERROR_NONE);
CHECK_EQ(app_out.type, APP_EVENT_CLOSE);
CHECK_EQ(system_event_poll(&sys_sub), ERROR_TIMEOUT);
// Only system_event fires: combined wait matches just that bit, and only
// system_event_poll() finds something pending.
CHECK_EQ(system_event_emit(KERNEL_EVENT_BOOT_COMPLETED, nullptr, 0), ERROR_NONE);
out_flags = 0;
CHECK_EQ(task_event_group_wait(&event_group, both_bits, false, &out_flags, 0), ERROR_NONE);
CHECK_EQ(out_flags, sys_sub.bit);
CHECK_EQ(system_event_poll(&sys_sub), ERROR_NONE);
AppEvent app_out2 {};
CHECK_EQ(app_event_poll(&app_sub, &app_out2), ERROR_TIMEOUT);
// Both fire before the wait: combined wait matches both bits, and both subsystems' own
// polls (which re-check their own state rather than trusting the bit) still deliver.
CHECK_EQ(app_event_emit(100, &emitted), ERROR_NONE);
CHECK_EQ(system_event_emit(KERNEL_EVENT_BOOT_COMPLETED, nullptr, 0), ERROR_NONE);
out_flags = 0;
CHECK_EQ(task_event_group_wait(&event_group, both_bits, false, &out_flags, 0), ERROR_NONE);
CHECK_EQ(out_flags, both_bits);
CHECK_EQ(app_event_poll(&app_sub, &app_out), ERROR_NONE);
CHECK_EQ(system_event_poll(&sys_sub), ERROR_NONE);
// Unsubscribe order (system_event before app_event) must not matter - the group outlives
// both and is destructed last.
system_event_unsubscribe(&sys_sub);
app_event_unsubscribe(&app_sub);
task_event_group_destruct(&event_group);
}
+1 -1
View File
@@ -14,7 +14,7 @@ idf_component_register(
SRCS ${SOURCES}
INCLUDE_DIRS "include/"
PRIV_INCLUDE_DIRS "private/"
REQUIRES TactilityKernel driver esp_adc esp_driver_i2c esp_lcd vfs fatfs esp_wifi esp_netif esp_event
REQUIRES TactilityKernel TactilityKernelCpp driver esp_adc esp_driver_i2c esp_lcd vfs fatfs esp_wifi esp_netif esp_event
PRIV_REQUIRES ${PRIV_REQUIRES_LIST}
)
@@ -18,6 +18,8 @@
#include <tactility/log.h>
#include <tactility/time.h>
#include <TactilityCpp/Allocator.h>
#if defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
#include <tactility/drivers/esp32_esp_hosted_ota.h>
#endif
@@ -33,14 +35,8 @@
namespace {
constexpr size_t WIFI_MAX_CALLBACKS = 4;
constexpr uint16_t WIFI_SCAN_RECORD_LIMIT = 32;
struct WifiCallbackEntry {
WifiEventCallback fn = nullptr;
void* ctx = nullptr;
};
struct Esp32WifiCtx {
Device* device = nullptr;
@@ -66,9 +62,8 @@ struct Esp32WifiCtx {
int32_t lastEventId = -1;
TickType_t lastEventTick = 0;
Mutex callbackMutex{};
WifiCallbackEntry callbacks[WIFI_MAX_CALLBACKS] = {};
size_t callbackCount = 0;
Mutex subscriptionsMutex{};
WifiEventSubscription* subscriptions = nullptr;
};
#define GET_CTX(device) (static_cast<Esp32WifiCtx*>(device_get_driver_data(device)))
@@ -93,16 +88,18 @@ WifiAuthenticationType to_wifi_authentication_type(wifi_auth_mode_t mode) {
}
void fire_event(Esp32WifiCtx* ctx, WifiEvent event) {
WifiCallbackEntry local[WIFI_MAX_CALLBACKS];
size_t count;
mutex_lock(&ctx->callbackMutex);
count = ctx->callbackCount;
memcpy(local, ctx->callbacks, count * sizeof(WifiCallbackEntry));
mutex_unlock(&ctx->callbackMutex);
for (size_t i = 0; i < count; i++) {
local[i].fn(ctx->device, local[i].ctx, event);
mutex_lock(&ctx->subscriptionsMutex);
for (WifiEventSubscription* sub = ctx->subscriptions; sub != nullptr; sub = sub->internal.next) {
mutex_lock(&sub->internal.ring_mutex);
if (sub->internal.count < WIFI_EVENT_QUEUE_CAPACITY) {
uint8_t tail = (sub->internal.head + sub->internal.count) % WIFI_EVENT_QUEUE_CAPACITY;
sub->internal.queue[tail] = event;
sub->internal.count++;
}
mutex_unlock(&sub->internal.ring_mutex);
task_event_group_signal(sub->internal.event_group, sub->bit);
}
mutex_unlock(&ctx->subscriptionsMutex);
}
// ---- ESP-IDF event handling (runs on the esp_event task) ----
@@ -257,6 +254,11 @@ error_t bring_up_wifi(Esp32WifiCtx* ctx) {
ctx->radioState = WIFI_RADIO_STATE_ON;
mutex_unlock(&ctx->mutex);
WifiEvent radio_event = {};
radio_event.type = WIFI_EVENT_TYPE_RADIO_STATE_CHANGED;
radio_event.radio_state = WIFI_RADIO_STATE_ON;
fire_event(ctx, radio_event);
LOG_I(TAG, "WiFi radio on");
return ERROR_NONE;
}
@@ -306,6 +308,11 @@ void bring_down_wifi(Esp32WifiCtx* ctx) {
ctx->radioState = WIFI_RADIO_STATE_OFF;
mutex_unlock(&ctx->mutex);
WifiEvent radio_event = {};
radio_event.type = WIFI_EVENT_TYPE_RADIO_STATE_CHANGED;
radio_event.radio_state = WIFI_RADIO_STATE_OFF;
fire_event(ctx, radio_event);
LOG_I(TAG, "WiFi radio off");
}
@@ -497,38 +504,77 @@ error_t api_station_get_rssi(Device* device, int32_t* rssi) {
return ERROR_NONE;
}
error_t api_add_event_callback(Device* device, void* callback_context, WifiEventCallback callback) {
error_t api_set_radio_on(Device* device) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || callback == nullptr) return ERROR_INVALID_ARGUMENT;
if (ctx == nullptr) return ERROR_INVALID_STATE;
mutex_lock(&ctx->callbackMutex);
if (ctx->callbackCount >= WIFI_MAX_CALLBACKS) {
mutex_unlock(&ctx->callbackMutex);
return ERROR_OUT_OF_MEMORY;
}
ctx->callbacks[ctx->callbackCount] = { .fn = callback, .ctx = callback_context };
ctx->callbackCount++;
mutex_unlock(&ctx->callbackMutex);
mutex_lock(&ctx->mutex);
bool already_on = ctx->radioState == WIFI_RADIO_STATE_ON;
mutex_unlock(&ctx->mutex);
if (already_on) return ERROR_NONE;
return bring_up_wifi(ctx);
}
error_t api_set_radio_off(Device* device) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr) return ERROR_INVALID_STATE;
mutex_lock(&ctx->mutex);
bool already_off = ctx->radioState == WIFI_RADIO_STATE_OFF;
mutex_unlock(&ctx->mutex);
if (already_off) return ERROR_NONE;
bring_down_wifi(ctx);
return ERROR_NONE;
}
error_t api_remove_event_callback(Device* device, WifiEventCallback callback) {
error_t api_event_subscribe(Device* device, WifiEventSubscription* sub, TaskEventGroup* event_group) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || callback == nullptr) return ERROR_INVALID_ARGUMENT;
if (ctx == nullptr || sub == nullptr || event_group == nullptr) return ERROR_INVALID_ARGUMENT;
mutex_lock(&ctx->callbackMutex);
for (size_t i = 0; i < ctx->callbackCount; i++) {
if (ctx->callbacks[i].fn == callback) {
for (size_t j = i; j + 1 < ctx->callbackCount; j++) {
ctx->callbacks[j] = ctx->callbacks[j + 1];
}
ctx->callbackCount--;
mutex_unlock(&ctx->callbackMutex);
return ERROR_NONE;
uint32_t bit;
error_t claim_result = task_event_group_claim_bit(event_group, &bit);
if (claim_result != ERROR_NONE) {
return claim_result;
}
mutex_lock(&ctx->subscriptionsMutex);
// Avoid cyclic subscription list that would loop forever
if (ctx->subscriptions == sub) {
mutex_unlock(&ctx->subscriptionsMutex);
task_event_group_release_bit(event_group, bit);
return ERROR_INVALID_STATE;
}
sub->internal.event_group = event_group;
sub->bit = bit;
sub->internal.next = ctx->subscriptions;
ctx->subscriptions = sub;
mutex_unlock(&ctx->subscriptionsMutex);
return ERROR_NONE;
}
error_t api_event_unsubscribe(Device* device, WifiEventSubscription* sub) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || sub == nullptr) return ERROR_INVALID_ARGUMENT;
error_t result = ERROR_NOT_FOUND;
mutex_lock(&ctx->subscriptionsMutex);
for (WifiEventSubscription** link = &ctx->subscriptions; *link != nullptr; link = &(*link)->internal.next) {
if (*link == sub) {
*link = sub->internal.next;
result = ERROR_NONE;
break;
}
}
mutex_unlock(&ctx->callbackMutex);
return ERROR_NOT_FOUND;
mutex_unlock(&ctx->subscriptionsMutex);
if (result == ERROR_NONE) {
task_event_group_release_bit(sub->internal.event_group, sub->bit);
}
return result;
}
error_t api_get_firmware_ops(Device* /*device*/, const FirmwareOps** ops, void** ctx) {
@@ -550,6 +596,8 @@ error_t api_get_firmware_ops(Device* /*device*/, const FirmwareOps** ops, void**
}
const WifiApi esp32_wifi_api = {
.set_radio_on = api_set_radio_on,
.set_radio_off = api_set_radio_off,
.get_radio_state = api_get_radio_state,
.get_station_state = api_get_station_state,
.get_access_point_state = api_get_access_point_state,
@@ -561,34 +609,23 @@ const WifiApi esp32_wifi_api = {
.station_connect = api_station_connect,
.station_disconnect = api_station_disconnect,
.station_get_rssi = api_station_get_rssi,
.add_event_callback = api_add_event_callback,
.remove_event_callback = api_remove_event_callback,
.event_subscribe = api_event_subscribe,
.event_unsubscribe = api_event_unsubscribe,
.get_firmware_ops = api_get_firmware_ops
};
// ---- Driver lifecycle ----
// The ESP-IDF WiFi stack isn't touched until the device is actually started:
// registering this driver (module start()) only makes it available for
// binding, it doesn't spin up any resources.
error_t start_device(Device* device) {
auto* ctx = new(std::nothrow) Esp32WifiCtx();
if (ctx == nullptr) return ERROR_OUT_OF_MEMORY;
// Prefers PSRAM/SPIRAM, falling back to internal RAM when unavailable - aborts on true OOM
// (see OptExternalAllocator's own doc), so no null check here.
tt::OptExternalAllocator<Esp32WifiCtx> allocator;
auto* ctx = allocator.allocate(1);
new (ctx) Esp32WifiCtx();
ctx->device = device;
mutex_construct(&ctx->mutex);
mutex_construct(&ctx->callbackMutex);
mutex_construct(&ctx->subscriptionsMutex);
device_set_driver_data(device, ctx);
error_t result = bring_up_wifi(ctx);
if (result != ERROR_NONE) {
device_set_driver_data(device, nullptr);
mutex_destruct(&ctx->callbackMutex);
mutex_destruct(&ctx->mutex);
delete ctx;
return result;
}
return ERROR_NONE;
}
@@ -596,12 +633,28 @@ error_t stop_device(Device* device) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr) return ERROR_NONE;
bring_down_wifi(ctx);
if (ctx->radioState == WIFI_RADIO_STATE_ON) {
bring_down_wifi(ctx);
}
// Release any subscribers that never unsubscribed: device_stop() doesn't wait for apps still
// using this device, so a later wifi_event_unsubscribe() would find no ctx and skip releasing
// the bit and destructing sub->internal.ring_mutex.
mutex_lock(&ctx->subscriptionsMutex);
for (WifiEventSubscription* sub = ctx->subscriptions; sub != nullptr;) {
WifiEventSubscription* next = sub->internal.next;
task_event_group_release_bit(sub->internal.event_group, sub->bit);
mutex_destruct(&sub->internal.ring_mutex);
sub = next;
}
ctx->subscriptions = nullptr;
mutex_unlock(&ctx->subscriptionsMutex);
device_set_driver_data(device, nullptr);
mutex_destruct(&ctx->callbackMutex);
mutex_destruct(&ctx->subscriptionsMutex);
mutex_destruct(&ctx->mutex);
delete ctx;
ctx->~Esp32WifiCtx();
tt::OptExternalAllocator<Esp32WifiCtx>().deallocate(ctx, 1);
return ERROR_NONE;
}
@@ -41,6 +41,12 @@ struct Esp32WifiPinnedCtx {
};
#define GET_CTX(device) (static_cast<Esp32WifiPinnedCtx*>(device_get_driver_data(device)))
// Only for event_subscribe()/event_unsubscribe(): unlike the rest of WifiApi, have
// construct/destruct side effects on sub->ring_mutex at the wifi_event_subscribe()/
// wifi_event_unsubscribe() kernel-wrapper layer (see wifi.cpp) - calling that wrapper AGAIN here
// (instead of the child driver's raw vtable entry) double-runs those side effects, corrupting the
// mutex. Every other WifiApi call below is a plain pass-through.
#define CHILD_WIFI_API(child) ((const struct WifiApi*)device_get_driver(child)->api)
struct MarshalledCall {
const std::function<void()>* work;
@@ -176,20 +182,44 @@ error_t api_station_get_rssi(Device* device, int32_t* rssi) {
return wifi_station_get_rssi(ctx->child, rssi);
}
error_t api_add_event_callback(Device* device, void* callback_context, WifiEventCallback callback) {
error_t api_event_subscribe(Device* device, WifiEventSubscription* sub, TaskEventGroup* event_group) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || ctx->child == nullptr) return ERROR_INVALID_STATE;
return wifi_add_event_callback(ctx->child, callback_context, callback);
return CHILD_WIFI_API(ctx->child)->event_subscribe(ctx->child, sub, event_group);
}
error_t api_remove_event_callback(Device* device, WifiEventCallback callback) {
error_t api_event_unsubscribe(Device* device, WifiEventSubscription* sub) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || ctx->child == nullptr) return ERROR_INVALID_STATE;
return wifi_remove_event_callback(ctx->child, callback);
return CHILD_WIFI_API(ctx->child)->event_unsubscribe(ctx->child, sub);
}
// ---- WifiApi: state-changing calls are marshalled onto the pinned WiFi task ----
error_t api_set_radio_on(Device* device) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || ctx->child == nullptr) return ERROR_INVALID_STATE;
error_t result = ERROR_NONE;
Device* child = ctx->child;
error_t err = run_on_pinned_thread(ctx, [child, &result]() {
result = wifi_set_radio_on(child);
});
return err != ERROR_NONE ? err : result;
}
error_t api_set_radio_off(Device* device) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || ctx->child == nullptr) return ERROR_INVALID_STATE;
error_t result = ERROR_NONE;
Device* child = ctx->child;
error_t err = run_on_pinned_thread(ctx, [child, &result]() {
result = wifi_set_radio_off(child);
});
return err != ERROR_NONE ? err : result;
}
error_t api_scan(Device* device) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || ctx->child == nullptr) return ERROR_INVALID_STATE;
@@ -227,6 +257,8 @@ error_t api_station_disconnect(Device* device) {
}
const WifiApi esp32_wifi_pinned_api = {
.set_radio_on = api_set_radio_on,
.set_radio_off = api_set_radio_off,
.get_radio_state = api_get_radio_state,
.get_station_state = api_get_station_state,
.get_access_point_state = api_get_access_point_state,
@@ -238,15 +270,15 @@ const WifiApi esp32_wifi_pinned_api = {
.station_connect = api_station_connect,
.station_disconnect = api_station_disconnect,
.station_get_rssi = api_station_get_rssi,
.add_event_callback = api_add_event_callback,
.remove_event_callback = api_remove_event_callback
.event_subscribe = api_event_subscribe,
.event_unsubscribe = api_event_unsubscribe
};
// ---- Driver lifecycle ----
// Starting/stopping the child (which brings the ESP-IDF WiFi stack up/down)
// also happens on the pinned thread, since esp_wifi_init/start/stop/deinit
// are subject to the same core-affinity requirement as the state-changing
// WifiApi calls above.
// Starting/stopping the child (allocating/freeing its bookkeeping - see esp32_wifi.cpp's own
// start_device()/stop_device() comment) happens on the pinned thread for the same core-affinity
// reason as the state-changing WifiApi calls above; enable()/disable() (which actually bring the
// ESP-IDF WiFi stack up/down) are marshalled the same way.
error_t start_device(Device* device) {
auto* ctx = new(std::nothrow) Esp32WifiPinnedCtx();
@@ -19,8 +19,6 @@
namespace {
constexpr size_t WIFI_MAX_CALLBACKS = 4;
struct MockApRecord {
const char* ssid;
int8_t rssi;
@@ -40,11 +38,6 @@ constexpr size_t MOCK_SCAN_RESULT_COUNT = sizeof(MOCK_SCAN_RESULTS) / sizeof(MOC
constexpr int8_t MOCK_CONNECTED_RSSI = -30;
constexpr const char* MOCK_IPV4_ADDRESS = "192.168.1.2";
struct WifiCallbackEntry {
WifiEventCallback fn = nullptr;
void* ctx = nullptr;
};
struct PosixWifiCtx {
Device* device = nullptr;
@@ -54,24 +47,25 @@ struct PosixWifiCtx {
bool scanning = false;
char targetSsid[33] = {};
Mutex callbackMutex {};
WifiCallbackEntry callbacks[WIFI_MAX_CALLBACKS] = {};
size_t callbackCount = 0;
Mutex subscriptionsMutex {};
WifiEventSubscription* subscriptions = nullptr;
};
#define GET_CTX(device) (static_cast<PosixWifiCtx*>(device_get_driver_data(device)))
void fireEvent(PosixWifiCtx* ctx, WifiEvent event) {
WifiCallbackEntry local[WIFI_MAX_CALLBACKS];
size_t count;
mutex_lock(&ctx->callbackMutex);
count = ctx->callbackCount;
memcpy(local, ctx->callbacks, count * sizeof(WifiCallbackEntry));
mutex_unlock(&ctx->callbackMutex);
for (size_t i = 0; i < count; i++) {
local[i].fn(ctx->device, local[i].ctx, event);
mutex_lock(&ctx->subscriptionsMutex);
for (WifiEventSubscription* sub = ctx->subscriptions; sub != nullptr; sub = sub->internal.next) {
mutex_lock(&sub->internal.ring_mutex);
if (sub->internal.count < WIFI_EVENT_QUEUE_CAPACITY) {
uint8_t tail = (sub->internal.head + sub->internal.count) % WIFI_EVENT_QUEUE_CAPACITY;
sub->internal.queue[tail] = event;
sub->internal.count++;
}
mutex_unlock(&sub->internal.ring_mutex);
task_event_group_signal(sub->internal.event_group, sub->bit);
}
mutex_unlock(&ctx->subscriptionsMutex);
}
// ---- WifiApi ----
@@ -247,41 +241,105 @@ error_t apiStationGetRssi(Device* device, int32_t* rssi) {
return ERROR_NONE;
}
error_t apiAddEventCallback(Device* device, void* callback_context, WifiEventCallback callback) {
error_t apiSetRadioOn(Device* device) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || callback == nullptr) return ERROR_INVALID_ARGUMENT;
if (ctx == nullptr) return ERROR_INVALID_STATE;
mutex_lock(&ctx->callbackMutex);
if (ctx->callbackCount >= WIFI_MAX_CALLBACKS) {
mutex_unlock(&ctx->callbackMutex);
return ERROR_OUT_OF_MEMORY;
}
ctx->callbacks[ctx->callbackCount] = { .fn = callback, .ctx = callback_context };
ctx->callbackCount++;
mutex_unlock(&ctx->callbackMutex);
mutex_lock(&ctx->mutex);
bool already_on = ctx->radioState == WIFI_RADIO_STATE_ON;
ctx->radioState = WIFI_RADIO_STATE_ON;
mutex_unlock(&ctx->mutex);
if (already_on) return ERROR_NONE;
WifiEvent radio_event = {};
radio_event.type = WIFI_EVENT_TYPE_RADIO_STATE_CHANGED;
radio_event.radio_state = WIFI_RADIO_STATE_ON;
fireEvent(ctx, radio_event);
LOG_I(TAG, "WiFi radio on (mock)");
return ERROR_NONE;
}
error_t apiRemoveEventCallback(Device* device, WifiEventCallback callback) {
error_t apiSetRadioOff(Device* device) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || callback == nullptr) return ERROR_INVALID_ARGUMENT;
if (ctx == nullptr) return ERROR_INVALID_STATE;
mutex_lock(&ctx->callbackMutex);
for (size_t i = 0; i < ctx->callbackCount; i++) {
if (ctx->callbacks[i].fn == callback) {
for (size_t j = i; j + 1 < ctx->callbackCount; j++) {
ctx->callbacks[j] = ctx->callbacks[j + 1];
}
ctx->callbackCount--;
mutex_unlock(&ctx->callbackMutex);
return ERROR_NONE;
mutex_lock(&ctx->mutex);
bool already_off = ctx->radioState == WIFI_RADIO_STATE_OFF;
bool was_connected = ctx->stationState != WIFI_STATION_STATE_DISCONNECTED;
ctx->radioState = WIFI_RADIO_STATE_OFF;
ctx->stationState = WIFI_STATION_STATE_DISCONNECTED;
ctx->scanning = false;
mutex_unlock(&ctx->mutex);
if (already_off) return ERROR_NONE;
if (was_connected) {
WifiEvent station_event = {};
station_event.type = WIFI_EVENT_TYPE_STATION_STATE_CHANGED;
station_event.station_state = WIFI_STATION_STATE_DISCONNECTED;
fireEvent(ctx, station_event);
}
WifiEvent radio_event = {};
radio_event.type = WIFI_EVENT_TYPE_RADIO_STATE_CHANGED;
radio_event.radio_state = WIFI_RADIO_STATE_OFF;
fireEvent(ctx, radio_event);
LOG_I(TAG, "WiFi radio off (mock)");
return ERROR_NONE;
}
error_t apiEventSubscribe(Device* device, WifiEventSubscription* sub, TaskEventGroup* event_group) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || sub == nullptr || event_group == nullptr) return ERROR_INVALID_ARGUMENT;
uint32_t bit;
error_t claim_result = task_event_group_claim_bit(event_group, &bit);
if (claim_result != ERROR_NONE) {
return claim_result;
}
mutex_lock(&ctx->subscriptionsMutex);
// Avoid cyclic subscription list that would loop forever
if (ctx->subscriptions == sub) {
mutex_unlock(&ctx->subscriptionsMutex);
task_event_group_release_bit(event_group, bit);
return ERROR_INVALID_STATE;
}
sub->internal.event_group = event_group;
sub->bit = bit;
sub->internal.next = ctx->subscriptions;
ctx->subscriptions = sub;
mutex_unlock(&ctx->subscriptionsMutex);
return ERROR_NONE;
}
error_t apiEventUnsubscribe(Device* device, WifiEventSubscription* sub) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || sub == nullptr) return ERROR_INVALID_ARGUMENT;
error_t result = ERROR_NOT_FOUND;
mutex_lock(&ctx->subscriptionsMutex);
for (WifiEventSubscription** link = &ctx->subscriptions; *link != nullptr; link = &(*link)->internal.next) {
if (*link == sub) {
*link = sub->internal.next;
result = ERROR_NONE;
break;
}
}
mutex_unlock(&ctx->callbackMutex);
return ERROR_NOT_FOUND;
mutex_unlock(&ctx->subscriptionsMutex);
if (result == ERROR_NONE) {
task_event_group_release_bit(sub->internal.event_group, sub->bit);
}
return result;
}
const WifiApi posix_wifi_api = {
.set_radio_on = apiSetRadioOn,
.set_radio_off = apiSetRadioOff,
.get_radio_state = apiGetRadioState,
.get_station_state = apiGetStationState,
.get_access_point_state = apiGetAccessPointState,
@@ -293,13 +351,14 @@ const WifiApi posix_wifi_api = {
.station_connect = apiStationConnect,
.station_disconnect = apiStationDisconnect,
.station_get_rssi = apiStationGetRssi,
.add_event_callback = apiAddEventCallback,
.remove_event_callback = apiRemoveEventCallback
.event_subscribe = apiEventSubscribe,
.event_unsubscribe = apiEventUnsubscribe
};
// ---- Driver lifecycle ----
// Unlike the real esp32 driver, there's no hardware to bring up: the radio
// simply reports itself as ON as soon as the device is started.
// startDevice()/stopDevice() only allocate/free this driver's bookkeeping (subscriber list
// included), so event subscribers can stay subscribed across radio on/off toggles - the radio
// itself is only touched by apiSetRadioOn()/apiSetRadioOff().
error_t startDevice(Device* device) {
auto* ctx = new(std::nothrow) PosixWifiCtx();
@@ -307,12 +366,9 @@ error_t startDevice(Device* device) {
ctx->device = device;
mutex_construct(&ctx->mutex);
mutex_construct(&ctx->callbackMutex);
ctx->radioState = WIFI_RADIO_STATE_ON;
mutex_construct(&ctx->subscriptionsMutex);
device_set_driver_data(device, ctx);
LOG_I(TAG, "WiFi radio on (mock)");
return ERROR_NONE;
}
@@ -320,12 +376,28 @@ error_t stopDevice(Device* device) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr) return ERROR_NONE;
if (ctx->radioState == WIFI_RADIO_STATE_ON) {
apiSetRadioOff(device);
}
// Release any subscribers that never unsubscribed: device_stop() doesn't wait for apps still
// using this device, so a later wifi_event_unsubscribe() would find no ctx and skip releasing
// the bit and destructing sub->internal.ring_mutex.
mutex_lock(&ctx->subscriptionsMutex);
for (WifiEventSubscription* sub = ctx->subscriptions; sub != nullptr;) {
WifiEventSubscription* next = sub->internal.next;
task_event_group_release_bit(sub->internal.event_group, sub->bit);
mutex_destruct(&sub->internal.ring_mutex);
sub = next;
}
ctx->subscriptions = nullptr;
mutex_unlock(&ctx->subscriptionsMutex);
device_set_driver_data(device, nullptr);
mutex_destruct(&ctx->callbackMutex);
mutex_destruct(&ctx->subscriptionsMutex);
mutex_destruct(&ctx->mutex);
delete ctx;
LOG_I(TAG, "WiFi radio off (mock)");
return ERROR_NONE;
}
@@ -26,12 +26,6 @@ enum class RadioState {
Off,
};
/**
* @brief Get wifi pubsub that broadcasts Event objects
* @return PubSub
*/
std::shared_ptr<PubSub<WifiEvent>> getPubsub();
/** @return Get the current radio state */
RadioState getRadioState();
@@ -24,7 +24,6 @@ struct Context {
uint32_t appInstanceId;
ChatState state;
ChatView view = ChatView(this, &state);
service::espnow::ReceiverSubscription receiveSubscription = -1;
ChatSettingsData settings;
bool isFirstLaunch = false;
};
+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;
}

Some files were not shown because too many files have changed in this diff Show More