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
@@ -16,9 +16,9 @@ error_t api_load(AppLocation location, AppRuntime* out_runtime) {
return ERROR_NONE;
}
int32_t api_run(AppRuntime runtime, uint32_t app_instance_id, int argc, char* argv[]) {
int32_t api_run(AppRuntime runtime, uint32_t /*app_instance_id*/, int argc, char* argv[]) {
auto entry = reinterpret_cast<AppMainFn>(runtime);
return entry(app_instance_id, argc, argv);
return entry(argc, argv);
}
void api_unload(AppRuntime /*unused*/) {
+44 -36
View File
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: Apache-2.0
#include <app/event.h>
#include <app/scheduler.h>
#include <tactility/concurrent/mutex.h>
#include <tactility/time.h>
@@ -7,7 +8,7 @@
/**
* Intrusive singly-linked list of subscriptions, keyed by app_instance_id.
* Guarded by a single coarse-grained mutex, notifying a subscriber here never invokes caller code
* (just a struct copy and an xTaskNotifyGive), so there is no reentrancy concern requiring a snapshot-then-unlock dance.
* (just a struct copy and a task_event_group_signal), so there is no reentrancy concern requiring a snapshot-then-unlock dance.
*/
static AppEventSubscription* subscriptions = nullptr;
@@ -21,32 +22,55 @@ static AppEventMutex subscriptions_mutex;
extern "C" {
error_t app_event_subscribe(AppEventSubscription* sub) {
sub->task = xTaskGetCurrentTaskHandle();
sub->head = 0;
sub->count = 0;
error_t app_event_subscribe_with_app_id(AppEventSubscription* sub, TaskEventGroup* event_group, AppInstanceId app_instance_id) {
uint32_t bit;
error_t claim_result = task_event_group_claim_bit(event_group, &bit);
if (claim_result != ERROR_NONE) {
return claim_result;
}
mutex_lock(&subscriptions_mutex.handle);
sub->next = subscriptions;
// Avoid cyclic subscription list that would loop forever
if (subscriptions == sub) {
mutex_unlock(&subscriptions_mutex.handle);
task_event_group_release_bit(event_group, bit);
return ERROR_INVALID_STATE;
}
sub->bit = bit;
sub->internal.app_instance_id = app_instance_id;
sub->internal.event_group = event_group;
sub->internal.head = 0;
sub->internal.count = 0;
sub->internal.next = subscriptions;
subscriptions = sub;
mutex_unlock(&subscriptions_mutex.handle);
return ERROR_NONE;
}
error_t app_event_subscribe(AppEventSubscription* sub, TaskEventGroup* event_group) {
return app_event_subscribe_with_app_id(sub, event_group, app_scheduler_current_app_id());
}
error_t app_event_unsubscribe(AppEventSubscription* sub) {
error_t result = ERROR_NOT_FOUND;
mutex_lock(&subscriptions_mutex.handle);
for (AppEventSubscription** link = &subscriptions; *link != nullptr; link = &(*link)->next) {
for (AppEventSubscription** link = &subscriptions; *link != nullptr; link = &(*link)->internal.next) {
if (*link == sub) {
*link = sub->next;
*link = sub->internal.next;
result = ERROR_NONE;
break;
}
}
mutex_unlock(&subscriptions_mutex.handle);
if (result == ERROR_NONE) {
task_event_group_release_bit(sub->internal.event_group, sub->bit);
}
return result;
}
@@ -57,23 +81,23 @@ error_t app_event_emit(AppInstanceId app_instance_id, const AppEvent* event) {
error_t result = ERROR_NOT_FOUND;
mutex_lock(&subscriptions_mutex.handle);
for (AppEventSubscription* sub = subscriptions; sub != nullptr; sub = sub->next) {
if (sub->app_instance_id != app_instance_id) {
for (AppEventSubscription* sub = subscriptions; sub != nullptr; sub = sub->internal.next) {
if (sub->internal.app_instance_id != app_instance_id) {
continue;
}
if (sub->count >= APP_EVENT_QUEUE_CAPACITY) {
if (sub->internal.count >= APP_EVENT_QUEUE_CAPACITY) {
result = ERROR_RESOURCE;
continue;
}
uint8_t tail = (sub->head + sub->count) % APP_EVENT_QUEUE_CAPACITY;
sub->queue[tail] = stamped_event;
sub->count++;
uint8_t tail = (sub->internal.head + sub->internal.count) % APP_EVENT_QUEUE_CAPACITY;
sub->internal.queue[tail] = stamped_event;
sub->internal.count++;
if (result != ERROR_RESOURCE) {
result = ERROR_NONE;
}
xTaskNotifyGive(sub->task);
task_event_group_signal(sub->internal.event_group, sub->bit);
}
mutex_unlock(&subscriptions_mutex.handle);
@@ -82,33 +106,17 @@ error_t app_event_emit(AppInstanceId app_instance_id, const AppEvent* event) {
static bool try_pop(AppEventSubscription* sub, AppEvent* out_event) {
mutex_lock(&subscriptions_mutex.handle);
bool has_event = sub->count > 0;
bool has_event = sub->internal.count > 0;
if (has_event) {
*out_event = sub->queue[sub->head];
sub->head = (sub->head + 1) % APP_EVENT_QUEUE_CAPACITY;
sub->count--;
*out_event = sub->internal.queue[sub->internal.head];
sub->internal.head = (sub->internal.head + 1) % APP_EVENT_QUEUE_CAPACITY;
sub->internal.count--;
}
mutex_unlock(&subscriptions_mutex.handle);
return has_event;
}
error_t app_event_await(AppEventSubscription* sub, AppEvent* out_event, TickType_t timeout) {
if (try_pop(sub, out_event)) {
// Drain any notification credit this (or an earlier) push accumulated on this task's
// FreeRTOS notification value: each app_event_emit() calls xTaskNotifyGive() regardless
// of whether the consumer takes this fast path or the blocking path below, so without
// this the credit would carry over and cause a future ulTaskNotifyTake() below to
// return immediately for a notification that was already accounted for here.
ulTaskNotifyTake(pdTRUE, 0);
return ERROR_NONE;
}
if (ulTaskNotifyTake(pdTRUE, timeout) == 0) {
return ERROR_TIMEOUT;
}
// Single-consumer by design (one task per subscription), so a wakeup implies the event
// this call was notified for is still there for us to pop.
error_t app_event_poll(AppEventSubscription* sub, AppEvent* out_event) {
return try_pop(sub, out_event) ? ERROR_NONE : ERROR_TIMEOUT;
}
+3 -1
View File
@@ -8,6 +8,7 @@
#include <service/manager.h>
#include <tactility/concurrent/task_event_group.h>
#include <tactility/error.h>
#include <tactility/module.h>
@@ -18,9 +19,10 @@ extern ServiceManifest app_internal_loader_service_manifest;
const ModuleSymbol app_module_symbols[] = {
// app/event
DEFINE_MODULE_SYMBOL(app_event_subscribe),
DEFINE_MODULE_SYMBOL(app_event_subscribe_with_app_id),
DEFINE_MODULE_SYMBOL(app_event_unsubscribe),
DEFINE_MODULE_SYMBOL(app_event_emit),
DEFINE_MODULE_SYMBOL(app_event_await),
DEFINE_MODULE_SYMBOL(app_event_poll),
// app/install
DEFINE_MODULE_SYMBOL(app_get_install_path),
DEFINE_MODULE_SYMBOL(app_install),