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 # 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`): 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. - `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()`. - 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`). 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 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
ble0 { ble0 {
-1
View File
@@ -18,7 +18,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
gpio0 { gpio0 {
-1
View File
@@ -18,7 +18,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
gpio0 { gpio0 {
-1
View File
@@ -19,7 +19,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
gpio0 { gpio0 {
@@ -19,7 +19,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
gpio0 { gpio0 {
-1
View File
@@ -18,7 +18,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
gpio0 { gpio0 {
-1
View File
@@ -19,7 +19,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
gpio0 { gpio0 {
-1
View File
@@ -18,7 +18,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
ble0 { ble0 {
-1
View File
@@ -19,7 +19,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
ble0 { ble0 {
-1
View File
@@ -17,7 +17,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
gpio0 { gpio0 {
-1
View File
@@ -20,7 +20,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
gpio0 { gpio0 {
@@ -20,7 +20,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
ble0 { ble0 {
@@ -20,7 +20,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
ble0 { ble0 {
@@ -20,7 +20,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
ble0 { ble0 {
@@ -18,7 +18,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
gpio0 { gpio0 {
@@ -18,7 +18,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
gpio0 { gpio0 {
@@ -20,7 +20,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
ble0 { ble0 {
-1
View File
@@ -24,7 +24,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
ble0 { ble0 {
-1
View File
@@ -11,7 +11,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
gpio0 { gpio0 {
@@ -11,7 +11,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
gpio0 { gpio0 {
@@ -11,7 +11,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
gpio0 { gpio0 {
@@ -19,7 +19,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
gpio0 { gpio0 {
@@ -21,7 +21,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
ble0 { ble0 {
@@ -18,7 +18,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
ble0 { ble0 {
@@ -26,7 +26,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
ble0 { ble0 {
@@ -48,7 +48,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
ble0 { ble0 {
@@ -23,7 +23,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
ble0 { ble0 {
-1
View File
@@ -46,7 +46,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
ble0 { ble0 {
@@ -19,7 +19,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
ble0 { ble0 {
@@ -15,7 +15,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
gpio0 { gpio0 {
@@ -19,7 +19,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
ble0 { ble0 {
-1
View File
@@ -23,7 +23,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
ble0 { ble0 {
@@ -27,7 +27,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
ble0 { ble0 {
@@ -27,7 +27,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
ble0 { ble0 {
@@ -28,7 +28,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
ble0 { ble0 {
-1
View File
@@ -24,7 +24,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
gpio0 { gpio0 {
@@ -31,7 +31,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
ble0 { ble0 {
@@ -23,7 +23,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
ble0 { ble0 {
@@ -31,7 +31,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
ble0 { ble0 {
@@ -19,7 +19,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
gpio0 { gpio0 {
@@ -24,7 +24,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
ble0 { ble0 {
-1
View File
@@ -24,7 +24,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
ble0 { ble0 {
-1
View File
@@ -22,7 +22,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
ble0 { ble0 {
@@ -21,7 +21,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
ble0 { ble0 {
@@ -20,7 +20,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
ble0 { ble0 {
@@ -21,7 +21,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
ble0 { ble0 {
@@ -21,7 +21,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
ble0 { ble0 {
@@ -20,7 +20,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
ble0 { ble0 {
@@ -20,7 +20,6 @@
wifi0 { wifi0 {
compatible = "espressif,esp32-wifi-pinned"; compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
}; };
ble0 { ble0 {
+7 -3
View File
@@ -11,11 +11,15 @@
## Higher Priority ## 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. - AppHubApp: Prevent download callbacks from accessing a destroyed view.
- Move USB host task stacks to SPIRAM when available: esp32_usbhost*.cpp - 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. - Get rid of WiFi service (Wifi.cpp/h) in Tactility.cpp
When that's changed reduce LVGL callstack size in Tactility.cpp run()
- Make it more clear to end-users that an SD card is required to run Tactility - 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) - 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 - Wrap file operations like fopen/fclose with file_mutex
+55 -21
View File
@@ -6,15 +6,14 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include <tactility/concurrent/task_event_group.h>
#include <tactility/error.h> #include <tactility/error.h>
#include <tactility/freertos/freertos.h>
#include <tactility/freertos/task.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #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 { enum AppEventType {
APP_EVENT_RESULT, // struct AppResultEventData APP_EVENT_RESULT, // struct AppResultEventData
APP_EVENT_CLOSE, // no data - terminate now, permanently 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 * 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 * (which coalesces to the latest value), this queues events by value (FIFO) since dropping an
* APP_EVENT_RESULT would be unacceptable. * APP_EVENT_RESULT would be unacceptable.
* @warning Fields other than `app_instance_id` are for internal use only; do not read or write * @warning Fields other than `bit` are for internal use only; do not read or write them
* them directly. * directly.
*/ */
struct AppEventSubscription { struct AppEventSubscription {
/** The app instance this subscription receives events for; set by the caller before app_event_subscribe(). */ /** Set by app_event_subscribe()/app_event_subscribe_with_app_id(). Read-only for the
AppInstanceId app_instance_id; * 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]; /** Caller-owned, borrowed; set by app_event_subscribe(). */
uint8_t head; struct TaskEventGroup* event_group;
uint8_t count;
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. * Register a subscription for events addressed to the calling app's own instance (identified via
* @warning Does not work in ISR context. * app_scheduler_current_app_id()).
* @param[in,out] sub subscription to register; caller sets @a sub->app_instance_id beforehand, * @warning Does not work in ISR context. Must be called from the app's own task.
* owns the storage, and must keep it alive (and stationary) until unsubscribed * @param[in,out] sub subscription to register; owns the storage, must stay alive (and
* @return ERROR_NONE on success * 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. * 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); 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_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 #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 * 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, * 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 * blocking for the app's whole lifetime - same contract as an external app's main(). Use
* @a app_instance_id identifying this running instance (use it with * 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 * app_event_subscribe()/window_manager_create()/etc.). The instance closes when this function
* returns - no separate call is needed. * returns - no separate call is needed.
* AppManifest::location.location holds this cast to void*. * 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; typedef void* AppRuntime;
@@ -16,9 +16,9 @@ error_t api_load(AppLocation location, AppRuntime* out_runtime) {
return ERROR_NONE; 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); auto entry = reinterpret_cast<AppMainFn>(runtime);
return entry(app_instance_id, argc, argv); return entry(argc, argv);
} }
void api_unload(AppRuntime /*unused*/) { void api_unload(AppRuntime /*unused*/) {
+44 -36
View File
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include <app/event.h> #include <app/event.h>
#include <app/scheduler.h>
#include <tactility/concurrent/mutex.h> #include <tactility/concurrent/mutex.h>
#include <tactility/time.h> #include <tactility/time.h>
@@ -7,7 +8,7 @@
/** /**
* Intrusive singly-linked list of subscriptions, keyed by app_instance_id. * 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 * 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; static AppEventSubscription* subscriptions = nullptr;
@@ -21,32 +22,55 @@ static AppEventMutex subscriptions_mutex;
extern "C" { extern "C" {
error_t app_event_subscribe(AppEventSubscription* sub) { error_t app_event_subscribe_with_app_id(AppEventSubscription* sub, TaskEventGroup* event_group, AppInstanceId app_instance_id) {
sub->task = xTaskGetCurrentTaskHandle(); uint32_t bit;
sub->head = 0; error_t claim_result = task_event_group_claim_bit(event_group, &bit);
sub->count = 0; if (claim_result != ERROR_NONE) {
return claim_result;
}
mutex_lock(&subscriptions_mutex.handle); 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; subscriptions = sub;
mutex_unlock(&subscriptions_mutex.handle); mutex_unlock(&subscriptions_mutex.handle);
return ERROR_NONE; 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 app_event_unsubscribe(AppEventSubscription* sub) {
error_t result = ERROR_NOT_FOUND; error_t result = ERROR_NOT_FOUND;
mutex_lock(&subscriptions_mutex.handle); 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) { if (*link == sub) {
*link = sub->next; *link = sub->internal.next;
result = ERROR_NONE; result = ERROR_NONE;
break; break;
} }
} }
mutex_unlock(&subscriptions_mutex.handle); mutex_unlock(&subscriptions_mutex.handle);
if (result == ERROR_NONE) {
task_event_group_release_bit(sub->internal.event_group, sub->bit);
}
return result; return result;
} }
@@ -57,23 +81,23 @@ error_t app_event_emit(AppInstanceId app_instance_id, const AppEvent* event) {
error_t result = ERROR_NOT_FOUND; error_t result = ERROR_NOT_FOUND;
mutex_lock(&subscriptions_mutex.handle); mutex_lock(&subscriptions_mutex.handle);
for (AppEventSubscription* sub = subscriptions; sub != nullptr; sub = sub->next) { for (AppEventSubscription* sub = subscriptions; sub != nullptr; sub = sub->internal.next) {
if (sub->app_instance_id != app_instance_id) { if (sub->internal.app_instance_id != app_instance_id) {
continue; continue;
} }
if (sub->count >= APP_EVENT_QUEUE_CAPACITY) { if (sub->internal.count >= APP_EVENT_QUEUE_CAPACITY) {
result = ERROR_RESOURCE; result = ERROR_RESOURCE;
continue; continue;
} }
uint8_t tail = (sub->head + sub->count) % APP_EVENT_QUEUE_CAPACITY; uint8_t tail = (sub->internal.head + sub->internal.count) % APP_EVENT_QUEUE_CAPACITY;
sub->queue[tail] = stamped_event; sub->internal.queue[tail] = stamped_event;
sub->count++; sub->internal.count++;
if (result != ERROR_RESOURCE) { if (result != ERROR_RESOURCE) {
result = ERROR_NONE; result = ERROR_NONE;
} }
xTaskNotifyGive(sub->task); task_event_group_signal(sub->internal.event_group, sub->bit);
} }
mutex_unlock(&subscriptions_mutex.handle); 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) { static bool try_pop(AppEventSubscription* sub, AppEvent* out_event) {
mutex_lock(&subscriptions_mutex.handle); mutex_lock(&subscriptions_mutex.handle);
bool has_event = sub->count > 0; bool has_event = sub->internal.count > 0;
if (has_event) { if (has_event) {
*out_event = sub->queue[sub->head]; *out_event = sub->internal.queue[sub->internal.head];
sub->head = (sub->head + 1) % APP_EVENT_QUEUE_CAPACITY; sub->internal.head = (sub->internal.head + 1) % APP_EVENT_QUEUE_CAPACITY;
sub->count--; sub->internal.count--;
} }
mutex_unlock(&subscriptions_mutex.handle); mutex_unlock(&subscriptions_mutex.handle);
return has_event; return has_event;
} }
error_t app_event_await(AppEventSubscription* sub, AppEvent* out_event, TickType_t timeout) { error_t app_event_poll(AppEventSubscription* sub, AppEvent* out_event) {
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.
return try_pop(sub, out_event) ? ERROR_NONE : ERROR_TIMEOUT; return try_pop(sub, out_event) ? ERROR_NONE : ERROR_TIMEOUT;
} }
+3 -1
View File
@@ -8,6 +8,7 @@
#include <service/manager.h> #include <service/manager.h>
#include <tactility/concurrent/task_event_group.h>
#include <tactility/error.h> #include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
@@ -18,9 +19,10 @@ extern ServiceManifest app_internal_loader_service_manifest;
const ModuleSymbol app_module_symbols[] = { const ModuleSymbol app_module_symbols[] = {
// app/event // app/event
DEFINE_MODULE_SYMBOL(app_event_subscribe), 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_unsubscribe),
DEFINE_MODULE_SYMBOL(app_event_emit), DEFINE_MODULE_SYMBOL(app_event_emit),
DEFINE_MODULE_SYMBOL(app_event_await), DEFINE_MODULE_SYMBOL(app_event_poll),
// app/install // app/install
DEFINE_MODULE_SYMBOL(app_get_install_path), DEFINE_MODULE_SYMBOL(app_get_install_path),
DEFINE_MODULE_SYMBOL(app_install), DEFINE_MODULE_SYMBOL(app_install),
@@ -6,10 +6,12 @@
#include <tactility/delay.h> #include <tactility/delay.h>
#include <tactility/time.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 {}; AppEventSubscription sub {};
sub.app_instance_id = 1; CHECK_EQ(app_event_subscribe_with_app_id(&sub, &event_group, 1), ERROR_NONE);
CHECK_EQ(app_event_subscribe(&sub), ERROR_NONE);
for (uint32_t i = 0; i < 3; i++) { for (uint32_t i = 0; i < 3; i++) {
AppEvent event { .type = APP_EVENT_RESULT, .timestamp = 0, .result = { .launch_id = i, .result = 0 } }; 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++) { for (uint32_t i = 0; i < 3; i++) {
AppEvent out {}; 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.type, APP_EVENT_RESULT);
CHECK_EQ(out.result.launch_id, i); CHECK_EQ(out.result.launch_id, i);
} }
app_event_unsubscribe(&sub); app_event_unsubscribe(&sub);
task_event_group_destruct(&event_group);
} }
TEST_CASE("app_event_emit only delivers to subscriptions for that app_instance_id") { 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 {}; AppEventSubscription sub {};
sub.app_instance_id = 10; app_event_subscribe_with_app_id(&sub, &event_group, 10);
app_event_subscribe(&sub);
AppEvent event { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} }; AppEvent event { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
CHECK_EQ(app_event_emit(11, &event), ERROR_NOT_FOUND); CHECK_EQ(app_event_emit(11, &event), ERROR_NOT_FOUND);
AppEvent out {}; 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); 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") { 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 {}; AppEventSubscription sub {};
sub.app_instance_id = 20; app_event_subscribe_with_app_id(&sub, &event_group, 20);
app_event_subscribe(&sub);
for (uint32_t i = 0; i < APP_EVENT_QUEUE_CAPACITY; i++) { 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 } }; 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. // The already-queued events survive, in order, and the dropped one never arrives.
for (uint32_t i = 0; i < APP_EVENT_QUEUE_CAPACITY; i++) { for (uint32_t i = 0; i < APP_EVENT_QUEUE_CAPACITY; i++) {
AppEvent out {}; 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); CHECK_EQ(out.result.launch_id, i);
} }
AppEvent out {}; 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); app_event_unsubscribe(&sub);
task_event_group_destruct(&event_group);
} }
TEST_CASE("app_event_unsubscribe stops further delivery") { TEST_CASE("app_event_unsubscribe stops further delivery") {
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {}; AppEventSubscription sub {};
sub.app_instance_id = 30; app_event_subscribe_with_app_id(&sub, &event_group, 30);
app_event_subscribe(&sub);
CHECK_EQ(app_event_unsubscribe(&sub), ERROR_NONE); CHECK_EQ(app_event_unsubscribe(&sub), ERROR_NONE);
CHECK_EQ(app_event_unsubscribe(&sub), ERROR_NOT_FOUND); CHECK_EQ(app_event_unsubscribe(&sub), ERROR_NOT_FOUND);
AppEvent event { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} }; AppEvent event { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
CHECK_EQ(app_event_emit(30, &event), ERROR_NOT_FOUND); 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 {}; AppEventSubscription sub {};
sub.app_instance_id = 40; app_event_subscribe_with_app_id(&sub, &event_group, 40);
app_event_subscribe(&sub);
AppEvent out {}; 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); app_event_unsubscribe(&sub);
task_event_group_destruct(&event_group);
} }
TEST_CASE("app_event_emit stamps the event with the current boot-relative time") { 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 {}; AppEventSubscription sub {};
sub.app_instance_id = 50; app_event_subscribe_with_app_id(&sub, &event_group, 50);
app_event_subscribe(&sub);
auto before = static_cast<uint64_t>(get_micros_since_boot()); auto before = static_cast<uint64_t>(get_micros_since_boot());
AppEvent event { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} }; 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()); auto after = static_cast<uint64_t>(get_micros_since_boot());
AppEvent out {}; 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_GE(out.timestamp, before);
CHECK_LE(out.timestamp, after); CHECK_LE(out.timestamp, after);
app_event_unsubscribe(&sub); 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 {}; AppEventSubscription sub {};
sub.app_instance_id = 60; CHECK_EQ(app_event_subscribe_with_app_id(&sub, &event_group, 60), ERROR_NONE);
CHECK_EQ(app_event_subscribe(&sub), ERROR_NONE);
auto* thread = thread_alloc_full( auto* thread = thread_alloc_full(
"app-event-emitter", "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(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 {}; 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(out.type, APP_EVENT_CLOSE);
CHECK_EQ(thread_join(thread, pdMS_TO_TICKS(2000), 1), ERROR_NONE); CHECK_EQ(thread_join(thread, pdMS_TO_TICKS(2000), 1), ERROR_NONE);
thread_free(thread); thread_free(thread);
app_event_unsubscribe(&sub); app_event_unsubscribe(&sub);
task_event_group_destruct(&event_group);
} }
@@ -3,6 +3,7 @@
#include <app/event.h> #include <app/event.h>
#include <app/loader.h> #include <app/loader.h>
#include <app/manager.h> #include <app/manager.h>
#include <app/scheduler.h>
#include <service/manager.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 // 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 // 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()). // 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); stash_received_arguments(argc, argv);
if (argc == 1) { 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)); return static_cast<int32_t>(strtol(argv[0], nullptr, 10));
} }
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {}; AppEventSubscription sub {};
sub.app_instance_id = app_instance_id; app_event_subscribe(&sub, &event_group);
app_event_subscribe(&sub);
while (true) { while (true) {
AppEvent event {}; if (task_event_group_wait_any(&event_group, nullptr, pdMS_TO_TICKS(5000)) != ERROR_NONE) {
if (app_event_await(&sub, &event, pdMS_TO_TICKS(5000)) != ERROR_NONE) {
break; // safety net so a bug here can't hang the test suite 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); app_event_unsubscribe(&sub);
task_event_group_destruct(&event_group);
return 0; 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 - // 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. // 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[]) { int32_t fake_app_main(int argc, char* argv[]) {
return fake_run(nullptr, app_instance_id, argc, 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. // 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); REQUIRE_EQ(app_manager_start("test.app.parent", &parent_id), ERROR_NONE);
CHECK(wait_for_state(parent_id, APP_INSTANCE_STATE_ACTIVE, 1000)); 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 {}; AppEventSubscription parent_sub {};
parent_sub.app_instance_id = parent_id; REQUIRE_EQ(app_event_subscribe_with_app_id(&parent_sub, &parent_event_group, parent_id), ERROR_NONE);
REQUIRE_EQ(app_event_subscribe(&parent_sub), ERROR_NONE);
const char* argv[] = { "42" }; const char* argv[] = { "42" };
uint32_t child_id = 0; 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. // Launching a modal child never touches the parent's own task/state.
CHECK_EQ(app_manager_get_state(parent_id), APP_INSTANCE_STATE_ACTIVE); 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 {}; 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.type, APP_EVENT_RESULT);
CHECK_EQ(event.result.launch_id, child_id); CHECK_EQ(event.result.launch_id, child_id);
CHECK_EQ(event.result.result, 42); CHECK_EQ(event.result.result, 42);
app_event_unsubscribe(&parent_sub); app_event_unsubscribe(&parent_sub);
task_event_group_destruct(&parent_event_group);
app_manager_stop(child_id); app_manager_stop(child_id);
app_manager_stop(parent_id); app_manager_stop(parent_id);
app_manager_remove("test.app.parent"); 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); REQUIRE_EQ(app_manager_start("test.app.parent2", &parent_id), ERROR_NONE);
CHECK(wait_for_state(parent_id, APP_INSTANCE_STATE_ACTIVE, 1000)); 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 {}; AppEventSubscription parent_sub {};
parent_sub.app_instance_id = parent_id; REQUIRE_EQ(app_event_subscribe_with_app_id(&parent_sub, &parent_event_group, parent_id), ERROR_NONE);
REQUIRE_EQ(app_event_subscribe(&parent_sub), ERROR_NONE);
uint32_t child_id = 0; uint32_t child_id = 0;
// No parameters - fake_run falls through to its normal CLOSE loop instead of acting as a // 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 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 {}; 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.type, APP_EVENT_RESULT);
CHECK_EQ(event.result.launch_id, child_id); CHECK_EQ(event.result.launch_id, child_id);
CHECK_EQ(event.result.result, 0); // fake_run's CLOSE loop always returns 0 CHECK_EQ(event.result.result, 0); // fake_run's CLOSE loop always returns 0
app_event_unsubscribe(&parent_sub); app_event_unsubscribe(&parent_sub);
task_event_group_destruct(&parent_event_group);
app_manager_stop(parent_id); app_manager_stop(parent_id);
app_manager_remove("test.app.parent2"); app_manager_remove("test.app.parent2");
app_manager_remove("test.app.child2"); 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} SRCS ${SOURCES}
INCLUDE_DIRS "include/" INCLUDE_DIRS "include/"
PRIV_INCLUDE_DIRS "private/" 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} PRIV_REQUIRES ${PRIV_REQUIRES_LIST}
) )
@@ -18,6 +18,8 @@
#include <tactility/log.h> #include <tactility/log.h>
#include <tactility/time.h> #include <tactility/time.h>
#include <TactilityCpp/Allocator.h>
#if defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED) #if defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
#include <tactility/drivers/esp32_esp_hosted_ota.h> #include <tactility/drivers/esp32_esp_hosted_ota.h>
#endif #endif
@@ -33,14 +35,8 @@
namespace { namespace {
constexpr size_t WIFI_MAX_CALLBACKS = 4;
constexpr uint16_t WIFI_SCAN_RECORD_LIMIT = 32; constexpr uint16_t WIFI_SCAN_RECORD_LIMIT = 32;
struct WifiCallbackEntry {
WifiEventCallback fn = nullptr;
void* ctx = nullptr;
};
struct Esp32WifiCtx { struct Esp32WifiCtx {
Device* device = nullptr; Device* device = nullptr;
@@ -66,9 +62,8 @@ struct Esp32WifiCtx {
int32_t lastEventId = -1; int32_t lastEventId = -1;
TickType_t lastEventTick = 0; TickType_t lastEventTick = 0;
Mutex callbackMutex{}; Mutex subscriptionsMutex{};
WifiCallbackEntry callbacks[WIFI_MAX_CALLBACKS] = {}; WifiEventSubscription* subscriptions = nullptr;
size_t callbackCount = 0;
}; };
#define GET_CTX(device) (static_cast<Esp32WifiCtx*>(device_get_driver_data(device))) #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) { void fire_event(Esp32WifiCtx* ctx, WifiEvent event) {
WifiCallbackEntry local[WIFI_MAX_CALLBACKS]; mutex_lock(&ctx->subscriptionsMutex);
size_t count; for (WifiEventSubscription* sub = ctx->subscriptions; sub != nullptr; sub = sub->internal.next) {
mutex_lock(&ctx->callbackMutex); mutex_lock(&sub->internal.ring_mutex);
count = ctx->callbackCount; if (sub->internal.count < WIFI_EVENT_QUEUE_CAPACITY) {
memcpy(local, ctx->callbacks, count * sizeof(WifiCallbackEntry)); uint8_t tail = (sub->internal.head + sub->internal.count) % WIFI_EVENT_QUEUE_CAPACITY;
mutex_unlock(&ctx->callbackMutex); sub->internal.queue[tail] = event;
sub->internal.count++;
for (size_t i = 0; i < count; i++) { }
local[i].fn(ctx->device, local[i].ctx, event); 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) ---- // ---- 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; ctx->radioState = WIFI_RADIO_STATE_ON;
mutex_unlock(&ctx->mutex); 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"); LOG_I(TAG, "WiFi radio on");
return ERROR_NONE; return ERROR_NONE;
} }
@@ -306,6 +308,11 @@ void bring_down_wifi(Esp32WifiCtx* ctx) {
ctx->radioState = WIFI_RADIO_STATE_OFF; ctx->radioState = WIFI_RADIO_STATE_OFF;
mutex_unlock(&ctx->mutex); 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"); LOG_I(TAG, "WiFi radio off");
} }
@@ -497,38 +504,77 @@ error_t api_station_get_rssi(Device* device, int32_t* rssi) {
return ERROR_NONE; 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); 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); mutex_lock(&ctx->mutex);
if (ctx->callbackCount >= WIFI_MAX_CALLBACKS) { bool already_on = ctx->radioState == WIFI_RADIO_STATE_ON;
mutex_unlock(&ctx->callbackMutex); mutex_unlock(&ctx->mutex);
return ERROR_OUT_OF_MEMORY; if (already_on) return ERROR_NONE;
}
ctx->callbacks[ctx->callbackCount] = { .fn = callback, .ctx = callback_context }; return bring_up_wifi(ctx);
ctx->callbackCount++; }
mutex_unlock(&ctx->callbackMutex);
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; 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); 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); uint32_t bit;
for (size_t i = 0; i < ctx->callbackCount; i++) { error_t claim_result = task_event_group_claim_bit(event_group, &bit);
if (ctx->callbacks[i].fn == callback) { if (claim_result != ERROR_NONE) {
for (size_t j = i; j + 1 < ctx->callbackCount; j++) { return claim_result;
ctx->callbacks[j] = ctx->callbacks[j + 1]; }
}
ctx->callbackCount--; mutex_lock(&ctx->subscriptionsMutex);
mutex_unlock(&ctx->callbackMutex);
return ERROR_NONE; // 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); mutex_unlock(&ctx->subscriptionsMutex);
return ERROR_NOT_FOUND;
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) { 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 = { 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_radio_state = api_get_radio_state,
.get_station_state = api_get_station_state, .get_station_state = api_get_station_state,
.get_access_point_state = api_get_access_point_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_connect = api_station_connect,
.station_disconnect = api_station_disconnect, .station_disconnect = api_station_disconnect,
.station_get_rssi = api_station_get_rssi, .station_get_rssi = api_station_get_rssi,
.add_event_callback = api_add_event_callback, .event_subscribe = api_event_subscribe,
.remove_event_callback = api_remove_event_callback, .event_unsubscribe = api_event_unsubscribe,
.get_firmware_ops = api_get_firmware_ops .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) { error_t start_device(Device* device) {
auto* ctx = new(std::nothrow) Esp32WifiCtx(); // Prefers PSRAM/SPIRAM, falling back to internal RAM when unavailable - aborts on true OOM
if (ctx == nullptr) return ERROR_OUT_OF_MEMORY; // (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; ctx->device = device;
mutex_construct(&ctx->mutex); mutex_construct(&ctx->mutex);
mutex_construct(&ctx->callbackMutex); mutex_construct(&ctx->subscriptionsMutex);
device_set_driver_data(device, ctx); 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; return ERROR_NONE;
} }
@@ -596,12 +633,28 @@ error_t stop_device(Device* device) {
auto* ctx = GET_CTX(device); auto* ctx = GET_CTX(device);
if (ctx == nullptr) return ERROR_NONE; 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); device_set_driver_data(device, nullptr);
mutex_destruct(&ctx->callbackMutex); mutex_destruct(&ctx->subscriptionsMutex);
mutex_destruct(&ctx->mutex); mutex_destruct(&ctx->mutex);
delete ctx; ctx->~Esp32WifiCtx();
tt::OptExternalAllocator<Esp32WifiCtx>().deallocate(ctx, 1);
return ERROR_NONE; return ERROR_NONE;
} }
@@ -41,6 +41,12 @@ struct Esp32WifiPinnedCtx {
}; };
#define GET_CTX(device) (static_cast<Esp32WifiPinnedCtx*>(device_get_driver_data(device))) #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 { struct MarshalledCall {
const std::function<void()>* work; 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); 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); auto* ctx = GET_CTX(device);
if (ctx == nullptr || ctx->child == nullptr) return ERROR_INVALID_STATE; 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); auto* ctx = GET_CTX(device);
if (ctx == nullptr || ctx->child == nullptr) return ERROR_INVALID_STATE; 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 ---- // ---- 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) { error_t api_scan(Device* device) {
auto* ctx = GET_CTX(device); auto* ctx = GET_CTX(device);
if (ctx == nullptr || ctx->child == nullptr) return ERROR_INVALID_STATE; 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 = { 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_radio_state = api_get_radio_state,
.get_station_state = api_get_station_state, .get_station_state = api_get_station_state,
.get_access_point_state = api_get_access_point_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_connect = api_station_connect,
.station_disconnect = api_station_disconnect, .station_disconnect = api_station_disconnect,
.station_get_rssi = api_station_get_rssi, .station_get_rssi = api_station_get_rssi,
.add_event_callback = api_add_event_callback, .event_subscribe = api_event_subscribe,
.remove_event_callback = api_remove_event_callback .event_unsubscribe = api_event_unsubscribe
}; };
// ---- Driver lifecycle ---- // ---- Driver lifecycle ----
// Starting/stopping the child (which brings the ESP-IDF WiFi stack up/down) // Starting/stopping the child (allocating/freeing its bookkeeping - see esp32_wifi.cpp's own
// also happens on the pinned thread, since esp_wifi_init/start/stop/deinit // start_device()/stop_device() comment) happens on the pinned thread for the same core-affinity
// are subject to the same core-affinity requirement as the state-changing // reason as the state-changing WifiApi calls above; enable()/disable() (which actually bring the
// WifiApi calls above. // ESP-IDF WiFi stack up/down) are marshalled the same way.
error_t start_device(Device* device) { error_t start_device(Device* device) {
auto* ctx = new(std::nothrow) Esp32WifiPinnedCtx(); auto* ctx = new(std::nothrow) Esp32WifiPinnedCtx();
@@ -19,8 +19,6 @@
namespace { namespace {
constexpr size_t WIFI_MAX_CALLBACKS = 4;
struct MockApRecord { struct MockApRecord {
const char* ssid; const char* ssid;
int8_t rssi; 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 int8_t MOCK_CONNECTED_RSSI = -30;
constexpr const char* MOCK_IPV4_ADDRESS = "192.168.1.2"; constexpr const char* MOCK_IPV4_ADDRESS = "192.168.1.2";
struct WifiCallbackEntry {
WifiEventCallback fn = nullptr;
void* ctx = nullptr;
};
struct PosixWifiCtx { struct PosixWifiCtx {
Device* device = nullptr; Device* device = nullptr;
@@ -54,24 +47,25 @@ struct PosixWifiCtx {
bool scanning = false; bool scanning = false;
char targetSsid[33] = {}; char targetSsid[33] = {};
Mutex callbackMutex {}; Mutex subscriptionsMutex {};
WifiCallbackEntry callbacks[WIFI_MAX_CALLBACKS] = {}; WifiEventSubscription* subscriptions = nullptr;
size_t callbackCount = 0;
}; };
#define GET_CTX(device) (static_cast<PosixWifiCtx*>(device_get_driver_data(device))) #define GET_CTX(device) (static_cast<PosixWifiCtx*>(device_get_driver_data(device)))
void fireEvent(PosixWifiCtx* ctx, WifiEvent event) { void fireEvent(PosixWifiCtx* ctx, WifiEvent event) {
WifiCallbackEntry local[WIFI_MAX_CALLBACKS]; mutex_lock(&ctx->subscriptionsMutex);
size_t count; for (WifiEventSubscription* sub = ctx->subscriptions; sub != nullptr; sub = sub->internal.next) {
mutex_lock(&ctx->callbackMutex); mutex_lock(&sub->internal.ring_mutex);
count = ctx->callbackCount; if (sub->internal.count < WIFI_EVENT_QUEUE_CAPACITY) {
memcpy(local, ctx->callbacks, count * sizeof(WifiCallbackEntry)); uint8_t tail = (sub->internal.head + sub->internal.count) % WIFI_EVENT_QUEUE_CAPACITY;
mutex_unlock(&ctx->callbackMutex); sub->internal.queue[tail] = event;
sub->internal.count++;
for (size_t i = 0; i < count; i++) { }
local[i].fn(ctx->device, local[i].ctx, event); mutex_unlock(&sub->internal.ring_mutex);
task_event_group_signal(sub->internal.event_group, sub->bit);
} }
mutex_unlock(&ctx->subscriptionsMutex);
} }
// ---- WifiApi ---- // ---- WifiApi ----
@@ -247,41 +241,105 @@ error_t apiStationGetRssi(Device* device, int32_t* rssi) {
return ERROR_NONE; return ERROR_NONE;
} }
error_t apiAddEventCallback(Device* device, void* callback_context, WifiEventCallback callback) { error_t apiSetRadioOn(Device* device) {
auto* ctx = GET_CTX(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); mutex_lock(&ctx->mutex);
if (ctx->callbackCount >= WIFI_MAX_CALLBACKS) { bool already_on = ctx->radioState == WIFI_RADIO_STATE_ON;
mutex_unlock(&ctx->callbackMutex); ctx->radioState = WIFI_RADIO_STATE_ON;
return ERROR_OUT_OF_MEMORY; mutex_unlock(&ctx->mutex);
} if (already_on) return ERROR_NONE;
ctx->callbacks[ctx->callbackCount] = { .fn = callback, .ctx = callback_context };
ctx->callbackCount++; WifiEvent radio_event = {};
mutex_unlock(&ctx->callbackMutex); 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; return ERROR_NONE;
} }
error_t apiRemoveEventCallback(Device* device, WifiEventCallback callback) { error_t apiSetRadioOff(Device* device) {
auto* ctx = GET_CTX(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); mutex_lock(&ctx->mutex);
for (size_t i = 0; i < ctx->callbackCount; i++) { bool already_off = ctx->radioState == WIFI_RADIO_STATE_OFF;
if (ctx->callbacks[i].fn == callback) { bool was_connected = ctx->stationState != WIFI_STATION_STATE_DISCONNECTED;
for (size_t j = i; j + 1 < ctx->callbackCount; j++) { ctx->radioState = WIFI_RADIO_STATE_OFF;
ctx->callbacks[j] = ctx->callbacks[j + 1]; ctx->stationState = WIFI_STATION_STATE_DISCONNECTED;
} ctx->scanning = false;
ctx->callbackCount--; mutex_unlock(&ctx->mutex);
mutex_unlock(&ctx->callbackMutex); if (already_off) return ERROR_NONE;
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); mutex_unlock(&ctx->subscriptionsMutex);
return ERROR_NOT_FOUND;
if (result == ERROR_NONE) {
task_event_group_release_bit(sub->internal.event_group, sub->bit);
}
return result;
} }
const WifiApi posix_wifi_api = { const WifiApi posix_wifi_api = {
.set_radio_on = apiSetRadioOn,
.set_radio_off = apiSetRadioOff,
.get_radio_state = apiGetRadioState, .get_radio_state = apiGetRadioState,
.get_station_state = apiGetStationState, .get_station_state = apiGetStationState,
.get_access_point_state = apiGetAccessPointState, .get_access_point_state = apiGetAccessPointState,
@@ -293,13 +351,14 @@ const WifiApi posix_wifi_api = {
.station_connect = apiStationConnect, .station_connect = apiStationConnect,
.station_disconnect = apiStationDisconnect, .station_disconnect = apiStationDisconnect,
.station_get_rssi = apiStationGetRssi, .station_get_rssi = apiStationGetRssi,
.add_event_callback = apiAddEventCallback, .event_subscribe = apiEventSubscribe,
.remove_event_callback = apiRemoveEventCallback .event_unsubscribe = apiEventUnsubscribe
}; };
// ---- Driver lifecycle ---- // ---- Driver lifecycle ----
// Unlike the real esp32 driver, there's no hardware to bring up: the radio // startDevice()/stopDevice() only allocate/free this driver's bookkeeping (subscriber list
// simply reports itself as ON as soon as the device is started. // 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) { error_t startDevice(Device* device) {
auto* ctx = new(std::nothrow) PosixWifiCtx(); auto* ctx = new(std::nothrow) PosixWifiCtx();
@@ -307,12 +366,9 @@ error_t startDevice(Device* device) {
ctx->device = device; ctx->device = device;
mutex_construct(&ctx->mutex); mutex_construct(&ctx->mutex);
mutex_construct(&ctx->callbackMutex); mutex_construct(&ctx->subscriptionsMutex);
ctx->radioState = WIFI_RADIO_STATE_ON;
device_set_driver_data(device, ctx); device_set_driver_data(device, ctx);
LOG_I(TAG, "WiFi radio on (mock)");
return ERROR_NONE; return ERROR_NONE;
} }
@@ -320,12 +376,28 @@ error_t stopDevice(Device* device) {
auto* ctx = GET_CTX(device); auto* ctx = GET_CTX(device);
if (ctx == nullptr) return ERROR_NONE; 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); device_set_driver_data(device, nullptr);
mutex_destruct(&ctx->callbackMutex); mutex_destruct(&ctx->subscriptionsMutex);
mutex_destruct(&ctx->mutex); mutex_destruct(&ctx->mutex);
delete ctx; delete ctx;
LOG_I(TAG, "WiFi radio off (mock)");
return ERROR_NONE; return ERROR_NONE;
} }
@@ -26,12 +26,6 @@ enum class RadioState {
Off, Off,
}; };
/**
* @brief Get wifi pubsub that broadcasts Event objects
* @return PubSub
*/
std::shared_ptr<PubSub<WifiEvent>> getPubsub();
/** @return Get the current radio state */ /** @return Get the current radio state */
RadioState getRadioState(); RadioState getRadioState();
@@ -24,7 +24,6 @@ struct Context {
uint32_t appInstanceId; uint32_t appInstanceId;
ChatState state; ChatState state;
ChatView view = ChatView(this, &state); ChatView view = ChatView(this, &state);
service::espnow::ReceiverSubscription receiveSubscription = -1;
ChatSettingsData settings; ChatSettingsData settings;
bool isFirstLaunch = false; bool isFirstLaunch = false;
}; };
+1 -3
View File
@@ -141,7 +141,6 @@ namespace app {
namespace boot { extern const ::AppManifest manifest; } namespace boot { extern const ::AppManifest manifest; }
namespace development { extern const ::AppManifest manifest; } namespace development { extern const ::AppManifest manifest; }
namespace display { extern const ::AppManifest manifest; } namespace display { extern const ::AppManifest manifest; }
namespace kerneldisplay { extern const ::AppManifest manifest; }
namespace files { extern const ::AppManifest manifest; } namespace files { extern const ::AppManifest manifest; }
namespace fileselection { extern const ::AppManifest manifest; } namespace fileselection { extern const ::AppManifest manifest; }
namespace gpssettings { extern const ::AppManifest manifest; } namespace gpssettings { extern const ::AppManifest manifest; }
@@ -206,7 +205,7 @@ static void registerInternalApps() {
app_manager_add(&app::audiosettings::manifest); app_manager_add(&app::audiosettings::manifest);
} }
if (device_exists_of_type(&DISPLAY_TYPE)) { 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::files::manifest);
app_manager_add(&app::fileselection::manifest); app_manager_add(&app::fileselection::manifest);
@@ -519,7 +518,6 @@ void run(Module* const dtsModules[], const DtsDevice dtsDevices[]) {
.on_start = onLvglStarted, .on_start = onLvglStarted,
.on_stop = onLvglStopped, .on_stop = onLvglStopped,
.task_priority = THREAD_PRIORITY_HIGHER, .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, .task_stack_size = 9120,
#ifdef ESP_PLATFORM #ifdef ESP_PLATFORM
.task_affinity = getCpuAffinityConfiguration().graphics .task_affinity = getCpuAffinityConfiguration().graphics
+24 -16
View File
@@ -5,11 +5,13 @@
#include <app/event.h> #include <app/event.h>
#include <app/manager.h> #include <app/manager.h>
#include <app/manifest.h> #include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h> #include <lvgl_window_manager/window_manager.h>
#include <lvgl/icons/shared.h> #include <lvgl/icons/shared.h>
#include <lvgl/widgets/toolbar.h> #include <lvgl/widgets/toolbar.h>
#include <tactility/check.h>
#include <tactility/drivers/uart_controller.h> #include <tactility/drivers/uart_controller.h>
#include <tactility/log.h> #include <tactility/log.h>
@@ -199,36 +201,42 @@ void createWidgets(lv_obj_t* parent, void* userData) {
// endregion // 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 {}; Context ctx {};
ctx.appInstanceId = appInstanceId; ctx.appInstanceId = appInstanceId;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {}; AppEventSubscription sub {};
sub.app_instance_id = appInstanceId; check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
app_event_subscribe(&sub);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx); WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false; bool shouldClose = false;
while (!shouldClose) { while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {}; AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) { while (app_event_poll(&sub, &event) == ERROR_NONE) {
break; switch (event.type) {
} case APP_EVENT_CLOSE:
switch (event.type) { shouldClose = true;
case APP_EVENT_CLOSE: break;
shouldClose = true; case APP_EVENT_RESULT:
break; app_manager_stop(event.result.launch_id);
case APP_EVENT_RESULT: break;
app_manager_stop(event.result.launch_id); default:
break; break;
default: }
break; if (shouldClose) break;
} }
} }
window_manager_remove(window); window_manager_remove(window);
app_event_unsubscribe(&sub); check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0; return 0;
} }
@@ -3,9 +3,11 @@
#include <app/event.h> #include <app/event.h>
#include <app/manager.h> #include <app/manager.h>
#include <app/manifest.h> #include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h> #include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <tactility/log.h> #include <tactility/log.h>
#include <lvgl.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 }; Context ctx { appInstanceId };
ctx.argc = argc; ctx.argc = argc;
ctx.argv = argv; ctx.argv = argv;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {}; AppEventSubscription sub {};
sub.app_instance_id = appInstanceId; check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
app_event_subscribe(&sub);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx); WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
while (true) { while (true) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
bool shouldClose = false;
AppEvent event {}; AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) { while (app_event_poll(&sub, &event) == ERROR_NONE) {
break; if (event.type == APP_EVENT_CLOSE) {
} shouldClose = true;
if (event.type == APP_EVENT_CLOSE) { break;
break; }
} }
if (shouldClose) break;
} }
window_manager_remove(window); window_manager_remove(window);
app_event_unsubscribe(&sub); check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return ctx.result; return ctx.result;
} }
+29 -21
View File
@@ -2,6 +2,7 @@
#include <app/manager.h> #include <app/manager.h>
#include <app/manifest.h> #include <app/manifest.h>
#include <app/install.h> #include <app/install.h>
#include <app/scheduler.h>
#include <format> #include <format>
@@ -15,6 +16,7 @@
#include <Tactility/file/File.h> #include <Tactility/file/File.h>
#include <Tactility/lvgl/Style.h> #include <Tactility/lvgl/Style.h>
#include <tactility/check.h>
#include <tactility/log.h> #include <tactility/log.h>
constexpr auto* TAG = "AppDetails"; 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 {}; Context ctx {};
ctx.appInstanceId = appInstanceId; ctx.appInstanceId = appInstanceId;
@@ -114,38 +117,43 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
return 0; return 0;
} }
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {}; AppEventSubscription sub {};
sub.app_instance_id = appInstanceId; check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
app_event_subscribe(&sub);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx); WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false; bool shouldClose = false;
while (!shouldClose) { while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {}; AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) { while (app_event_poll(&sub, &event) == ERROR_NONE) {
break; switch (event.type) {
} case APP_EVENT_CLOSE:
switch (event.type) { shouldClose = true;
case APP_EVENT_CLOSE: break;
shouldClose = true; case APP_EVENT_RESULT:
break; if (event.result.launch_id == ctx.pendingUninstallDialogId) {
case APP_EVENT_RESULT: if (event.result.result == 0) { // 0 = Yes
if (event.result.launch_id == ctx.pendingUninstallDialogId) { app_uninstall(ctx.targetManifest.id);
if (event.result.result == 0) { // 0 = Yes shouldClose = true;
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); window_manager_remove(window);
app_event_unsubscribe(&sub); check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0; return 0;
} }
+24 -16
View File
@@ -10,9 +10,11 @@
#include <app/event.h> #include <app/event.h>
#include <app/manager.h> #include <app/manager.h>
#include <app/manifest.h> #include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h> #include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <tactility/log.h> #include <tactility/log.h>
#include <lvgl/lvgl.h> #include <lvgl/lvgl.h>
@@ -49,7 +51,7 @@ void refresh(Context* ctx);
void onBackPressed(lv_event_t* event) { void onBackPressed(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(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); app_event_emit(ctx->appInstanceId, &closeEvent);
} }
@@ -197,33 +199,39 @@ void destroyWidgets(void* userData) {
ctx->refreshButton = nullptr; 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; Context ctx;
ctx.appInstanceId = appInstanceId; ctx.appInstanceId = appInstanceId;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {}; AppEventSubscription sub {};
sub.app_instance_id = appInstanceId; check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
app_event_subscribe(&sub);
WindowId window = window_manager_create_ext(appInstanceId, createWidgets, destroyWidgets, &ctx); WindowId window = window_manager_create_ext(appInstanceId, createWidgets, destroyWidgets, &ctx);
bool shouldClose = false; bool shouldClose = false;
while (!shouldClose) { while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {}; AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) { while (app_event_poll(&sub, &event) == ERROR_NONE) {
break; switch (event.type) {
} case APP_EVENT_CLOSE:
switch (event.type) { shouldClose = true;
case APP_EVENT_CLOSE: break;
shouldClose = true; default:
break; break;
default: }
break; if (shouldClose) break;
} }
} }
window_manager_remove(window); window_manager_remove(window);
app_event_unsubscribe(&sub); check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0; return 0;
} }
@@ -234,7 +242,7 @@ extern const ::AppManifest manifest = {
.id = "tactility.apphub", .id = "tactility.apphub",
.name = "App Hub", .name = "App Hub",
.category = APP_CATEGORY_SYSTEM, .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/metadata.h>
#include <app/manager.h> #include <app/manager.h>
#include <app/manifest.h> #include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h> #include <lvgl_window_manager/window_manager.h>
#include <lvgl/lvgl.h> #include <lvgl/lvgl.h>
#include <lvgl/widgets/toolbar.h> #include <lvgl/widgets/toolbar.h>
#include <tactility/check.h>
#include <tactility/log.h> #include <tactility/log.h>
#include <atomic> #include <atomic>
@@ -211,7 +213,8 @@ void createWidgets(lv_obj_t* parent, void* userData) {
updateViews(ctx); 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, // argv layout: [0]=appId, [1]=appVersionName, [2]=appVersionCode, [3]=appName,
// [4]=appDescription, [5]=targetSdk, [6]=file, [7..argc)=targetPlatforms. // [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 {}; AppEventSubscription sub {};
sub.app_instance_id = appInstanceId; check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
app_event_subscribe(&sub);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx); WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false; bool shouldClose = false;
while (!shouldClose) { while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {}; AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) { while (app_event_poll(&sub, &event) == ERROR_NONE) {
break; switch (event.type) {
} case APP_EVENT_CLOSE:
switch (event.type) { shouldClose = true;
case APP_EVENT_CLOSE: break;
shouldClose = true; case APP_EVENT_RESULT: {
break; bool confirmed = event.result.result == CONFIRMATION_BUTTON_INDEX;
case APP_EVENT_RESULT: { if (event.result.launch_id == ctx.installDialogId && confirmed) {
bool confirmed = event.result.result == CONFIRMATION_BUTTON_INDEX; installApp(&ctx);
if (event.result.launch_id == ctx.installDialogId && confirmed) { } else if (event.result.launch_id == ctx.uninstallDialogId && confirmed) {
installApp(&ctx); uninstallApp(&ctx);
} else if (event.result.launch_id == ctx.uninstallDialogId && confirmed) { } else if (event.result.launch_id == ctx.updateDialogId && confirmed) {
uninstallApp(&ctx); updateApp(&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); default:
break; break;
} }
default: if (shouldClose) break;
break;
} }
} }
window_manager_remove(window); window_manager_remove(window);
app_event_unsubscribe(&sub); check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0; return 0;
} }
+20 -9
View File
@@ -1,9 +1,12 @@
#include <app/event.h> #include <app/event.h>
#include <app/manager.h> #include <app/manager.h>
#include <app/manifest.h> #include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h> #include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <lvgl.h> #include <lvgl.h>
#include <algorithm> #include <algorithm>
#include <cstring> #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; appListInstanceId = appInstanceId;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {}; AppEventSubscription sub {};
sub.app_instance_id = appInstanceId; check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
app_event_subscribe(&sub);
WindowId window = window_manager_create(appInstanceId, createWidgets, nullptr); WindowId window = window_manager_create(appInstanceId, createWidgets, nullptr);
while (true) { while (true) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
bool shouldClose = false;
AppEvent event {}; AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) { while (app_event_poll(&sub, &event) == ERROR_NONE) {
break; if (event.type == APP_EVENT_CLOSE) {
} shouldClose = true;
if (event.type == APP_EVENT_CLOSE) { break;
break; }
} }
if (shouldClose) break;
} }
window_manager_remove(window); window_manager_remove(window);
app_event_unsubscribe(&sub); check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0; return 0;
} }
@@ -6,9 +6,12 @@
#include <app/event.h> #include <app/event.h>
#include <app/manager.h> #include <app/manager.h>
#include <app/manifest.h> #include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h> #include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <lvgl/widgets/toolbar.h> #include <lvgl/widgets/toolbar.h>
#include <lvgl.h> #include <lvgl.h>
#include <algorithm> #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; appSettingsInstanceId = appInstanceId;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {}; AppEventSubscription sub {};
sub.app_instance_id = appInstanceId; check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
app_event_subscribe(&sub);
WindowId window = window_manager_create(appInstanceId, createWidgets, nullptr); WindowId window = window_manager_create(appInstanceId, createWidgets, nullptr);
bool shouldClose = false; bool shouldClose = false;
while (!shouldClose) { while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {}; AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) { while (app_event_poll(&sub, &event) == ERROR_NONE) {
break; switch (event.type) {
} case APP_EVENT_CLOSE:
switch (event.type) { shouldClose = true;
case APP_EVENT_CLOSE: break;
shouldClose = true; default:
break; break;
default: }
break; if (shouldClose) break;
} }
} }
window_manager_remove(window); window_manager_remove(window);
app_event_unsubscribe(&sub); check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0; return 0;
} }
@@ -7,11 +7,13 @@
#include <app/event.h> #include <app/event.h>
#include <app/manager.h> #include <app/manager.h>
#include <app/manifest.h> #include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h> #include <lvgl_window_manager/window_manager.h>
#include <lvgl.h> #include <lvgl.h>
#include <lvgl/widgets/toolbar.h> #include <lvgl/widgets/toolbar.h>
#include <tactility/check.h>
#include <tactility/log.h> #include <tactility/log.h>
namespace tt::app::apwebserver { namespace tt::app::apwebserver {
@@ -121,29 +123,34 @@ void createWidgets(lv_obj_t* parent, void* userData) {
ctx->webServerEnabledChanged = true; 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 {}; Context ctx {};
ctx.appInstanceId = appInstanceId; ctx.appInstanceId = appInstanceId;
ctx.wsSettings = settings::webserver::loadOrGetDefault(); ctx.wsSettings = settings::webserver::loadOrGetDefault();
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {}; AppEventSubscription sub {};
sub.app_instance_id = appInstanceId; check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
app_event_subscribe(&sub);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx); WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false; bool shouldClose = false;
while (!shouldClose) { while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {}; AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) { while (app_event_poll(&sub, &event) == ERROR_NONE) {
break; switch (event.type) {
} case APP_EVENT_CLOSE:
switch (event.type) { shouldClose = true;
case APP_EVENT_CLOSE: break;
shouldClose = true; default:
break; break;
default: }
break; if (shouldClose) break;
} }
} }
@@ -167,7 +174,8 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
}); });
window_manager_remove(window); window_manager_remove(window);
app_event_unsubscribe(&sub); check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0; return 0;
} }
@@ -5,9 +5,12 @@
#include <app/event.h> #include <app/event.h>
#include <app/manager.h> #include <app/manager.h>
#include <app/manifest.h> #include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h> #include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <lvgl/lvgl.h> #include <lvgl/lvgl.h>
#include <lvgl/widgets/sliderbox.h> #include <lvgl/widgets/sliderbox.h>
#include <lvgl/widgets/toolbar.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 {}; Context ctx {};
ctx.appInstanceId = appInstanceId; ctx.appInstanceId = appInstanceId;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {}; AppEventSubscription sub {};
sub.app_instance_id = appInstanceId; check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
app_event_subscribe(&sub);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx); WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false; bool shouldClose = false;
while (!shouldClose) { while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {}; AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) { while (app_event_poll(&sub, &event) == ERROR_NONE) {
break; switch (event.type) {
} case APP_EVENT_CLOSE:
switch (event.type) { shouldClose = true;
case APP_EVENT_CLOSE: break;
shouldClose = true; default:
break; break;
default: }
break; if (shouldClose) break;
} }
} }
@@ -242,7 +250,8 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
} }
window_manager_remove(window); window_manager_remove(window);
app_event_unsubscribe(&sub); check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0; return 0;
} }
+19 -9
View File
@@ -1,5 +1,6 @@
#include "tactility/system_event.h" #include "tactility/system_event.h"
#include <tactility/check.h>
#include <tactility/delay.h> #include <tactility/delay.h>
#include <tactility/drivers/backlight.h> #include <tactility/drivers/backlight.h>
#include <tactility/drivers/display.h> #include <tactility/drivers/display.h>
@@ -9,6 +10,7 @@
#include <app/event.h> #include <app/event.h>
#include <app/manager.h> #include <app/manager.h>
#include <app/manifest.h> #include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.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); 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; bootAppInstanceId = appInstanceId;
const auto start_time = get_ticks(); const auto start_time = get_ticks();
@@ -292,9 +295,11 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
isUsbBootSplash = hal::usb::isUsbBootMode(); isUsbBootSplash = hal::usb::isUsbBootMode();
sdCardMissing = false; sdCardMissing = false;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {}; AppEventSubscription sub {};
sub.app_instance_id = appInstanceId; check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
app_event_subscribe(&sub);
bootWindowId = window_manager_create(appInstanceId, createSplashWidgets, nullptr); 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 // startNextApp() above is what triggers that, via app-module's "save the previously active
// app" policy, unless sdCardMissing halted before it. // app" policy, unless sdCardMissing halted before it.
while (true) { while (true) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
bool shouldClose = false;
AppEvent event {}; AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) { while (app_event_poll(&sub, &event) == ERROR_NONE) {
break; if (event.type == APP_EVENT_CLOSE) {
} shouldClose = true;
if (event.type == APP_EVENT_CLOSE) { break;
break; }
} }
if (shouldClose) break;
} }
if (bootWindowId != 0) { if (bootWindowId != 0) {
window_manager_remove(bootWindowId); window_manager_remove(bootWindowId);
} }
app_event_unsubscribe(&sub); check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0; return 0;
} }
+21 -13
View File
@@ -8,9 +8,11 @@
#include <app/event.h> #include <app/event.h>
#include <app/manager.h> #include <app/manager.h>
#include <app/manifest.h> #include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h> #include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <tactility/log.h> #include <tactility/log.h>
namespace tt::app::btmanage { namespace tt::app::btmanage {
@@ -199,7 +201,8 @@ void createWidgets(lv_obj_t* parent, void* userData) {
ctx->unlock(); 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; Context ctx;
ctx.appInstanceId = appInstanceId; ctx.appInstanceId = appInstanceId;
ctx.bindings = (Bindings) { ctx.bindings = (Bindings) {
@@ -220,9 +223,11 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
ctx.state.updateScanResults(); ctx.state.updateScanResults();
ctx.state.updatePairedPeers(); ctx.state.updatePairedPeers();
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {}; AppEventSubscription sub {};
sub.app_instance_id = appInstanceId; check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
app_event_subscribe(&sub);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx); 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; bool shouldClose = false;
while (!shouldClose) { while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {}; AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) { while (app_event_poll(&sub, &event) == ERROR_NONE) {
break; switch (event.type) {
} case APP_EVENT_CLOSE:
switch (event.type) { shouldClose = true;
case APP_EVENT_CLOSE: break;
shouldClose = true; default:
break; break;
default: }
break; if (shouldClose) break;
} }
} }
@@ -270,7 +277,8 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
} }
window_manager_remove(window); window_manager_remove(window);
app_event_unsubscribe(&sub); check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0; return 0;
} }
@@ -11,9 +11,11 @@
#include <app/event.h> #include <app/event.h>
#include <app/manager.h> #include <app/manager.h>
#include <app/manifest.h> #include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h> #include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <tactility/device.h> #include <tactility/device.h>
#include <tactility/drivers/bluetooth.h> #include <tactility/drivers/bluetooth.h>
#include <tactility/log.h> #include <tactility/log.h>
@@ -178,7 +180,8 @@ void createWidgets(lv_obj_t* parent, void* userData) {
updateViews(ctx); 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 {}; Context ctx {};
ctx.appInstanceId = appInstanceId; ctx.appInstanceId = appInstanceId;
@@ -199,38 +202,42 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
device_put(btDevice); device_put(btDevice);
} }
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {}; AppEventSubscription sub {};
sub.app_instance_id = appInstanceId; check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
app_event_subscribe(&sub);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx); WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false; bool shouldClose = false;
while (!shouldClose) { while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {}; AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) { while (app_event_poll(&sub, &event) == ERROR_NONE) {
break; switch (event.type) {
} case APP_EVENT_CLOSE:
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);
shouldClose = true; shouldClose = true;
} break;
app_manager_stop(event.result.launch_id); case APP_EVENT_RESULT:
break; if (event.result.result == 0) { // 0 = Yes
default: if (isCurrentlyConnected(&ctx)) {
break; 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); window_manager_remove(window);
app_event_unsubscribe(&sub); check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0; return 0;
} }
+29 -22
View File
@@ -10,9 +10,11 @@
#include <app/event.h> #include <app/event.h>
#include <app/manager.h> #include <app/manager.h>
#include <app/manifest.h> #include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h> #include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <tactility/log.h> #include <tactility/log.h>
#include <lvgl/lvgl.h> #include <lvgl/lvgl.h>
@@ -167,7 +169,8 @@ void switchChannel(Context* ctx, const std::string& chatChannel) {
namespace { 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 {}; Context ctx {};
ctx.appInstanceId = appInstanceId; ctx.appInstanceId = appInstanceId;
ctx.isFirstLaunch = !settingsFileExists(); ctx.isFirstLaunch = !settingsFileExists();
@@ -178,40 +181,44 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
} }
enableEspNow(&ctx); 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) { [&ctx](const esp_now_recv_info_t* receiveInfo, const uint8_t* data, int length) {
onReceive(&ctx, receiveInfo, data, 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; bool shouldClose = false;
while (!shouldClose) { while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {}; AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) { while (app_event_poll(&sub, &event) == ERROR_NONE) {
break; switch (event.type) {
} case APP_EVENT_CLOSE:
switch (event.type) { shouldClose = true;
case APP_EVENT_CLOSE: break;
shouldClose = true; default:
break; break;
default: }
break; if (shouldClose) break;
} }
} }
window_manager_remove(window); service::espnow::unsubscribeReceiver(espnow_subscription);
app_event_unsubscribe(&sub);
service::espnow::unsubscribeReceiver(ctx.receiveSubscription);
disableEspNow(&ctx); disableEspNow(&ctx);
window_manager_remove(window);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0; return 0;
} }
@@ -8,11 +8,13 @@
#include <app/event.h> #include <app/event.h>
#include <app/manager.h> #include <app/manager.h>
#include <app/manifest.h> #include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h> #include <lvgl_window_manager/window_manager.h>
#include <lvgl.h> #include <lvgl.h>
#include <qrcode.h> #include <qrcode.h>
#include <tactility/check.h>
#include <tactility/drivers/pointer.h> #include <tactility/drivers/pointer.h>
#include <tactility/log.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 {}; Context ctx {};
ctx.appInstanceId = appInstanceId; ctx.appInstanceId = appInstanceId;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {}; AppEventSubscription sub {};
sub.app_instance_id = appInstanceId; check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
app_event_subscribe(&sub);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx); WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
if (!ctx.hasFatalError) { if (!ctx.hasFatalError) {
bool shouldClose = false; bool shouldClose = false;
while (!shouldClose) { while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {}; AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) { while (app_event_poll(&sub, &event) == ERROR_NONE) {
break; switch (event.type) {
} case APP_EVENT_CLOSE:
switch (event.type) { shouldClose = true;
case APP_EVENT_CLOSE: break;
shouldClose = true; default:
break; break;
default: }
break; if (shouldClose) break;
} }
} }
} }
window_manager_remove(window); 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; bool continuePressed = ctx.continuePressed;
@@ -10,9 +10,11 @@
#include <app/event.h> #include <app/event.h>
#include <app/manager.h> #include <app/manager.h>
#include <app/manifest.h> #include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h> #include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <tactility/log.h> #include <tactility/log.h>
#include <lvgl.h> #include <lvgl.h>
@@ -162,7 +164,8 @@ void createWidgets(lv_obj_t* parent, void* userData) {
updateViewState(ctx); 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 {}; Context ctx {};
ctx.appInstanceId = appInstanceId; ctx.appInstanceId = appInstanceId;
ctx.service = service::development::findService(); ctx.service = service::development::findService();
@@ -174,9 +177,11 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
return 0; return 0;
} }
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {}; AppEventSubscription sub {};
sub.app_instance_id = appInstanceId; check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
app_event_subscribe(&sub);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx); 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; bool shouldClose = false;
while (!shouldClose) { while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {}; AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) { while (app_event_poll(&sub, &event) == ERROR_NONE) {
break; switch (event.type) {
} case APP_EVENT_CLOSE:
switch (event.type) { shouldClose = true;
case APP_EVENT_CLOSE: break;
shouldClose = true; default:
break; break;
default: }
break; if (shouldClose) break;
} }
} }
@@ -216,7 +223,8 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
lvgl_unlock(); lvgl_unlock();
window_manager_remove(window); window_manager_remove(window);
app_event_unsubscribe(&sub); check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0; return 0;
} }
@@ -1,4 +1,5 @@
#include <lvgl/lvgl.h> #include <lvgl/lvgl.h>
#include <tactility/check.h>
#include <tactility/device.h> #include <tactility/device.h>
#include <tactility/drivers/backlight.h> #include <tactility/drivers/backlight.h>
#include <tactility/drivers/display.h> #include <tactility/drivers/display.h>
@@ -12,8 +13,8 @@
#include <Tactility/settings/DisplaySettings.h> #include <Tactility/settings/DisplaySettings.h>
#include <app/event.h> #include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h> #include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h> #include <lvgl_window_manager/window_manager.h>
@@ -25,11 +26,11 @@
#include <sdkconfig.h> #include <sdkconfig.h>
#endif #endif
namespace tt::app::kerneldisplay { namespace tt::app::display {
extern const ::AppManifest manifest; extern const ::AppManifest manifest;
constexpr auto* TAG = "KernelDisplay"; constexpr auto* TAG = "Display";
namespace { 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 {}; Context ctx {};
ctx.appInstanceId = appInstanceId; ctx.appInstanceId = appInstanceId;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {}; AppEventSubscription sub {};
sub.app_instance_id = appInstanceId; check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
app_event_subscribe(&sub);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx); WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false; bool shouldClose = false;
while (!shouldClose) { while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {}; AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) { while (app_event_poll(&sub, &event) == ERROR_NONE) {
break; switch (event.type) {
} case APP_EVENT_CLOSE:
switch (event.type) { persistIfUpdated(ctx);
case APP_EVENT_CLOSE: shouldClose = true;
persistIfUpdated(ctx); break;
shouldClose = true; default:
break; break;
default: }
break; if (shouldClose) break;
} }
} }
window_manager_remove(window); window_manager_remove(window);
app_event_unsubscribe(&sub); check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0; return 0;
} }
@@ -345,4 +352,4 @@ extern const ::AppManifest manifest = {
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) } .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/event.h>
#include <app/manager.h> #include <app/manager.h>
#include <app/manifest.h> #include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h> #include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <memory> #include <memory>
namespace tt::app::files { namespace tt::app::files {
@@ -25,39 +28,45 @@ void createWidgets(lv_obj_t* parent, void* userData) {
ctx->view->init(ctx->appInstanceId, parent); 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>(); auto state = std::make_shared<State>();
View view(state); View view(state);
CreateContext createContext { &view, appInstanceId }; CreateContext createContext { &view, appInstanceId };
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {}; AppEventSubscription sub {};
sub.app_instance_id = appInstanceId; check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
app_event_subscribe(&sub);
WindowId window = window_manager_create(appInstanceId, createWidgets, &createContext); WindowId window = window_manager_create(appInstanceId, createWidgets, &createContext);
bool shouldClose = false; bool shouldClose = false;
while (!shouldClose) { while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {}; AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) { while (app_event_poll(&sub, &event) == ERROR_NONE) {
break; switch (event.type) {
} case APP_EVENT_CLOSE:
switch (event.type) { shouldClose = true;
case APP_EVENT_CLOSE: break;
shouldClose = true; case APP_EVENT_RESULT:
break; view.onResult(event.result.launch_id, event.result.result);
case APP_EVENT_RESULT: app_manager_stop(event.result.launch_id);
view.onResult(event.result.launch_id, event.result.result); break;
app_manager_stop(event.result.launch_id); default:
break; break;
default: }
break; if (shouldClose) break;
} }
} }
view.deinit(); view.deinit();
window_manager_remove(window); window_manager_remove(window);
app_event_unsubscribe(&sub); check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0; return 0;
} }
@@ -5,9 +5,12 @@
#include <app/event.h> #include <app/event.h>
#include <app/manager.h> #include <app/manager.h>
#include <app/manifest.h> #include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h> #include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <memory> #include <memory>
#include <string> #include <string>
@@ -43,7 +46,8 @@ void createWidgets(lv_obj_t* parent, void* userData) {
ctx->view->init(parent, ctx->mode); 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". // argv layout: [0]="existing" or "existing_or_new".
Context ctx {}; Context ctx {};
@@ -62,24 +66,31 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
app_event_emit(appInstanceId, &closeEvent); app_event_emit(appInstanceId, &closeEvent);
}); });
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {}; AppEventSubscription sub {};
sub.app_instance_id = appInstanceId; check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
app_event_subscribe(&sub);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx); WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
while (true) { while (true) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
bool shouldClose = false;
AppEvent event {}; AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) { while (app_event_poll(&sub, &event) == ERROR_NONE) {
break; if (event.type == APP_EVENT_CLOSE) {
} shouldClose = true;
if (event.type == APP_EVENT_CLOSE) { break;
break; }
} }
if (shouldClose) break;
} }
window_manager_remove(window); window_manager_remove(window);
app_event_unsubscribe(&sub); check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return ctx.result; return ctx.result;
} }
@@ -9,9 +9,11 @@
#include <app/event.h> #include <app/event.h>
#include <app/manager.h> #include <app/manager.h>
#include <app/manifest.h> #include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h> #include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <tactility/device.h> #include <tactility/device.h>
#include <tactility/time.h> #include <tactility/time.h>
@@ -257,7 +259,8 @@ void createWidgets(lv_obj_t* parent, void* userData) {
updateDeviceStates(ctx); 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 {}; Context ctx {};
ctx.appInstanceId = appInstanceId; ctx.appInstanceId = appInstanceId;
@@ -267,51 +270,56 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
updateDeviceStates(&ctx); updateDeviceStates(&ctx);
}); });
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {}; AppEventSubscription sub {};
sub.app_instance_id = appInstanceId; check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
app_event_subscribe(&sub);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx); WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
ctx.timer->start(); ctx.timer->start();
bool shouldClose = false; bool shouldClose = false;
while (!shouldClose) { while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {}; AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) { while (app_event_poll(&sub, &event) == ERROR_NONE) {
break; switch (event.type) {
} case APP_EVENT_CLOSE:
switch (event.type) { shouldClose = true;
case APP_EVENT_CLOSE: break;
shouldClose = true; case APP_EVENT_RESULT:
break; if (ctx.hasPendingDelete) {
case APP_EVENT_RESULT: ctx.hasPendingDelete = false;
if (ctx.hasPendingDelete) { if (event.result.result == 0) { // 0 = Yes
ctx.hasPendingDelete = false; lvgl_lock();
if (event.result.result == 0) { // 0 = Yes std::erase_if(ctx.deviceRows, [&ctx](const DeviceRow& row) {
lvgl_lock(); return row.device == ctx.pendingDeleteDevice;
std::erase_if(ctx.deviceRows, [&ctx](const DeviceRow& row) { });
return row.device == ctx.pendingDeleteDevice; lvgl_unlock();
});
lvgl_unlock();
gps_settings_remove_configuration_at(ctx.pendingDeleteIndex); gps_settings_remove_configuration_at(ctx.pendingDeleteIndex);
ctx.pendingDeleteDevice = nullptr; ctx.pendingDeleteDevice = nullptr;
lvgl_lock(); lvgl_lock();
rebuildDeviceList(&ctx); rebuildDeviceList(&ctx);
lvgl_unlock(); lvgl_unlock();
}
} }
} app_manager_stop(event.result.launch_id);
app_manager_stop(event.result.launch_id); break;
break; default:
default: break;
break; }
if (shouldClose) break;
} }
} }
ctx.timer->stop(); ctx.timer->stop();
window_manager_remove(window); window_manager_remove(window);
app_event_unsubscribe(&sub); check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0; return 0;
} }
@@ -2,12 +2,14 @@
#include <lvgl.h> #include <lvgl.h>
#include <tactility/check.h>
#include <tactility/device.h> #include <tactility/device.h>
#include <tactility/drivers/grove.h> #include <tactility/drivers/grove.h>
#include <app/event.h> #include <app/event.h>
#include <app/manager.h> #include <app/manager.h>
#include <app/manifest.h> #include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.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 {}; Context ctx {};
ctx.appInstanceId = appInstanceId; ctx.appInstanceId = appInstanceId;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {}; AppEventSubscription sub {};
sub.app_instance_id = appInstanceId; check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
app_event_subscribe(&sub);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx); WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false; bool shouldClose = false;
while (!shouldClose) { while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {}; AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) { while (app_event_poll(&sub, &event) == ERROR_NONE) {
break; switch (event.type) {
} case APP_EVENT_CLOSE:
switch (event.type) { shouldClose = true;
case APP_EVENT_CLOSE: break;
shouldClose = true; default:
break; break;
default: }
break; if (shouldClose) break;
} }
} }
window_manager_remove(window); window_manager_remove(window);
app_event_unsubscribe(&sub); check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0; return 0;
} }
+22 -14
View File
@@ -9,9 +9,11 @@
#include <app/manager.h> #include <app/manager.h>
#include <app/manifest.h> #include <app/manifest.h>
#include <app/paths.h> #include <app/paths.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h> #include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <tactility/drivers/i2c_controller.h> #include <tactility/drivers/i2c_controller.h>
#include <tactility/log.h> #include <tactility/log.h>
#include <tactility/preferences.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; Context ctx;
ctx.appInstanceId = appInstanceId; ctx.appInstanceId = appInstanceId;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {}; AppEventSubscription sub {};
sub.app_instance_id = appInstanceId; check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
app_event_subscribe(&sub);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx); WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false; bool shouldClose = false;
while (!shouldClose) { while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {}; AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) { while (app_event_poll(&sub, &event) == ERROR_NONE) {
break; switch (event.type) {
} case APP_EVENT_CLOSE:
switch (event.type) { stopScanningIfRunning(&ctx);
case APP_EVENT_CLOSE: shouldClose = true;
stopScanningIfRunning(&ctx); break;
shouldClose = true; default:
break; break;
default: }
break; if (shouldClose) break;
} }
} }
window_manager_remove(window); window_manager_remove(window);
app_event_unsubscribe(&sub); check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0; return 0;
} }
@@ -7,6 +7,7 @@
#include <app/event.h> #include <app/event.h>
#include <app/manager.h> #include <app/manager.h>
#include <app/manifest.h> #include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.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"); check(argc > 0, "Parameters not set");
Context ctx {}; Context ctx {};
ctx.appInstanceId = appInstanceId; ctx.appInstanceId = appInstanceId;
ctx.filePath = argv[0]; ctx.filePath = argv[0];
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {}; AppEventSubscription sub {};
sub.app_instance_id = appInstanceId; check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
app_event_subscribe(&sub);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx); WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false; bool shouldClose = false;
while (!shouldClose) { while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {}; AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) { while (app_event_poll(&sub, &event) == ERROR_NONE) {
break; switch (event.type) {
} case APP_EVENT_CLOSE:
switch (event.type) { shouldClose = true;
case APP_EVENT_CLOSE: break;
shouldClose = true; default:
break; break;
default: }
break; if (shouldClose) break;
} }
} }
window_manager_remove(window); window_manager_remove(window);
app_event_unsubscribe(&sub); check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0; return 0;
} }
@@ -3,10 +3,12 @@
#include <app/event.h> #include <app/event.h>
#include <app/manager.h> #include <app/manager.h>
#include <app/manifest.h> #include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h> #include <lvgl_window_manager/window_manager.h>
#include <lvgl/widgets/toolbar.h> #include <lvgl/widgets/toolbar.h>
#include <tactility/check.h>
#include <tactility/log.h> #include <tactility/log.h>
#include <lvgl.h> #include <lvgl.h>
@@ -106,29 +108,37 @@ void createWidgets(lv_obj_t* parent, void* userData) {
createButton(ctx, button_wrapper, "Cancel", nullptr); 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 }; Context ctx { appInstanceId };
ctx.argc = argc; ctx.argc = argc;
ctx.argv = argv; ctx.argv = argv;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {}; AppEventSubscription sub {};
sub.app_instance_id = appInstanceId; check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
app_event_subscribe(&sub);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx); WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
while (true) { while (true) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
bool shouldClose = false;
AppEvent event {}; AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) { while (app_event_poll(&sub, &event) == ERROR_NONE) {
break; if (event.type == APP_EVENT_CLOSE) {
} shouldClose = true;
if (event.type == APP_EVENT_CLOSE) { break;
break; }
} }
if (shouldClose) break;
} }
window_manager_remove(window); window_manager_remove(window);
app_event_unsubscribe(&sub); check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return ctx.result; return ctx.result;
} }
@@ -7,9 +7,11 @@
#include <app/event.h> #include <app/event.h>
#include <app/manager.h> #include <app/manager.h>
#include <app/manifest.h> #include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h> #include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <tactility/device.h> #include <tactility/device.h>
#include <tactility/drivers/backlight.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 {}; Context ctx {};
ctx.appInstanceId = appInstanceId; ctx.appInstanceId = appInstanceId;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {}; AppEventSubscription sub {};
sub.app_instance_id = appInstanceId; check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
app_event_subscribe(&sub);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx); WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false; bool shouldClose = false;
while (!shouldClose) { while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {}; AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) { while (app_event_poll(&sub, &event) == ERROR_NONE) {
break; switch (event.type) {
} case APP_EVENT_CLOSE:
switch (event.type) { persistIfUpdated(ctx);
case APP_EVENT_CLOSE: shouldClose = true;
persistIfUpdated(ctx); break;
shouldClose = true; default:
break; break;
default: }
break; if (shouldClose) break;
} }
} }
window_manager_remove(window); window_manager_remove(window);
app_event_unsubscribe(&sub); check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0; return 0;
} }
+19 -9
View File
@@ -4,6 +4,7 @@
#include <app/event.h> #include <app/event.h>
#include <app/manager.h> #include <app/manager.h>
#include <app/manifest.h> #include <app/manifest.h>
#include <app/scheduler.h>
#include <cstring> #include <cstring>
@@ -14,6 +15,7 @@
#include <lvgl_window_manager/window_manager.h> #include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <tactility/device.h> #include <tactility/device.h>
#include <tactility/drivers/power_supply.h> #include <tactility/drivers/power_supply.h>
#include <tactility/log.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(); runAutoStart();
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {}; AppEventSubscription sub {};
sub.app_instance_id = appInstanceId; check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
app_event_subscribe(&sub);
WindowId window = window_manager_create(appInstanceId, createWidgets, nullptr); 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 // 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). // thread when app-module's scheduler asks it to (e.g. another new-model app is started).
while (true) { while (true) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
bool shouldClose = false;
AppEvent event {}; AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) { while (app_event_poll(&sub, &event) == ERROR_NONE) {
break; if (event.type == APP_EVENT_CLOSE) {
} shouldClose = true;
if (event.type == APP_EVENT_CLOSE) { break;
break; }
} }
if (shouldClose) break;
} }
window_manager_remove(window); window_manager_remove(window);
app_event_unsubscribe(&sub); check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0; return 0;
} }
@@ -9,9 +9,12 @@
#include <app/event.h> #include <app/event.h>
#include <app/manager.h> #include <app/manager.h>
#include <app/manifest.h> #include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h> #include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <lvgl/widgets/toolbar.h> #include <lvgl/widgets/toolbar.h>
#include <lvgl.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); 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; Context ctx;
ctx.appInstanceId = appInstanceId; ctx.appInstanceId = appInstanceId;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {}; AppEventSubscription sub {};
sub.app_instance_id = appInstanceId; check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
app_event_subscribe(&sub);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx); WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false; bool shouldClose = false;
while (!shouldClose) { while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {}; AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) { while (app_event_poll(&sub, &event) == ERROR_NONE) {
break; switch (event.type) {
} case APP_EVENT_CLOSE:
switch (event.type) { shouldClose = true;
case APP_EVENT_CLOSE: break;
shouldClose = true; default:
break; break;
default: }
break; if (shouldClose) break;
} }
} }
window_manager_remove(window); window_manager_remove(window);
app_event_unsubscribe(&sub); check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0; return 0;
} }
+41 -33
View File
@@ -5,12 +5,14 @@
#include <app/event.h> #include <app/event.h>
#include <app/manager.h> #include <app/manager.h>
#include <app/manifest.h> #include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h> #include <lvgl_window_manager/window_manager.h>
#include <lvgl.h> #include <lvgl.h>
#include <lvgl/lvgl.h> #include <lvgl/lvgl.h>
#include <lvgl/widgets/toolbar.h> #include <lvgl/widgets/toolbar.h>
#include <tactility/check.h>
#include <tactility/log.h> #include <tactility/log.h>
namespace tt::app::notes { 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 {}; Context ctx {};
ctx.appInstanceId = appInstanceId; ctx.appInstanceId = appInstanceId;
@@ -190,51 +193,56 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
ctx.filePath = argv[0]; ctx.filePath = argv[0];
} }
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {}; AppEventSubscription sub {};
sub.app_instance_id = appInstanceId; check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
app_event_subscribe(&sub);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx); WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false; bool shouldClose = false;
while (!shouldClose) { while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {}; AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) { while (app_event_poll(&sub, &event) == ERROR_NONE) {
break; switch (event.type) {
} case APP_EVENT_CLOSE:
switch (event.type) { shouldClose = true;
case APP_EVENT_CLOSE: break;
shouldClose = true; case APP_EVENT_RESULT:
break; LOG_I(TAG, "Result for launch id %u", event.result.launch_id);
case APP_EVENT_RESULT: if (event.result.launch_id == ctx.loadFileLaunchId) {
LOG_I(TAG, "Result for launch id %u", event.result.launch_id); ctx.loadFileLaunchId = 0;
if (event.result.launch_id == ctx.loadFileLaunchId) { if (event.result.result == 0 /* Ok */) {
ctx.loadFileLaunchId = 0; auto path = fileselection::getLastPath();
if (event.result.result == 0 /* Ok */) { if (!path.empty()) {
auto path = fileselection::getLastPath(); openFile(&ctx, path);
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) { app_manager_stop(event.result.launch_id);
ctx.saveFileLaunchId = 0; break;
if (event.result.result == 0 /* Ok */) { default:
auto path = fileselection::getLastPath(); break;
// Must re-open file, because the UI was cleared after opening the dialog. }
if (!path.empty() && saveFile(&ctx, path)) { if (shouldClose) break;
openFile(&ctx, path);
}
}
}
app_manager_stop(event.result.launch_id);
break;
default:
break;
} }
} }
window_manager_remove(window); window_manager_remove(window);
app_event_unsubscribe(&sub); check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0; return 0;
} }
+21 -13
View File
@@ -4,9 +4,11 @@
#include <app/event.h> #include <app/event.h>
#include <app/manager.h> #include <app/manager.h>
#include <app/manifest.h> #include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h> #include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <tactility/device.h> #include <tactility/device.h>
#include <tactility/drivers/power_supply.h> #include <tactility/drivers/power_supply.h>
#include <tactility/time.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 {}; Context ctx {};
ctx.appInstanceId = appInstanceId; ctx.appInstanceId = appInstanceId;
@@ -240,31 +243,36 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
updateUi(&ctx); updateUi(&ctx);
}); });
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {}; AppEventSubscription sub {};
sub.app_instance_id = appInstanceId; check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
app_event_subscribe(&sub);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx); WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
ctx.timer->start(); ctx.timer->start();
bool shouldClose = false; bool shouldClose = false;
while (!shouldClose) { while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {}; AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) { while (app_event_poll(&sub, &event) == ERROR_NONE) {
break; switch (event.type) {
} case APP_EVENT_CLOSE:
switch (event.type) { shouldClose = true;
case APP_EVENT_CLOSE: break;
shouldClose = true; default:
break; break;
default: }
break; if (shouldClose) break;
} }
} }
ctx.timer->stop(); ctx.timer->stop();
window_manager_remove(window); window_manager_remove(window);
app_event_unsubscribe(&sub); check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0; return 0;
} }
+15 -7
View File
@@ -4,11 +4,13 @@
#include <app/event.h> #include <app/event.h>
#include <app/manager.h> #include <app/manager.h>
#include <app/manifest.h> #include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h> #include <lvgl_window_manager/window_manager.h>
#include <lvgl.h> #include <lvgl.h>
#include <lvgl/fonts.h> #include <lvgl/fonts.h>
#include <tactility/check.h>
#include <tactility/device.h> #include <tactility/device.h>
#include <tactility/drivers/power_supply.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); 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 {}; Context ctx {};
ctx.appInstanceId = appInstanceId; ctx.appInstanceId = appInstanceId;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {}; AppEventSubscription sub {};
sub.app_instance_id = appInstanceId; check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
app_event_subscribe(&sub);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx); WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false; bool shouldClose = false;
while (!shouldClose) { while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {}; AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) { while (app_event_poll(&sub, &event) == ERROR_NONE) {
break;
}
switch (event.type) { switch (event.type) {
case APP_EVENT_CLOSE: case APP_EVENT_CLOSE:
shouldClose = true; shouldClose = true;
@@ -155,10 +160,13 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
default: default:
break; break;
} }
if (shouldClose) break;
}
} }
window_manager_remove(window); window_manager_remove(window);
app_event_unsubscribe(&sub); check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0; return 0;
} }
+21 -13
View File
@@ -12,9 +12,11 @@
#include <app/event.h> #include <app/event.h>
#include <app/manager.h> #include <app/manager.h>
#include <app/manifest.h> #include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h> #include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <tactility/log.h> #include <tactility/log.h>
#include <lvgl.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 {}; Context ctx {};
ctx.appInstanceId = appInstanceId; ctx.appInstanceId = appInstanceId;
ctx.updateTimer = std::make_unique<Timer>(Timer::Type::Periodic, 500 / portTICK_PERIOD_MS, [&ctx] { 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 {}; AppEventSubscription sub {};
sub.app_instance_id = appInstanceId; check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
app_event_subscribe(&sub);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx); WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false; bool shouldClose = false;
while (!shouldClose) { while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {}; AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) { while (app_event_poll(&sub, &event) == ERROR_NONE) {
break; switch (event.type) {
} case APP_EVENT_CLOSE:
switch (event.type) { shouldClose = true;
case APP_EVENT_CLOSE: break;
shouldClose = true; default:
break; break;
default: }
break; if (shouldClose) break;
} }
} }
@@ -276,7 +283,8 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
} }
window_manager_remove(window); window_manager_remove(window);
app_event_unsubscribe(&sub); check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0; return 0;
} }
@@ -3,9 +3,11 @@
#include <app/event.h> #include <app/event.h>
#include <app/manager.h> #include <app/manager.h>
#include <app/manifest.h> #include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h> #include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <tactility/log.h> #include <tactility/log.h>
#include <lvgl.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 }; Context ctx { appInstanceId };
ctx.argc = argc; ctx.argc = argc;
ctx.argv = argv; ctx.argv = argv;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {}; AppEventSubscription sub {};
sub.app_instance_id = appInstanceId; check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
app_event_subscribe(&sub);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx); WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
while (true) { while (true) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
bool shouldClose = false;
AppEvent event {}; AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) { while (app_event_poll(&sub, &event) == ERROR_NONE) {
break; if (event.type == APP_EVENT_CLOSE) {
} shouldClose = true;
if (event.type == APP_EVENT_CLOSE) { break;
break; }
} }
if (shouldClose) break;
} }
window_manager_remove(window); window_manager_remove(window);
app_event_unsubscribe(&sub); check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return ctx.result; return ctx.result;
} }
+18 -9
View File
@@ -1,6 +1,7 @@
#include <app/event.h> #include <app/event.h>
#include <app/manager.h> #include <app/manager.h>
#include <app/manifest.h> #include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.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; settingsInstanceId = appInstanceId;
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {}; AppEventSubscription sub {};
sub.app_instance_id = appInstanceId; check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
app_event_subscribe(&sub);
WindowId window = window_manager_create(appInstanceId, createWidgets, nullptr); WindowId window = window_manager_create(appInstanceId, createWidgets, nullptr);
while (true) { while (true) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
bool shouldClose = false;
AppEvent event {}; AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) { while (app_event_poll(&sub, &event) == ERROR_NONE) {
break; if (event.type == APP_EVENT_CLOSE) {
} shouldClose = true;
if (event.type == APP_EVENT_CLOSE) { break;
break; }
} }
if (shouldClose) break;
} }
window_manager_remove(window); window_manager_remove(window);
app_event_unsubscribe(&sub); check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0; return 0;
} }
+29 -21
View File
@@ -11,9 +11,11 @@
#include <app/event.h> #include <app/event.h>
#include <app/manager.h> #include <app/manager.h>
#include <app/manifest.h> #include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h> #include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <tactility/log.h> #include <tactility/log.h>
#include <lvgl/fonts.h> #include <lvgl/fonts.h>
@@ -159,7 +161,7 @@ void onContinueClicked(lv_event_t* event) {
markCompleted(); markCompleted();
// Async, non-blocking - must NOT call app_manager_stop() directly here: this // 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 // 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 = {} }; AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(ctx->appInstanceId, &closeEvent); app_event_emit(ctx->appInstanceId, &closeEvent);
break; break;
@@ -203,7 +205,8 @@ void createWidgets(lv_obj_t* parent, void* userData) {
renderCurrent(ctx); 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 {}; Context ctx {};
ctx.appInstanceId = appInstanceId; ctx.appInstanceId = appInstanceId;
ctx.steps = { 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 {}; AppEventSubscription sub {};
sub.app_instance_id = appInstanceId; check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
app_event_subscribe(&sub);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx); WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false; bool shouldClose = false;
while (!shouldClose) { while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {}; AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) { while (app_event_poll(&sub, &event) == ERROR_NONE) {
break; switch (event.type) {
} case APP_EVENT_CLOSE:
switch (event.type) { shouldClose = true;
case APP_EVENT_CLOSE: break;
shouldClose = true; case APP_EVENT_RESULT:
break; if (event.result.launch_id == ctx.pendingStepDialogId) {
case APP_EVENT_RESULT: ctx.pendingStepDialogId = 0;
if (event.result.launch_id == ctx.pendingStepDialogId) { advanceTo(&ctx, ctx.stepIndex + 1);
ctx.pendingStepDialogId = 0; }
advanceTo(&ctx, ctx.stepIndex + 1); app_manager_stop(event.result.launch_id);
} break;
app_manager_stop(event.result.launch_id); default:
break; break;
default: }
break; if (shouldClose) break;
} }
} }
window_manager_remove(window); window_manager_remove(window);
app_event_unsubscribe(&sub); check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0; return 0;
} }
+21 -13
View File
@@ -1,3 +1,4 @@
#include <tactility/check.h>
#include "tactility/time.h" #include "tactility/time.h"
#include <Tactility/DeprecatedPaths.h> #include <Tactility/DeprecatedPaths.h>
@@ -8,6 +9,7 @@
#include <app/event.h> #include <app/event.h>
#include <app/manager.h> #include <app/manager.h>
#include <app/manifest.h> #include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h> #include <lvgl_window_manager/window_manager.h>
@@ -399,7 +401,8 @@ void createWidgets(lv_obj_t* parent, void* userData) {
updateTasks(ctx); 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 {}; Context ctx {};
ctx.appInstanceId = appInstanceId; ctx.appInstanceId = appInstanceId;
@@ -417,9 +420,11 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
lvgl_unlock(); lvgl_unlock();
}); });
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {}; AppEventSubscription sub {};
sub.app_instance_id = appInstanceId; check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
app_event_subscribe(&sub);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx); WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
ctx.memoryTimer->start(); // Memory: every 10s ctx.memoryTimer->start(); // Memory: every 10s
@@ -427,23 +432,26 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
bool shouldClose = false; bool shouldClose = false;
while (!shouldClose) { while (!shouldClose) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
AppEvent event {}; AppEvent event {};
if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) { while (app_event_poll(&sub, &event) == ERROR_NONE) {
break; switch (event.type) {
} case APP_EVENT_CLOSE:
switch (event.type) { shouldClose = true;
case APP_EVENT_CLOSE: break;
shouldClose = true; default:
break; break;
default: }
break; if (shouldClose) break;
} }
} }
ctx.memoryTimer->stop(); ctx.memoryTimer->stop();
ctx.tasksTimer->stop(); ctx.tasksTimer->stop();
window_manager_remove(window); window_manager_remove(window);
app_event_unsubscribe(&sub); check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
return 0; return 0;
} }

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