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:
committed by
GitHub
parent
db48dfe812
commit
ab75d2022d
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <Tactility/service/espnow/EspNowHostedTransport.h>
|
||||
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/drivers/wifi.h>
|
||||
|
||||
namespace tt::service::espnow::backend {
|
||||
@@ -16,16 +17,19 @@ namespace tt::service::espnow::backend {
|
||||
// truth shared with any other caller (e.g. an external OTA app), instead of being duplicated
|
||||
// here.
|
||||
bool waitForHostedTransport(uint32_t timeoutMs) {
|
||||
Device* device = wifi_find_first_registered_device();
|
||||
if (device == nullptr) {
|
||||
Device* device = nullptr;
|
||||
if (device_get_first_by_type(&WIFI_TYPE, &device) != ERROR_NONE) {
|
||||
return false;
|
||||
}
|
||||
const FirmwareOps* ops = nullptr;
|
||||
void* ctx = nullptr;
|
||||
if (wifi_get_firmware_ops(device, &ops, &ctx) != ERROR_NONE) {
|
||||
device_put(device);
|
||||
return false;
|
||||
}
|
||||
return ops->wait_ready(ctx, timeoutMs);
|
||||
bool ready = ops->wait_ready(ctx, timeoutMs);
|
||||
device_put(device);
|
||||
return ready;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <Tactility/LogMessages.h>
|
||||
#include <Tactility/RecursiveMutex.h>
|
||||
#include <Tactility/Tactility.h>
|
||||
#include <Tactility/Thread.h>
|
||||
#include <Tactility/Timer.h>
|
||||
#include <Tactility/service/Service.h>
|
||||
#include <Tactility/service/ServiceManifest.h>
|
||||
@@ -62,7 +63,6 @@ namespace {
|
||||
/** State lives for the entire process; only ever (re)initialized by onStart(). */
|
||||
struct WifiServiceState {
|
||||
Device* device = nullptr;
|
||||
std::shared_ptr<PubSub<WifiEvent>> pubsub = std::make_shared<PubSub<WifiEvent>>();
|
||||
RecursiveMutex mutex;
|
||||
bool secureConnection = false;
|
||||
// Internal: set by connect()/disconnect() while a manual attempt is in flight, cleared on
|
||||
@@ -79,24 +79,32 @@ struct WifiServiceState {
|
||||
TickType_t lastScanTime = MAX_TICKS;
|
||||
std::unique_ptr<Timer> autoConnectTimer;
|
||||
bool bootEventSubscribed = false;
|
||||
|
||||
// Dedicated consumer for WifiEvents, alive for the service's whole lifetime (started in
|
||||
// onStart(), stopped in onStop() - see dispatchSetEnabled()'s comment on why this outlives
|
||||
// radio on/off toggles): runs onWifiDeviceEvent() on its own stack instead of the ESP-IDF
|
||||
// esp_event task's, by blocking in task_event_group_wait_any() rather than being called back
|
||||
// directly from fire_event().
|
||||
TaskEventGroup wifiEventGroup {};
|
||||
WifiEventSubscription wifiEventSub {};
|
||||
Thread* wifiEventThread = nullptr;
|
||||
std::atomic<bool> wifiEventThreadRunning {false};
|
||||
};
|
||||
|
||||
WifiServiceState state;
|
||||
bool started = false;
|
||||
|
||||
void onWifiDeviceEvent(Device* device, void* context, ::WifiEvent event);
|
||||
void onWifiDeviceEvent(Device* device, ::WifiEvent event);
|
||||
|
||||
// ---- Helpers ----
|
||||
|
||||
void publish(WifiEvent event) {
|
||||
state.pubsub->publish(event);
|
||||
}
|
||||
|
||||
void publishRadioState(WifiRadioState radio_state) {
|
||||
WifiEvent event = {};
|
||||
event.type = WIFI_EVENT_TYPE_RADIO_STATE_CHANGED;
|
||||
event.radio_state = radio_state;
|
||||
publish(event);
|
||||
// state.device is started (bookkeeping allocated) for the service's entire lifetime now - see
|
||||
// dispatchSetEnabled()'s comment - so device_is_ready() no longer tracks radio-on state; query
|
||||
// the driver directly instead.
|
||||
bool isRadioOn() {
|
||||
if (state.device == nullptr) return false;
|
||||
WifiRadioState radio = WIFI_RADIO_STATE_OFF;
|
||||
return wifi_get_radio_state(state.device, &radio) == ERROR_NONE && radio == WIFI_RADIO_STATE_ON;
|
||||
}
|
||||
|
||||
RadioState combineRadioState(WifiRadioState radio, WifiStationState station) {
|
||||
@@ -114,56 +122,88 @@ RadioState combineRadioState(WifiRadioState radio, WifiStationState station) {
|
||||
return RadioState::Off;
|
||||
}
|
||||
|
||||
// ---- WifiEvent consumer thread ----
|
||||
// Runs onWifiDeviceEvent() on its own stack (see WifiServiceState::wifiEventGroup's comment).
|
||||
|
||||
constexpr configSTACK_DEPTH_TYPE WIFI_EVENT_THREAD_STACK_SIZE = 4096;
|
||||
|
||||
int32_t wifiEventThreadMain() {
|
||||
// The 250ms timeout only bounds how promptly a stop request (wifiEventThreadRunning going
|
||||
// false) is noticed; a real event still wakes this immediately regardless, since
|
||||
// task_event_group_wait_any() returns as soon as the bit is signalled, whichever comes first.
|
||||
while (state.wifiEventThreadRunning.load()) {
|
||||
task_event_group_wait_any(&state.wifiEventGroup, nullptr, pdMS_TO_TICKS(250));
|
||||
|
||||
WifiEvent event {};
|
||||
while (wifi_event_poll(&state.wifiEventSub, &event) == ERROR_NONE) {
|
||||
onWifiDeviceEvent(state.device, event);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool startWifiEventThread() {
|
||||
task_event_group_construct(&state.wifiEventGroup);
|
||||
if (wifi_event_subscribe(state.device, &state.wifiEventSub, &state.wifiEventGroup) != ERROR_NONE) {
|
||||
task_event_group_destruct(&state.wifiEventGroup);
|
||||
return false;
|
||||
}
|
||||
|
||||
state.wifiEventThreadRunning = true;
|
||||
state.wifiEventThread = new Thread("wifi-events", WIFI_EVENT_THREAD_STACK_SIZE, [] { return wifiEventThreadMain(); });
|
||||
state.wifiEventThread->start();
|
||||
return true;
|
||||
}
|
||||
|
||||
void stopWifiEventThread() {
|
||||
if (state.wifiEventThread == nullptr) return;
|
||||
|
||||
state.wifiEventThreadRunning = false;
|
||||
state.wifiEventThread->join();
|
||||
delete state.wifiEventThread;
|
||||
state.wifiEventThread = nullptr;
|
||||
|
||||
wifi_event_unsubscribe(state.device, &state.wifiEventSub);
|
||||
task_event_group_destruct(&state.wifiEventGroup);
|
||||
}
|
||||
|
||||
// ---- Dispatched work (runs on the main task) ----
|
||||
|
||||
// state.device is started (device_start()) once, in onStart(), and never stopped until onStop() -
|
||||
// this only toggles the radio itself, so the wifi-events thread (and any app subscribed directly
|
||||
// to the driver) stays subscribed across on/off toggles instead of having to resubscribe.
|
||||
void dispatchSetEnabled(bool enabled) {
|
||||
LOG_I(TAG, "dispatchSetEnabled(%d)", (int)enabled);
|
||||
if (!started || state.device == nullptr) return;
|
||||
|
||||
bool ready = device_is_ready(state.device);
|
||||
if (enabled == ready) {
|
||||
if (enabled == isRadioOn()) {
|
||||
LOG_W(TAG, "Can't enable/disable from current state");
|
||||
return;
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
publishRadioState(WIFI_RADIO_STATE_ON_PENDING);
|
||||
|
||||
if (device_start(state.device) != ERROR_NONE) {
|
||||
LOG_E(TAG, "Failed to start WiFi device");
|
||||
publishRadioState(WIFI_RADIO_STATE_OFF);
|
||||
return;
|
||||
}
|
||||
|
||||
if (wifi_add_event_callback(state.device, nullptr, onWifiDeviceEvent) != ERROR_NONE) {
|
||||
LOG_E(TAG, "Failed to register WiFi event callback");
|
||||
device_stop(state.device);
|
||||
publishRadioState(WIFI_RADIO_STATE_OFF);
|
||||
if (wifi_set_radio_on(state.device) != ERROR_NONE) {
|
||||
LOG_E(TAG, "Failed to enable WiFi radio");
|
||||
return;
|
||||
}
|
||||
|
||||
state.pauseAutoConnect = false;
|
||||
state.lastScanTime = 0;
|
||||
publishRadioState(WIFI_RADIO_STATE_ON);
|
||||
} else {
|
||||
publishRadioState(WIFI_RADIO_STATE_OFF_PENDING);
|
||||
|
||||
if (device_stop(state.device) != ERROR_NONE) {
|
||||
LOG_E(TAG, "Failed to stop WiFi device");
|
||||
publishRadioState(WIFI_RADIO_STATE_ON);
|
||||
if (wifi_set_radio_off(state.device) != ERROR_NONE) {
|
||||
LOG_E(TAG, "Failed to disable WiFi radio");
|
||||
return;
|
||||
}
|
||||
|
||||
wifi_remove_event_callback(state.device, onWifiDeviceEvent);
|
||||
|
||||
state.secureConnection = false;
|
||||
publishRadioState(WIFI_RADIO_STATE_OFF);
|
||||
}
|
||||
}
|
||||
|
||||
void dispatchScan() {
|
||||
LOG_I(TAG, "dispatchScan()");
|
||||
if (!started || state.device == nullptr || !device_is_ready(state.device)) return;
|
||||
if (!started || state.device == nullptr || !isRadioOn()) return;
|
||||
|
||||
state.lastScanTime = get_ticks();
|
||||
|
||||
@@ -189,18 +229,7 @@ void dispatchConnect() {
|
||||
|
||||
LOG_I(TAG, "Connecting to %s", target.ssid.c_str());
|
||||
|
||||
error_t result = wifi_station_connect(state.device, target.ssid.c_str(), target.password.c_str(), target.channel);
|
||||
if (result != ERROR_NONE) {
|
||||
LOG_E(TAG, "Failed to connect to %s (%s)", target.ssid.c_str(), error_to_string(result));
|
||||
WifiEvent event = {};
|
||||
event.type = WIFI_EVENT_TYPE_STATION_CONNECTION_RESULT;
|
||||
// The driver couldn't even initiate the connection attempt; there's no
|
||||
// more specific WifiStationConnectionError for that.
|
||||
event.connection_error = WIFI_STATION_CONNECTION_ERROR_TIMEOUT;
|
||||
publish(event);
|
||||
}
|
||||
// On success, WIFI_EVENT_TYPE_STATION_STATE_CHANGED / _CONNECTION_RESULT arrive
|
||||
// asynchronously via onWifiDeviceEvent().
|
||||
wifi_station_connect(state.device, target.ssid.c_str(), target.password.c_str(), target.channel);
|
||||
}
|
||||
|
||||
void dispatchDisconnect() {
|
||||
@@ -233,12 +262,14 @@ bool findAutoConnectAp(settings::WifiApSettings& out) {
|
||||
|
||||
void dispatchAutoConnect() {
|
||||
LOG_I(TAG, "dispatchAutoConnect()");
|
||||
if (state.pauseAutoConnect || state.externalScanPause.load()) {
|
||||
if (state.pauseAutoConnect || state.externalScanPause.load() || !isRadioOn()) {
|
||||
// A manual disconnect() or an in-progress manual connect() has paused
|
||||
// auto-connect, or a caller (e.g. AutoScanPauseGuard) has externally paused it.
|
||||
// This is called on every SCAN_FINISHED, not just the auto-connect timer's own
|
||||
// scans (e.g. WifiManage re-scans on show), so it must honor the pause instead of
|
||||
// reconnecting unconditionally.
|
||||
// reconnecting unconditionally. The radio-off check matters because a scan that was
|
||||
// already in flight can finish after the user turns the radio off. Without it,
|
||||
// connect() would call dispatchSetEnabled(true) and turn the radio back on.
|
||||
return;
|
||||
}
|
||||
RadioState radio_state = getRadioState();
|
||||
@@ -278,7 +309,7 @@ void onAutoConnectTimer() {
|
||||
|
||||
// ---- Kernel driver event bridge ----
|
||||
|
||||
void onWifiDeviceEvent(Device* device, void* /*context*/, ::WifiEvent event) {
|
||||
void onWifiDeviceEvent(Device* device, ::WifiEvent event) {
|
||||
switch (event.type) {
|
||||
case WIFI_EVENT_TYPE_SCAN_FINISHED:
|
||||
getMainDispatcher().dispatch([] { dispatchAutoConnect(); });
|
||||
@@ -333,10 +364,6 @@ void onWifiDeviceEvent(Device* device, void* /*context*/, ::WifiEvent event) {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Forward the event as-is: subscribers inspect event.type and the
|
||||
// relevant union field directly, same as this function does.
|
||||
publish(event);
|
||||
}
|
||||
|
||||
void autoScanSetPaused(bool paused) {
|
||||
@@ -348,12 +375,8 @@ void autoScanSetPaused(bool paused) {
|
||||
|
||||
// region Public functions
|
||||
|
||||
std::shared_ptr<PubSub<WifiEvent>> getPubsub() {
|
||||
return state.pubsub;
|
||||
}
|
||||
|
||||
RadioState getRadioState() {
|
||||
if (!started || state.device == nullptr || !device_is_ready(state.device)) {
|
||||
if (!started || state.device == nullptr) {
|
||||
return RadioState::Off;
|
||||
}
|
||||
|
||||
@@ -403,7 +426,7 @@ void connect(const settings::WifiApSettings& ap, bool remember) {
|
||||
state.pauseAutoConnect = true;
|
||||
state.connectionTarget = ap;
|
||||
state.connectionTargetRemember = remember;
|
||||
radio_off = !device_is_ready(state.device);
|
||||
radio_off = !isRadioOn();
|
||||
}
|
||||
|
||||
getMainDispatcher().dispatch([radio_off] {
|
||||
@@ -505,9 +528,17 @@ public:
|
||||
|
||||
wifi_auto_scan_set_paused_function(autoScanSetPaused);
|
||||
|
||||
state.device = wifi_find_first_registered_device();
|
||||
if (state.device == nullptr) {
|
||||
Device* wifi_device = nullptr;
|
||||
if (device_get_first_by_type(&WIFI_TYPE, &wifi_device) != ERROR_NONE) {
|
||||
LOG_W(TAG, "No WiFi device found");
|
||||
} else if (device_start(wifi_device) != ERROR_NONE) {
|
||||
LOG_E(TAG, "Failed to start WiFi device");
|
||||
device_put(wifi_device);
|
||||
} else {
|
||||
state.device = wifi_device;
|
||||
if (!startWifiEventThread()) {
|
||||
LOG_E(TAG, "Failed to subscribe to WiFi events");
|
||||
}
|
||||
}
|
||||
|
||||
if (system_event_callback_add(KERNEL_EVENT_BOOT_COMPLETED, onBootCompleted, nullptr) == ERROR_NONE) {
|
||||
@@ -535,9 +566,13 @@ public:
|
||||
state.bootEventSubscribed = false;
|
||||
}
|
||||
|
||||
if (state.device != nullptr && device_is_ready(state.device)) {
|
||||
wifi_remove_event_callback(state.device, onWifiDeviceEvent);
|
||||
if (state.device != nullptr) {
|
||||
if (isRadioOn()) {
|
||||
wifi_set_radio_off(state.device);
|
||||
}
|
||||
stopWifiEventThread();
|
||||
device_stop(state.device);
|
||||
device_put(state.device);
|
||||
}
|
||||
|
||||
state.secureConnection = false;
|
||||
|
||||
Reference in New Issue
Block a user