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
@@ -19,8 +19,6 @@
namespace {
constexpr size_t WIFI_MAX_CALLBACKS = 4;
struct MockApRecord {
const char* ssid;
int8_t rssi;
@@ -40,11 +38,6 @@ constexpr size_t MOCK_SCAN_RESULT_COUNT = sizeof(MOCK_SCAN_RESULTS) / sizeof(MOC
constexpr int8_t MOCK_CONNECTED_RSSI = -30;
constexpr const char* MOCK_IPV4_ADDRESS = "192.168.1.2";
struct WifiCallbackEntry {
WifiEventCallback fn = nullptr;
void* ctx = nullptr;
};
struct PosixWifiCtx {
Device* device = nullptr;
@@ -54,24 +47,25 @@ struct PosixWifiCtx {
bool scanning = false;
char targetSsid[33] = {};
Mutex callbackMutex {};
WifiCallbackEntry callbacks[WIFI_MAX_CALLBACKS] = {};
size_t callbackCount = 0;
Mutex subscriptionsMutex {};
WifiEventSubscription* subscriptions = nullptr;
};
#define GET_CTX(device) (static_cast<PosixWifiCtx*>(device_get_driver_data(device)))
void fireEvent(PosixWifiCtx* ctx, WifiEvent event) {
WifiCallbackEntry local[WIFI_MAX_CALLBACKS];
size_t count;
mutex_lock(&ctx->callbackMutex);
count = ctx->callbackCount;
memcpy(local, ctx->callbacks, count * sizeof(WifiCallbackEntry));
mutex_unlock(&ctx->callbackMutex);
for (size_t i = 0; i < count; i++) {
local[i].fn(ctx->device, local[i].ctx, event);
mutex_lock(&ctx->subscriptionsMutex);
for (WifiEventSubscription* sub = ctx->subscriptions; sub != nullptr; sub = sub->internal.next) {
mutex_lock(&sub->internal.ring_mutex);
if (sub->internal.count < WIFI_EVENT_QUEUE_CAPACITY) {
uint8_t tail = (sub->internal.head + sub->internal.count) % WIFI_EVENT_QUEUE_CAPACITY;
sub->internal.queue[tail] = event;
sub->internal.count++;
}
mutex_unlock(&sub->internal.ring_mutex);
task_event_group_signal(sub->internal.event_group, sub->bit);
}
mutex_unlock(&ctx->subscriptionsMutex);
}
// ---- WifiApi ----
@@ -247,41 +241,105 @@ error_t apiStationGetRssi(Device* device, int32_t* rssi) {
return ERROR_NONE;
}
error_t apiAddEventCallback(Device* device, void* callback_context, WifiEventCallback callback) {
error_t apiSetRadioOn(Device* device) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || callback == nullptr) return ERROR_INVALID_ARGUMENT;
if (ctx == nullptr) return ERROR_INVALID_STATE;
mutex_lock(&ctx->callbackMutex);
if (ctx->callbackCount >= WIFI_MAX_CALLBACKS) {
mutex_unlock(&ctx->callbackMutex);
return ERROR_OUT_OF_MEMORY;
}
ctx->callbacks[ctx->callbackCount] = { .fn = callback, .ctx = callback_context };
ctx->callbackCount++;
mutex_unlock(&ctx->callbackMutex);
mutex_lock(&ctx->mutex);
bool already_on = ctx->radioState == WIFI_RADIO_STATE_ON;
ctx->radioState = WIFI_RADIO_STATE_ON;
mutex_unlock(&ctx->mutex);
if (already_on) return ERROR_NONE;
WifiEvent radio_event = {};
radio_event.type = WIFI_EVENT_TYPE_RADIO_STATE_CHANGED;
radio_event.radio_state = WIFI_RADIO_STATE_ON;
fireEvent(ctx, radio_event);
LOG_I(TAG, "WiFi radio on (mock)");
return ERROR_NONE;
}
error_t apiRemoveEventCallback(Device* device, WifiEventCallback callback) {
error_t apiSetRadioOff(Device* device) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || callback == nullptr) return ERROR_INVALID_ARGUMENT;
if (ctx == nullptr) return ERROR_INVALID_STATE;
mutex_lock(&ctx->callbackMutex);
for (size_t i = 0; i < ctx->callbackCount; i++) {
if (ctx->callbacks[i].fn == callback) {
for (size_t j = i; j + 1 < ctx->callbackCount; j++) {
ctx->callbacks[j] = ctx->callbacks[j + 1];
}
ctx->callbackCount--;
mutex_unlock(&ctx->callbackMutex);
return ERROR_NONE;
mutex_lock(&ctx->mutex);
bool already_off = ctx->radioState == WIFI_RADIO_STATE_OFF;
bool was_connected = ctx->stationState != WIFI_STATION_STATE_DISCONNECTED;
ctx->radioState = WIFI_RADIO_STATE_OFF;
ctx->stationState = WIFI_STATION_STATE_DISCONNECTED;
ctx->scanning = false;
mutex_unlock(&ctx->mutex);
if (already_off) return ERROR_NONE;
if (was_connected) {
WifiEvent station_event = {};
station_event.type = WIFI_EVENT_TYPE_STATION_STATE_CHANGED;
station_event.station_state = WIFI_STATION_STATE_DISCONNECTED;
fireEvent(ctx, station_event);
}
WifiEvent radio_event = {};
radio_event.type = WIFI_EVENT_TYPE_RADIO_STATE_CHANGED;
radio_event.radio_state = WIFI_RADIO_STATE_OFF;
fireEvent(ctx, radio_event);
LOG_I(TAG, "WiFi radio off (mock)");
return ERROR_NONE;
}
error_t apiEventSubscribe(Device* device, WifiEventSubscription* sub, TaskEventGroup* event_group) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || sub == nullptr || event_group == nullptr) return ERROR_INVALID_ARGUMENT;
uint32_t bit;
error_t claim_result = task_event_group_claim_bit(event_group, &bit);
if (claim_result != ERROR_NONE) {
return claim_result;
}
mutex_lock(&ctx->subscriptionsMutex);
// Avoid cyclic subscription list that would loop forever
if (ctx->subscriptions == sub) {
mutex_unlock(&ctx->subscriptionsMutex);
task_event_group_release_bit(event_group, bit);
return ERROR_INVALID_STATE;
}
sub->internal.event_group = event_group;
sub->bit = bit;
sub->internal.next = ctx->subscriptions;
ctx->subscriptions = sub;
mutex_unlock(&ctx->subscriptionsMutex);
return ERROR_NONE;
}
error_t apiEventUnsubscribe(Device* device, WifiEventSubscription* sub) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || sub == nullptr) return ERROR_INVALID_ARGUMENT;
error_t result = ERROR_NOT_FOUND;
mutex_lock(&ctx->subscriptionsMutex);
for (WifiEventSubscription** link = &ctx->subscriptions; *link != nullptr; link = &(*link)->internal.next) {
if (*link == sub) {
*link = sub->internal.next;
result = ERROR_NONE;
break;
}
}
mutex_unlock(&ctx->callbackMutex);
return ERROR_NOT_FOUND;
mutex_unlock(&ctx->subscriptionsMutex);
if (result == ERROR_NONE) {
task_event_group_release_bit(sub->internal.event_group, sub->bit);
}
return result;
}
const WifiApi posix_wifi_api = {
.set_radio_on = apiSetRadioOn,
.set_radio_off = apiSetRadioOff,
.get_radio_state = apiGetRadioState,
.get_station_state = apiGetStationState,
.get_access_point_state = apiGetAccessPointState,
@@ -293,13 +351,14 @@ const WifiApi posix_wifi_api = {
.station_connect = apiStationConnect,
.station_disconnect = apiStationDisconnect,
.station_get_rssi = apiStationGetRssi,
.add_event_callback = apiAddEventCallback,
.remove_event_callback = apiRemoveEventCallback
.event_subscribe = apiEventSubscribe,
.event_unsubscribe = apiEventUnsubscribe
};
// ---- Driver lifecycle ----
// Unlike the real esp32 driver, there's no hardware to bring up: the radio
// simply reports itself as ON as soon as the device is started.
// startDevice()/stopDevice() only allocate/free this driver's bookkeeping (subscriber list
// included), so event subscribers can stay subscribed across radio on/off toggles - the radio
// itself is only touched by apiSetRadioOn()/apiSetRadioOff().
error_t startDevice(Device* device) {
auto* ctx = new(std::nothrow) PosixWifiCtx();
@@ -307,12 +366,9 @@ error_t startDevice(Device* device) {
ctx->device = device;
mutex_construct(&ctx->mutex);
mutex_construct(&ctx->callbackMutex);
ctx->radioState = WIFI_RADIO_STATE_ON;
mutex_construct(&ctx->subscriptionsMutex);
device_set_driver_data(device, ctx);
LOG_I(TAG, "WiFi radio on (mock)");
return ERROR_NONE;
}
@@ -320,12 +376,28 @@ error_t stopDevice(Device* device) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr) return ERROR_NONE;
if (ctx->radioState == WIFI_RADIO_STATE_ON) {
apiSetRadioOff(device);
}
// Release any subscribers that never unsubscribed: device_stop() doesn't wait for apps still
// using this device, so a later wifi_event_unsubscribe() would find no ctx and skip releasing
// the bit and destructing sub->internal.ring_mutex.
mutex_lock(&ctx->subscriptionsMutex);
for (WifiEventSubscription* sub = ctx->subscriptions; sub != nullptr;) {
WifiEventSubscription* next = sub->internal.next;
task_event_group_release_bit(sub->internal.event_group, sub->bit);
mutex_destruct(&sub->internal.ring_mutex);
sub = next;
}
ctx->subscriptions = nullptr;
mutex_unlock(&ctx->subscriptionsMutex);
device_set_driver_data(device, nullptr);
mutex_destruct(&ctx->callbackMutex);
mutex_destruct(&ctx->subscriptionsMutex);
mutex_destruct(&ctx->mutex);
delete ctx;
LOG_I(TAG, "WiFi radio off (mock)");
return ERROR_NONE;
}