Added WiFi kernel drivers and refactored Wifi service (#557)

+ other improvements
This commit is contained in:
Ken Van Hoeylandt
2026-07-09 21:03:38 +02:00
committed by GitHub
parent dbb96a891c
commit 1c2806bddf
104 changed files with 2347 additions and 1428 deletions
+29 -2
View File
@@ -1,18 +1,45 @@
#pragma once
#include "tactility/concurrent/dispatcher.h"
#include "tactility/device.h"
#include "tactility/module.h"
#include <Tactility/Dispatcher.h>
#include <Tactility/app/AppManifest.h>
#include <Tactility/hal/Configuration.h>
#include <Tactility/service/ServiceManifest.h>
#include <functional>
extern "C" {
struct DtsDevice;
}
namespace tt {
/**
* A thin C++ wrapper around the TactilityKernel dispatcher, allowing
* std::function (and therefore capturing lambdas) to be dispatched.
*/
class MainDispatcher final {
public:
using Function = std::function<void()>;
explicit MainDispatcher(DispatcherHandle_t handle) : handle(handle) {}
/**
* Queue a function to be consumed on the main task.
* @param[in] function the function to execute elsewhere
* @param[in] timeout lock acquisition timeout
* @return true if dispatching was successful (timeout not reached)
*/
bool dispatch(Function function, TickType_t timeout = portMAX_DELAY) const;
private:
DispatcherHandle_t handle;
};
/** @brief The configuration for the operating system
* It contains the hardware configuration, apps and services
*/
@@ -39,7 +66,7 @@ const Configuration* getConfiguration();
* @warning This dispatcher is used for WiFi and might block for some time during WiFi connection.
* @return the dispatcher
*/
Dispatcher& getMainDispatcher();
MainDispatcher getMainDispatcher();
namespace hal {
@@ -1,24 +0,0 @@
#pragma once
#include <tactility/drivers/spi_controller.h>
#include <tactility/device.h>
#include <Tactility/Lock.h>
namespace tt {
class SpiDeviceLock : public Lock {
::Device* device;
public:
explicit SpiDeviceLock(::Device* device) : device(device) { }
bool lock(TickType_t timeout) const override {
return spi_controller_try_lock(device, timeout) == ERROR_NONE;
}
void unlock() const override {
spi_controller_unlock(device);
}
};
}
@@ -1,9 +1,8 @@
#pragma once
#ifdef ESP_PLATFORM
#include <esp_http_server.h>
#include <Tactility/RecursiveMutex.h>
#include <Tactility/kernel/SystemEvents.h>
#include <esp_http_server.h>
namespace tt::network {
@@ -6,52 +6,16 @@
#include <string>
#include <vector>
#include "WifiApSettings.h"
#include <tactility/drivers/wifi.h>
#ifdef ESP_PLATFORM
#include <esp_wifi.h>
#else
#include <cstdint>
// From esp_wifi_types.h in ESP-IDF 5.2
typedef enum {
WIFI_AUTH_OPEN = 0, /**< authenticate mode : open */
WIFI_AUTH_WEP, /**< authenticate mode : WEP */
WIFI_AUTH_WPA_PSK, /**< authenticate mode : WPA_PSK */
WIFI_AUTH_WPA2_PSK, /**< authenticate mode : WPA2_PSK */
WIFI_AUTH_WPA_WPA2_PSK, /**< authenticate mode : WPA_WPA2_PSK */
WIFI_AUTH_ENTERPRISE, /**< authenticate mode : WiFi EAP security */
WIFI_AUTH_WPA2_ENTERPRISE = WIFI_AUTH_ENTERPRISE, /**< authenticate mode : WiFi EAP security */
WIFI_AUTH_WPA3_PSK, /**< authenticate mode : WPA3_PSK */
WIFI_AUTH_WPA2_WPA3_PSK, /**< authenticate mode : WPA2_WPA3_PSK */
WIFI_AUTH_WAPI_PSK, /**< authenticate mode : WAPI_PSK */
WIFI_AUTH_OWE, /**< authenticate mode : OWE */
WIFI_AUTH_WPA3_ENT_192, /**< authenticate mode : WPA3_ENT_SUITE_B_192_BIT */
WIFI_AUTH_WPA3_EXT_PSK, /**< authenticate mode : WPA3_PSK_EXT_KEY */
WIFI_AUTH_WPA3_EXT_PSK_MIXED_MODE, /**< authenticate mode: WPA3_PSK + WPA3_PSK_EXT_KEY */
WIFI_AUTH_MAX
} wifi_auth_mode_t;
#endif
#include "WifiApSettings.h"
namespace tt::service::wifi {
enum class WifiEvent {
/** Radio was turned on */
RadioStateOn,
/** Radio is turning on. */
RadioStateOnPending,
/** Radio is turned off */
RadioStateOff,
/** Radio is turning off */
RadioStateOffPending,
/** Started scanning for access points */
ScanStarted,
/** Finished scanning for access points */ // TODO: 1 second validity
ScanFinished,
Disconnected,
ConnectionPending,
ConnectionSuccess,
ConnectionFailed
};
/** The kernel wifi driver's event type: a WifiEventType tag plus a union
* payload (radio_state/station_state/access_point_state/connection_error
* depending on the tag). See tactility/drivers/wifi.h. */
using WifiEvent = ::WifiEvent;
enum class RadioState {
OnPending,
@@ -62,13 +26,6 @@ enum class RadioState {
Off,
};
struct ApRecord {
std::string ssid;
int8_t rssi;
int32_t channel;
wifi_auth_mode_t auth_mode;
};
/**
* @brief Get wifi pubsub that broadcasts Event objects
* @return PubSub
@@ -93,7 +50,7 @@ bool isScanning();
std::string getConnectionTarget();
/** @return the access points from the last scan (if any). It only contains public APs. */
std::vector<ApRecord> getScanResults();
std::vector<WifiApRecord> getScanResults();
/**
* @brief Overrides the default scan result size of 16.
@@ -14,7 +14,7 @@ class State final {
bool scanning = false;
bool scannedAfterRadioOn = false;
service::wifi::RadioState radioState;
std::vector<service::wifi::ApRecord> apRecords;
std::vector<WifiApRecord> apRecords;
std::string connectSsid;
public:
@@ -30,14 +30,14 @@ public:
void updateApRecords();
template <std::invocable<const std::vector<service::wifi::ApRecord>&> Func>
template <std::invocable<const std::vector<WifiApRecord>&> Func>
void withApRecords(Func&& onApRecords) const {
mutex.withLock([&] {
std::invoke(std::forward<Func>(onApRecords), apRecords);
});
}
std::vector<service::wifi::ApRecord> getApRecords() const {
std::vector<WifiApRecord> getApRecords() const {
auto lock = mutex.asScopedLock();
lock.lock();
return apRecords;
@@ -27,7 +27,7 @@ class View final {
void updateScanning();
void updateNetworkList();
void updateConnectToHidden();
void createSsidListItem(const service::wifi::ApRecord& record, bool isConnecting, size_t index);
void createSsidListItem(const WifiApRecord& record, bool isConnecting, size_t index);
static void showDetails(lv_event_t* event);
static void connect(lv_event_t* event);
@@ -1,4 +1,4 @@
#include <Tactility/kernel/critical/Critical.h>
#include <Tactility/Critical.h>
#include <Tactility/freertoscompat/Task.h>
#include <Tactility/kernel/Kernel.h>
@@ -86,7 +86,7 @@ const CrashData& getRtcCrashData() { return crashData; }
// Stub implementation for RISC-V and other architectures
// TODO: Implement crash data collection for RISC-V using frame pointer or EH frame
#include <Tactility/kernel/PanicHandler.h>
#include <Tactility/PanicHandler.h>
static CrashData emptyCrashData = {};
@@ -1,4 +1,4 @@
#include "Tactility/kernel/Platform.h"
#include "../Include/Tactility/Platform.h"
namespace tt::kernel {
@@ -1,4 +1,4 @@
#include <Tactility/kernel/SystemEvents.h>
#include <Tactility/SystemEvents.h>
#include <Tactility/Mutex.h>
#include <tactility/check.h>
+23 -5
View File
@@ -9,7 +9,6 @@
#include <Tactility/LogMessages.h>
#include <Tactility/CpuAffinity.h>
#include <Tactility/DispatcherThread.h>
#include <Tactility/MountPoints.h>
#include <Tactility/app/AppManifestParsing.h>
#include <Tactility/app/AppRegistration.h>
@@ -46,7 +45,26 @@ namespace tt {
constexpr auto* TAG = "Tactility";
static const Configuration* config_instance = nullptr;
static Dispatcher mainDispatcher;
static DispatcherHandle_t mainDispatcherHandle = dispatcher_alloc();
namespace {
void mainDispatcherTrampoline(void* context) {
auto* function = static_cast<MainDispatcher::Function*>(context);
(*function)();
delete function;
}
} // namespace
bool MainDispatcher::dispatch(Function function, TickType_t timeout) const {
auto* boxed = new Function(std::move(function));
if (dispatcher_dispatch_timed(handle, boxed, mainDispatcherTrampoline, timeout) != ERROR_NONE) {
delete boxed;
return false;
}
return true;
}
// region Default services
namespace service {
@@ -376,7 +394,7 @@ void run(const Configuration& config, Module* dtsModules[], DtsDevice dtsDevices
LOG_I(TAG, "Main dispatcher ready");
while (true) {
mainDispatcher.consume();
dispatcher_consume(mainDispatcherHandle);
}
}
@@ -385,8 +403,8 @@ const Configuration* getConfiguration() {
return config_instance;
}
Dispatcher& getMainDispatcher() {
return mainDispatcher;
MainDispatcher getMainDispatcher() {
return MainDispatcher(mainDispatcherHandle);
}
} // namespace
+6 -7
View File
@@ -1,16 +1,15 @@
#include "Tactility/lvgl/Lvgl.h"
#include <Tactility/TactilityCore.h>
#include <Tactility/SystemEvents.h>
#include <Tactility/CpuAffinity.h>
#include <Tactility/Paths.h>
#include <Tactility/TactilityPrivate.h>
#include <Tactility/app/alertdialog/AlertDialog.h>
#include <Tactility/app/AppContext.h>
#include <Tactility/app/AppPaths.h>
#include <Tactility/CpuAffinity.h>
#include <Tactility/app/alertdialog/AlertDialog.h>
#include <Tactility/hal/display/DisplayDevice.h>
#include <Tactility/hal/usb/Usb.h>
#include <Tactility/kernel/SystemEvents.h>
#include <Tactility/lvgl/Style.h>
#include <Tactility/Paths.h>
#include <Tactility/service/loader/Loader.h>
#include <Tactility/settings/BootSettings.h>
#include <Tactility/settings/DisplaySettings.h>
@@ -21,8 +20,8 @@
#include <atomic>
#ifdef ESP_PLATFORM
#include "Tactility/app/crashdiagnostics/CrashDiagnostics.h"
#include <Tactility/kernel/PanicHandler.h>
#include <Tactility/app/crashdiagnostics/CrashDiagnostics.h>
#include <Tactility/PanicHandler.h>
#include <esp_system.h>
#include <sdkconfig.h>
#else
@@ -1,7 +1,7 @@
#ifdef ESP_PLATFORM
#include <Tactility/app/crashdiagnostics/QrUrl.h>
#include <Tactility/kernel/PanicHandler.h>
#include <Tactility/PanicHandler.h>
#include <sstream>
#include <vector>
+1 -2
View File
@@ -1,10 +1,9 @@
#include <Tactility/app/files/State.h>
#include <Tactility/LogMessages.h>
#include <Tactility/Platform.h>
#include <Tactility/MountPoints.h>
#include <Tactility/file/File.h>
#include <Tactility/file/FileLock.h>
#include <Tactility/kernel/Platform.h>
#include <tactility/log.h>
#include <cstring>
+1 -3
View File
@@ -1,16 +1,14 @@
#include <Tactility/app/files/SupportedFiles.h>
#include <Tactility/app/files/View.h>
#include <Tactility/LogMessages.h>
#include <Tactility/Platform.h>
#include <Tactility/StringUtils.h>
#include <Tactility/Tactility.h>
#include <Tactility/app/ElfApp.h>
#include <Tactility/app/alertdialog/AlertDialog.h>
#include <Tactility/app/imageviewer/ImageViewer.h>
#include <Tactility/app/inputdialog/InputDialog.h>
#include <Tactility/app/notes/Notes.h>
#include <Tactility/file/File.h>
#include <Tactility/kernel/Platform.h>
#include <Tactility/lvgl/LvglSync.h>
#include <Tactility/lvgl/Toolbar.h>
#include <tactility/check.h>
+3 -4
View File
@@ -2,12 +2,11 @@
#include <Tactility/file/File.h>
#include <Tactility/MountPoints.h>
#include <Tactility/kernel/Platform.h>
#include <Tactility/Platform.h>
#include <Tactility/LogMessages.h>
#include <cstring>
#include <dirent.h>
#include <tactility/log.h>
#include <dirent.h>
#include <unistd.h>
#include <vector>
+1 -1
View File
@@ -5,7 +5,7 @@
#include <Tactility/Tactility.h>
#include <Tactility/app/alertdialog/AlertDialog.h>
#include <Tactility/file/File.h>
#include <Tactility/kernel/Platform.h>
#include <Tactility/Platform.h>
#include <Tactility/lvgl/LvglSync.h>
#include <Tactility/lvgl/Toolbar.h>
#include <tactility/check.h>
@@ -3,9 +3,9 @@
#if TT_FEATURE_SCREENSHOT_ENABLED
#include <Tactility/Platform.h>
#include <Tactility/app/App.h>
#include <Tactility/app/AppManifest.h>
#include <Tactility/kernel/Platform.h>
#include <Tactility/lvgl/Lvgl.h>
#include <Tactility/lvgl/LvglSync.h>
#include <Tactility/lvgl/Toolbar.h>
@@ -25,22 +25,19 @@ static void onConnect(const service::wifi::settings::WifiApSettings& ap_settings
void WifiConnect::onWifiEvent(service::wifi::WifiEvent event) {
State& state = getState();
switch (event) {
case service::wifi::WifiEvent::ConnectionFailed:
if (event.type == WIFI_EVENT_TYPE_STATION_CONNECTION_RESULT) {
if (event.connection_error == WIFI_STATION_CONNECTION_ERROR_NONE) {
if (state.isConnecting()) {
state.setConnecting(false);
stop(manifest.appId);
}
} else {
if (state.isConnecting()) {
state.setConnecting(false);
state.setConnectionError(true);
requestViewUpdate();
}
break;
case service::wifi::WifiEvent::ConnectionSuccess:
if (getState().isConnecting()) {
state.setConnecting(false);
stop(manifest.appId);
}
break;
default:
break;
}
}
requestViewUpdate();
}
+6 -6
View File
@@ -76,7 +76,7 @@ void View::connect(lv_event_t* event) {
if (index < ap_records.size()) {
LOG_I(TAG, "Clicked %zu/%zu", index, ap_records.size() - 1);
auto& ssid = ap_records[index].ssid;
std::string ssid = ap_records[index].ssid;
LOG_I(TAG, "Clicked AP: %s", ssid.c_str());
std::string connection_target = service::wifi::getConnectionTarget();
if (connection_target == ssid) {
@@ -97,7 +97,7 @@ void View::showDetails(lv_event_t* event) {
auto ap_records = self->state->getApRecords();
if (index < ap_records.size()) {
auto& ssid = ap_records[index].ssid;
std::string ssid = ap_records[index].ssid;
LOG_I(TAG, "Clicked AP: %s", ssid.c_str());
self->bindings->onShowApSettings(ssid);
} else {
@@ -105,14 +105,14 @@ void View::showDetails(lv_event_t* event) {
}
}
void View::createSsidListItem(const service::wifi::ApRecord& record, bool isConnecting, size_t index) {
void View::createSsidListItem(const WifiApRecord& record, bool isConnecting, size_t index) {
if (isConnecting) {
auto* button = lv_list_add_button(networks_list, LV_SYMBOL_WIFI, record.ssid.c_str());
auto* button = lv_list_add_button(networks_list, LV_SYMBOL_WIFI, record.ssid);
lv_obj_add_event_cb(button, showDetails, LV_EVENT_SHORT_CLICKED, this);
} else {
const std::string auth_info = (record.auth_mode == WIFI_AUTH_OPEN) ? "(open) " : " ";
const std::string auth_info = (record.authentication_type == WIFI_AUTHENTICATION_TYPE_OPEN) ? "(open) " : " ";
const auto percentage = mapRssiToPercentage(record.rssi);
const auto label = std::format("{} {}{}%", record.ssid, auth_info, percentage);
const auto label = std::format("{} {}{}%", std::string(record.ssid), auth_info, percentage);
auto* button = lv_list_add_button(networks_list, nullptr, label.c_str());
lv_obj_set_user_data(button, reinterpret_cast<void*>(index));
if (service::wifi::settings::contains(record.ssid)) {
@@ -79,17 +79,16 @@ void WifiManage::onWifiEvent(service::wifi::WifiEvent event) {
auto radio_state = service::wifi::getRadioState();
LOG_I(TAG, "Update with state %s", service::wifi::radioStateToString(radio_state));
getState().setRadioState(radio_state);
switch (event) {
using enum service::wifi::WifiEvent;
case ScanStarted:
switch (event.type) {
case WIFI_EVENT_TYPE_SCAN_STARTED:
getState().setScanning(true);
break;
case ScanFinished:
case WIFI_EVENT_TYPE_SCAN_FINISHED:
getState().setScanning(false);
getState().updateApRecords();
break;
case RadioStateOn:
if (!service::wifi::isScanning()) {
case WIFI_EVENT_TYPE_RADIO_STATE_CHANGED:
if (event.radio_state == WIFI_RADIO_STATE_ON && !service::wifi::isScanning()) {
service::wifi::scan();
}
break;
+2 -2
View File
@@ -1,9 +1,9 @@
#include <Tactility/SystemEvents.h>
#include <Tactility/Tactility.h>
#include <Tactility/hal/Configuration.h>
#include <Tactility/hal/display/DisplayDevice.h>
#include <Tactility/hal/SdCard.h>
#include <Tactility/hal/display/DisplayDevice.h>
#include <Tactility/hal/touch/TouchDevice.h>
#include <Tactility/kernel/SystemEvents.h>
#include <tactility/check.h>
#include <tactility/hal/Device.h>
+2 -2
View File
@@ -1,11 +1,10 @@
#define LV_USE_PRIVATE_API 1 // For actual lv_obj_t declaration
#include <Tactility/LogMessages.h>
#include <Tactility/SystemEvents.h>
#include <Tactility/PubSub.h>
#include <Tactility/RecursiveMutex.h>
#include <Tactility/Tactility.h>
#include <Tactility/Timer.h>
#include <Tactility/kernel/SystemEvents.h>
#include <Tactility/lvgl/LvglSync.h>
#include <Tactility/lvgl/Statusbar.h>
#include <Tactility/lvgl/Style.h>
@@ -17,6 +16,7 @@
#include <tactility/lvgl_module.h>
#include <lvgl.h>
#include <memory>
namespace tt::lvgl {
+1 -1
View File
@@ -6,7 +6,7 @@
#include <memory>
#ifdef ESP_PLATFORM
#include <Tactility/kernel/SystemEvents.h>
#include <Tactility/SystemEvents.h>
#include <Tactility/TactilityCore.h>
#include <esp_netif_sntp.h>
#include <esp_sntp.h>
+494 -1
View File
@@ -1,12 +1,30 @@
#include <Tactility/service/wifi/Wifi.h>
#include <Tactility/CoreDefines.h>
#include <tactility/check.h>
#include <Tactility/LogMessages.h>
#include <Tactility/RecursiveMutex.h>
#include <Tactility/SystemEvents.h>
#include <Tactility/Tactility.h>
#include <Tactility/Timer.h>
#include <Tactility/service/Service.h>
#include <Tactility/service/ServiceManifest.h>
#include <Tactility/service/ServiceRegistration.h>
#include <Tactility/service/wifi/WifiBootSplashInit.h>
#include <Tactility/service/wifi/WifiGlobals.h>
#include <Tactility/service/wifi/WifiSettings.h>
#include <tactility/check.h>
#include <tactility/device.h>
#include <tactility/drivers/wifi.h>
#include <tactility/log.h>
#include <algorithm>
namespace tt::service::wifi {
constexpr auto* TAG = "WifiService";
constexpr auto AUTO_SCAN_INTERVAL = 10000; // ms
const char* radioStateToString(RadioState state) {
switch (state) {
using enum RadioState;
@@ -32,4 +50,479 @@ std::shared_ptr<ServiceContext> findServiceContext() {
return findServiceContextById(manifest.id);
}
namespace {
// Everything below wraps a TactilityKernel WIFI_TYPE device: the driver owns
// the radio state, station state and scan results, this file only tracks the
// bits the kernel driver doesn't (in-flight connection target/credentials,
// auto-connect bookkeeping).
/** 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;
bool pauseAutoConnect = false;
bool connectionTargetRemember = false;
settings::WifiApSettings connectionTarget;
uint16_t scanRecordLimit = TT_WIFI_SCAN_RECORD_LIMIT;
TickType_t lastScanTime = kernel::MAX_TICKS;
std::unique_ptr<Timer> autoConnectTimer;
kernel::SystemEventSubscription bootEventSubscription = kernel::NoSystemEventSubscription;
};
WifiServiceState state;
bool started = false;
void onWifiDeviceEvent(Device* device, void* context, ::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);
}
RadioState combineRadioState(WifiRadioState radio, WifiStationState station) {
switch (radio) {
case WIFI_RADIO_STATE_OFF: return RadioState::Off;
case WIFI_RADIO_STATE_ON_PENDING: return RadioState::OnPending;
case WIFI_RADIO_STATE_OFF_PENDING: return RadioState::OffPending;
case WIFI_RADIO_STATE_ON:
switch (station) {
case WIFI_STATION_STATE_CONNECTION_PENDING: return RadioState::ConnectionPending;
case WIFI_STATION_STATE_CONNECTED: return RadioState::ConnectionActive;
case WIFI_STATION_STATE_DISCONNECTED: default: return RadioState::On;
}
}
return RadioState::Off;
}
// ---- Dispatched work (runs on the main task) ----
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) {
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);
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);
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;
state.lastScanTime = kernel::getTicks();
error_t result = wifi_scan(state.device);
if (result != ERROR_NONE) {
LOG_I(TAG, "Can't start scan (%s)", error_to_string(result));
}
}
void dispatchConnect() {
LOG_I(TAG, "dispatchConnect()");
if (!started || state.device == nullptr) return;
settings::WifiApSettings target;
{
auto lock = state.mutex.asScopedLock();
if (!lock.lock(50 / portTICK_PERIOD_MS)) {
LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "dispatchConnect()");
return;
}
target = state.connectionTarget;
}
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().
}
void dispatchDisconnect() {
LOG_I(TAG, "dispatchDisconnect()");
if (!started || state.device == nullptr) return;
error_t result = wifi_station_disconnect(state.device);
if (result != ERROR_NONE) {
LOG_E(TAG, "Failed to disconnect (%s)", error_to_string(result));
}
// The Disconnected event arrives asynchronously via onWifiDeviceEvent().
}
bool findAutoConnectAp(settings::WifiApSettings& out) {
for (const auto& record : getScanResults()) {
if (settings::contains(record.ssid)) {
settings::WifiApSettings loaded;
if (settings::load(record.ssid, loaded)) {
if (loaded.autoConnect) {
out = loaded;
return true;
}
} else {
LOG_E(TAG, "Failed to load credentials for ssid %s", record.ssid);
}
}
}
return false;
}
void dispatchAutoConnect() {
LOG_I(TAG, "dispatchAutoConnect()");
if (state.pauseAutoConnect) {
// A manual disconnect() or an in-progress manual connect() has paused
// auto-connect. 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.
return;
}
RadioState radio_state = getRadioState();
if (radio_state == RadioState::ConnectionActive || radio_state == RadioState::ConnectionPending) {
// Already connected (or connecting): reconnecting to the same AP would just
// force a pointless disconnect/reconnect blip, e.g. when WifiManage's
// on-show scan finishes while we're already on the saved auto-connect AP.
return;
}
settings::WifiApSettings target;
if (findAutoConnectAp(target)) {
LOG_I(TAG, "Auto-connecting to %s", target.ssid.c_str());
connect(target, false);
// connect() pauses auto-connect (it assumes a manual/user call); undo that
// since this call was automatic.
state.pauseAutoConnect = false;
}
}
bool shouldScanForAutoConnect() {
bool radio_scannable = getRadioState() == RadioState::On && !isScanning() && !state.pauseAutoConnect;
if (!radio_scannable) return false;
TickType_t current_time = kernel::getTicks();
bool scan_time_has_looped = current_time < state.lastScanTime;
bool no_recent_scan = (current_time - state.lastScanTime) > (AUTO_SCAN_INTERVAL / portTICK_PERIOD_MS);
return scan_time_has_looped || no_recent_scan;
}
void onAutoConnectTimer() {
if (!started || state.device == nullptr) return;
if (shouldScanForAutoConnect()) {
getMainDispatcher().dispatch([] { dispatchScan(); });
}
}
// ---- Kernel driver event bridge ----
void onWifiDeviceEvent(Device* /*device*/, void* /*context*/, ::WifiEvent event) {
switch (event.type) {
case WIFI_EVENT_TYPE_SCAN_FINISHED:
getMainDispatcher().dispatch([] { dispatchAutoConnect(); });
break;
case WIFI_EVENT_TYPE_STATION_STATE_CHANGED:
if (event.station_state == WIFI_STATION_STATE_DISCONNECTED) {
// Don't touch pauseAutoConnect here: a deliberate disconnect() sets it
// and relies on it staying set until a new connection is established.
// Resetting it on every disconnect (including deliberate ones) would
// let auto-connect immediately reconnect the user. Attempts that fail
// while pending are unpaused via WIFI_EVENT_TYPE_STATION_CONNECTION_RESULT below.
kernel::publishSystemEvent(kernel::SystemEvent::NetworkDisconnected);
}
break;
case WIFI_EVENT_TYPE_STATION_CONNECTION_RESULT:
if (event.connection_error == WIFI_STATION_CONNECTION_ERROR_NONE) {
settings::WifiApSettings target;
bool remember;
{
auto lock = state.mutex.asScopedLock();
if (lock.lock(50 / portTICK_PERIOD_MS)) {
target = state.connectionTarget;
remember = state.connectionTargetRemember;
state.secureConnection = !target.password.empty();
} else {
remember = false;
}
}
{
auto lock = state.mutex.asScopedLock();
if (lock.lock(50 / portTICK_PERIOD_MS)) {
state.pauseAutoConnect = false;
}
}
LOG_I(TAG, "Connected to %s", target.ssid.c_str());
if (remember && !settings::save(target)) {
LOG_E(TAG, "Failed to store credentials");
}
kernel::publishSystemEvent(kernel::SystemEvent::NetworkConnected);
} else {
// The pending connection attempt (which paused auto-connect via connect())
// failed; unpause so auto-connect can try other saved APs.
auto lock = state.mutex.asScopedLock();
if (lock.lock(50 / portTICK_PERIOD_MS)) {
state.pauseAutoConnect = false;
}
}
break;
default:
break;
}
// Forward the event as-is: subscribers inspect event.type and the
// relevant union field directly, same as this function does.
publish(event);
}
} // namespace
// region Public functions
std::shared_ptr<PubSub<WifiEvent>> getPubsub() {
return state.pubsub;
}
RadioState getRadioState() {
if (!started || state.device == nullptr || !device_is_ready(state.device)) {
return RadioState::Off;
}
WifiRadioState radio = WIFI_RADIO_STATE_OFF;
WifiStationState station = WIFI_STATION_STATE_DISCONNECTED;
wifi_get_radio_state(state.device, &radio);
wifi_get_station_state(state.device, &station);
return combineRadioState(radio, station);
}
std::string getConnectionTarget() {
RadioState radio_state = getRadioState();
if (radio_state != RadioState::ConnectionPending && radio_state != RadioState::ConnectionActive) {
return "";
}
char ssid[33] = {};
if (wifi_station_get_target_ssid(state.device, ssid) != ERROR_NONE) {
return "";
}
return { ssid };
}
void scan() {
LOG_I(TAG, "scan()");
if (!started || state.device == nullptr) return;
getMainDispatcher().dispatch([] { dispatchScan(); });
}
bool isScanning() {
if (!started || state.device == nullptr) return false;
return wifi_is_scanning(state.device);
}
void connect(const settings::WifiApSettings& ap, bool remember) {
LOG_I(TAG, "connect(%s, %d)", ap.ssid.c_str(), (int)remember);
if (!started || state.device == nullptr) return;
bool radio_off;
{
auto lock = state.mutex.asScopedLock();
if (!lock.lock(10 / portTICK_PERIOD_MS)) {
LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED);
return;
}
// Stop auto-connecting until the connection is established.
state.pauseAutoConnect = true;
state.connectionTarget = ap;
state.connectionTargetRemember = remember;
radio_off = !device_is_ready(state.device);
}
getMainDispatcher().dispatch([radio_off] {
if (radio_off) {
dispatchSetEnabled(true);
}
dispatchConnect();
});
}
void disconnect() {
LOG_I(TAG, "disconnect()");
if (!started || state.device == nullptr) return;
{
auto lock = state.mutex.asScopedLock();
if (!lock.lock(10 / portTICK_PERIOD_MS)) {
LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED);
return;
}
state.connectionTarget = settings::WifiApSettings("", "");
// Manual disconnect (e.g. via app) should stop auto-connecting until a new connection is established.
state.pauseAutoConnect = true;
}
getMainDispatcher().dispatch([] { dispatchDisconnect(); });
}
void setScanRecords(uint16_t records) {
LOG_I(TAG, "setScanRecords(%u)", records);
if (!started) return;
auto lock = state.mutex.asScopedLock();
if (lock.lock(10 / portTICK_PERIOD_MS)) {
state.scanRecordLimit = records;
}
}
std::vector<WifiApRecord> getScanResults() {
std::vector<WifiApRecord> records;
if (!started || state.device == nullptr) return records;
records.resize(state.scanRecordLimit);
size_t count = records.size();
if (wifi_get_scan_results(state.device, records.data(), &count) != ERROR_NONE) {
records.clear();
return records;
}
records.resize(count);
return records;
}
void setEnabled(bool enabled) {
LOG_I(TAG, "setEnabled(%d)", (int)enabled);
if (!started || state.device == nullptr) return;
getMainDispatcher().dispatch([enabled] { dispatchSetEnabled(enabled); });
}
bool isConnectionSecure() {
return state.secureConnection;
}
int getRssi() {
if (!started || state.device == nullptr) return 1;
int32_t rssi = 0;
if (wifi_station_get_rssi(state.device, &rssi) == ERROR_NONE) {
return rssi;
}
return 1;
}
std::string getIp() {
if (!started || state.device == nullptr) return "";
char ipv4[16] = {};
if (wifi_station_get_ipv4_address(state.device, ipv4) != ERROR_NONE) {
return "";
}
return { ipv4 };
}
// endregion Public functions
namespace {
class WifiService final : public Service {
public:
bool onStart(ServiceContext& /*service*/) override {
check(!started);
state.device = wifi_find_first_registered_device();
if (state.device == nullptr) {
LOG_W(TAG, "No WiFi device found");
}
state.bootEventSubscription = kernel::subscribeSystemEvent(kernel::SystemEvent::BootSplash, [](auto) {
bootSplashInit();
});
auto timer_interval = std::min(2000, AUTO_SCAN_INTERVAL);
state.autoConnectTimer = std::make_unique<Timer>(Timer::Type::Periodic, timer_interval, [] { onAutoConnectTimer(); });
// We want to try and scan more often in case of startup or scan lock failure.
state.autoConnectTimer->start();
started = true;
return true;
}
void onStop(ServiceContext& /*service*/) override {
check(started);
started = false;
state.autoConnectTimer->stop();
state.autoConnectTimer = nullptr; // Must release as it holds a reference via its callback.
kernel::unsubscribeSystemEvent(state.bootEventSubscription);
state.bootEventSubscription = kernel::NoSystemEventSubscription;
if (state.device != nullptr && device_is_ready(state.device)) {
wifi_remove_event_callback(state.device, onWifiDeviceEvent);
device_stop(state.device);
}
state.secureConnection = false;
state.pauseAutoConnect = false;
state.device = nullptr;
}
};
} // namespace
extern const ServiceManifest manifest = {
.id = "wifi",
.createService = create<WifiService>
};
} // namespace tt::service::wifi
-967
View File
@@ -1,967 +0,0 @@
#ifdef ESP_PLATFORM
#include <sdkconfig.h>
#endif
#if defined(CONFIG_SOC_WIFI_SUPPORTED) || defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
#include <Tactility/service/wifi/Wifi.h>
#include <Tactility/LogMessages.h>
#include <Tactility/EventGroup.h>
#include <Tactility/RecursiveMutex.h>
#include <Tactility/Tactility.h>
#include <Tactility/Timer.h>
#include <Tactility/kernel/SystemEvents.h>
#include <Tactility/service/ServiceContext.h>
#include <Tactility/service/wifi/WifiBootSplashInit.h>
#include <Tactility/service/wifi/WifiGlobals.h>
#include <Tactility/service/wifi/WifiSettings.h>
#include <tactility/check.h>
#include <tactility/log.h>
#include <esp_wifi_default.h>
#include <lwip/esp_netif_net_stack.h>
#include <freertos/FreeRTOS.h>
#include <atomic>
#include <cstring>
#include <sys/cdefs.h>
namespace tt::service::wifi {
constexpr auto* TAG = "WifiService";
constexpr auto WIFI_CONNECTED_BIT = BIT0;
constexpr auto WIFI_FAIL_BIT = BIT1;
constexpr auto AUTO_SCAN_INTERVAL = 10000; // ms
// Forward declarations
class Wifi;
static void scan_list_free_safely(std::shared_ptr<Wifi> wifi);
// Methods for main thread dispatcher
static void dispatchAutoConnect(std::shared_ptr<Wifi> wifi);
static void dispatchEnable(std::shared_ptr<Wifi> wifi);
static void dispatchDisable(std::shared_ptr<Wifi> wifi);
static void dispatchScan(std::shared_ptr<Wifi> wifi);
static void dispatchConnect(std::shared_ptr<Wifi> wifi);
static void dispatchDisconnectButKeepActive(std::shared_ptr<Wifi> wifi);
class Wifi {
std::atomic<RadioState> radio_state = RadioState::Off;
std::atomic<bool> scan_active = false;
std::atomic<bool> secure_connection = false;
public:
/** @brief Locking mechanism for modifying the Wifi instance */
RecursiveMutex radioMutex;
RecursiveMutex dataMutex;
std::unique_ptr<Timer> autoConnectTimer;
/** @brief The public event bus */
std::shared_ptr<PubSub<WifiEvent>> pubsub = std::make_shared<PubSub<WifiEvent>>();
// TODO: Deal with messages that come in while an action is ongoing
// for example: when scanning and you turn off the radio, the scan should probably stop or turning off
// the radio should disable the on/off button in the app as it is pending.
/** @brief The network interface when wifi is started */
esp_netif_t* netif = nullptr;
/** @brief Scanning results */
wifi_ap_record_t* scan_list = nullptr;
/** @brief The current item count in scan_list (-1 when scan_list is NULL) */
uint16_t scan_list_count = 0;
/** @brief Maximum amount of records to scan (value > 0) */
uint16_t scan_list_limit = TT_WIFI_SCAN_RECORD_LIMIT;
/** @brief when we last requested a scan. Loops around every 50 days. */
TickType_t last_scan_time = kernel::MAX_TICKS;
esp_event_handler_instance_t event_handler_any_id = nullptr;
esp_event_handler_instance_t event_handler_got_ip = nullptr;
EventGroup connection_wait_flags;
settings::WifiApSettings connection_target;
bool pause_auto_connect = false; // Pause when manually disconnecting until manually connecting again
bool connection_target_remember = false; // Whether to store the connection_target on successful connection or not
esp_netif_ip_info_t ip_info;
kernel::SystemEventSubscription bootEventSubscription = kernel::NoSystemEventSubscription;
RadioState getRadioState() const {
return radio_state;
}
void setRadioState(RadioState newState) {
radio_state = newState;
}
bool isScanning() const {
return scan_active;
}
void setScanning(bool newState) {
scan_active = newState;
}
bool isScanActive() const {
return scan_active;
}
void setScanActive(bool newState) {
scan_active = newState;
}
bool isSecureConnection() const {
return secure_connection;
}
void setSecureConnection(bool newState) {
secure_connection = newState;
}
};
static std::shared_ptr<Wifi> wifi_singleton;
// region Public functions
std::shared_ptr<PubSub<WifiEvent>> getPubsub() {
auto wifi = wifi_singleton;
if (wifi == nullptr) {
check(false, "Service not running");
}
return wifi->pubsub;
}
RadioState getRadioState() {
auto wifi = wifi_singleton;
if (wifi != nullptr) {
return wifi->getRadioState();
} else {
return RadioState::Off;
}
}
std::string getConnectionTarget() {
auto wifi = wifi_singleton;
if (wifi == nullptr) {
return "";
}
RadioState state = wifi->getRadioState();
if (
state != RadioState::ConnectionPending &&
state != RadioState::ConnectionActive
) {
return "";
}
return wifi->connection_target.ssid;
}
void scan() {
LOG_I(TAG, "scan()");
auto wifi = wifi_singleton;
if (wifi == nullptr) {
return;
}
getMainDispatcher().dispatch([wifi]() { dispatchScan(wifi); });
}
bool isScanning() {
auto wifi = wifi_singleton;
if (wifi == nullptr) {
return false;
} else {
return wifi->isScanActive();
}
}
void connect(const settings::WifiApSettings& ap, bool remember) {
LOG_I(TAG, "connect(%s, %d)", ap.ssid.c_str(), (int)remember);
auto wifi = wifi_singleton;
if (wifi == nullptr) {
return;
}
auto lock = wifi->dataMutex.asScopedLock();
if (!lock.lock(10 / portTICK_PERIOD_MS)) {
return;
}
// Stop auto-connecting until the connection is established
wifi->pause_auto_connect = true;
wifi->connection_target = ap;
wifi->connection_target_remember = remember;
if (wifi->getRadioState() == RadioState::Off) {
getMainDispatcher().dispatch([wifi] { dispatchEnable(wifi); });
}
getMainDispatcher().dispatch([wifi] { dispatchConnect(wifi); });
}
void disconnect() {
LOG_I(TAG, "disconnect()");
auto wifi = wifi_singleton;
if (wifi == nullptr) {
return;
}
auto lock = wifi->dataMutex.asScopedLock();
if (!lock.lock(10 / portTICK_PERIOD_MS)) {
return;
}
wifi->connection_target = settings::WifiApSettings("", "");
// Manual disconnect (e.g. via app) should stop auto-connecting until a new connection is established
wifi->pause_auto_connect = true;
getMainDispatcher().dispatch([wifi]() { dispatchDisconnectButKeepActive(wifi); });
}
void clearIp() {
auto wifi = wifi_singleton;
if (wifi == nullptr) {
return;
}
auto lock = wifi->dataMutex.asScopedLock();
if (!lock.lock(10 / portTICK_PERIOD_MS)) {
return;
}
memset(&wifi->ip_info, 0, sizeof(esp_netif_ip_info_t));
}
void setScanRecords(uint16_t records) {
LOG_I(TAG, "setScanRecords(%u)", records);
auto wifi = wifi_singleton;
if (wifi == nullptr) {
return;
}
auto lock = wifi->dataMutex.asScopedLock();
if (!lock.lock(10 / portTICK_PERIOD_MS)) {
return;
}
if (records != wifi->scan_list_limit) {
scan_list_free_safely(wifi);
wifi->scan_list_limit = records;
}
}
std::vector<ApRecord> getScanResults() {
LOG_I(TAG, "getScanResults()");
auto wifi = wifi_singleton;
std::vector<ApRecord> records;
if (wifi == nullptr) {
return records;
}
auto lock = wifi->dataMutex.asScopedLock();
if (!lock.lock(10 / portTICK_PERIOD_MS)) {
return records;
}
if (wifi->scan_list_count > 0) {
uint16_t i = 0;
for (; i < wifi->scan_list_count; ++i) {
const auto& scanned_item = wifi->scan_list[i];
records.push_back((ApRecord) {
.ssid = reinterpret_cast<const char*>(scanned_item.ssid),
.rssi = scanned_item.rssi,
.channel = scanned_item.primary,
.auth_mode = scanned_item.authmode
});
}
}
return records;
}
void setEnabled(bool enabled) {
LOG_I(TAG, "setEnabled(%d)", (int)enabled);
auto wifi = wifi_singleton;
if (wifi == nullptr) {
return;
}
auto lock = wifi->dataMutex.asScopedLock();
if (!lock.lock(10 / portTICK_PERIOD_MS)) {
return;
}
if (enabled) {
getMainDispatcher().dispatch([wifi] { dispatchEnable(wifi); });
} else {
getMainDispatcher().dispatch([wifi] { dispatchDisable(wifi); });
}
wifi->pause_auto_connect = false;
wifi->last_scan_time = 0;
}
bool isConnectionSecure() {
auto wifi = wifi_singleton;
if (wifi == nullptr) {
return false;
}
return wifi->isSecureConnection();
}
int getRssi() {
assert(wifi_singleton);
static int rssi = 0;
if (esp_wifi_sta_get_rssi(&rssi) == ESP_OK) {
return rssi;
} else {
return 1;
}
}
// endregion Public functions
static void scan_list_alloc(std::shared_ptr<Wifi> wifi) {
auto lock = wifi->dataMutex.asScopedLock();
if (lock.lock()) {
assert(wifi->scan_list == nullptr);
wifi->scan_list = static_cast<wifi_ap_record_t*>(malloc(sizeof(wifi_ap_record_t) * wifi->scan_list_limit));
wifi->scan_list_count = 0;
}
}
static void scan_list_alloc_safely(std::shared_ptr<Wifi> wifi) {
auto lock = wifi->dataMutex.asScopedLock();
if (lock.lock()) {
if (wifi->scan_list == nullptr) {
scan_list_alloc(wifi);
}
}
}
static void scan_list_free(std::shared_ptr<Wifi> wifi) {
auto lock = wifi->dataMutex.asScopedLock();
if (lock.lock()) {
assert(wifi->scan_list != nullptr);
free(wifi->scan_list);
wifi->scan_list = nullptr;
wifi->scan_list_count = 0;
}
}
static void scan_list_free_safely(std::shared_ptr<Wifi> wifi) {
auto lock = wifi->dataMutex.asScopedLock();
if (lock.lock()) {
if (wifi->scan_list != nullptr) {
scan_list_free(wifi);
}
}
}
static void publish_event(std::shared_ptr<Wifi> wifi, WifiEvent event) {
auto lock = wifi->dataMutex.asScopedLock();
if (lock.lock()) {
wifi->pubsub->publish(event);
}
}
static bool copy_scan_list(std::shared_ptr<Wifi> wifi) {
auto state = wifi->getRadioState();
bool can_fetch_results = (state == RadioState::On || state == RadioState::ConnectionActive) &&
wifi->isScanActive();
if (!can_fetch_results) {
LOG_I(TAG, "Skip scan result fetching");
return false;
}
auto lock = wifi->dataMutex.asScopedLock();
if (!lock.lock()) {
return false;
}
// Create scan list if it does not exist
scan_list_alloc_safely(wifi);
wifi->scan_list_count = 0;
uint16_t record_count = wifi->scan_list_limit;
esp_err_t scan_result = esp_wifi_scan_get_ap_records(&record_count, wifi->scan_list);
if (scan_result == ESP_OK) {
uint16_t safe_record_count = std::min(wifi->scan_list_limit, record_count);
wifi->scan_list_count = safe_record_count;
LOG_I(TAG, "Scanned %u APs. Showing %u:", record_count, safe_record_count);
for (uint16_t i = 0; i < safe_record_count; i++) {
wifi_ap_record_t* record = &wifi->scan_list[i];
if (record->ssid[0] != 0 && record->primary != 0) {
LOG_I(TAG, " - SSID %s, RSSI %d, channel %u, BSSID %02x:%02x:%02x:%02x:%02x:%02x",
reinterpret_cast<const char*>(record->ssid),
record->rssi,
record->primary,
record->bssid[0],
record->bssid[1],
record->bssid[2],
record->bssid[3],
record->bssid[4],
record->bssid[5]
);
} else {
LOG_I(TAG, " - (missing channel info)"); // Behaviour on on P4 with C6
}
}
return true;
} else {
LOG_I(TAG, "Failed to get scanned records: %s", esp_err_to_name(scan_result));
return false;
}
}
static bool find_auto_connect_ap(std::shared_ptr<Wifi> wifi, settings::WifiApSettings& settings) {
LOG_I(TAG, "find_auto_connect_ap()");
auto lock = wifi->dataMutex.asScopedLock();
if (lock.lock(10 / portTICK_PERIOD_MS)) {
for (int i = 0; i < wifi->scan_list_count; ++i) {
auto ssid = reinterpret_cast<const char*>(wifi->scan_list[i].ssid);
if (settings::contains(ssid)) {
static_assert(sizeof(wifi->scan_list[i].ssid) == (TT_WIFI_SSID_LIMIT + 1), "SSID size mismatch");
if (settings::load(ssid, settings)) {
if (settings.autoConnect) {
return true;
}
} else {
LOG_E(TAG, "Failed to load credentials for ssid %s", ssid);
}
break;
}
}
}
return false;
}
static void dispatchAutoConnect(std::shared_ptr<Wifi> wifi) {
LOG_I(TAG, "dispatchAutoConnect()");
settings::WifiApSettings settings;
if (find_auto_connect_ap(wifi, settings)) {
LOG_I(TAG, "Auto-connecting to %s", settings.ssid.c_str());
connect(settings, false);
// TODO: We currently have to manually reset it because connect() sets it.
// connect() assumes it's only being called by the user and not internally, so it disables auto-connect
wifi->pause_auto_connect = false;
}
}
static void eventHandler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
auto wifi = wifi_singleton;
if (wifi == nullptr) {
LOG_E(TAG, "eventHandler: no wifi instance");
return;
}
if (event_base == WIFI_EVENT) {
LOG_I(TAG, "eventHandler: WIFI_EVENT %d", (int)event_id);
} else if (event_base == IP_EVENT) {
LOG_I(TAG, "eventHandler: IP_EVENT %d", (int)event_id);
}
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
LOG_I(TAG, "eventHandler: STA_START");
if (wifi->getRadioState() == RadioState::ConnectionPending) {
esp_wifi_connect();
}
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
LOG_I(TAG, "eventHandler: STA_DISCONNECTED");
clearIp();
switch (wifi->getRadioState()) {
case RadioState::ConnectionPending:
wifi->connection_wait_flags.set(WIFI_FAIL_BIT);
break;
case RadioState::On:
// Ensure we can reconnect again
wifi->pause_auto_connect = false;
break;
default:
break;
}
wifi->setRadioState(RadioState::On);
publish_event(wifi, WifiEvent::Disconnected);
kernel::publishSystemEvent(kernel::SystemEvent::NetworkDisconnected);
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
auto* event = static_cast<ip_event_got_ip_t*>(event_data);
memcpy(&wifi->ip_info, &event->ip_info, sizeof(esp_netif_ip_info_t));
LOG_I(TAG, "eventHandler: got ip: %d.%d.%d.%d", IP2STR(&event->ip_info.ip));
if (wifi->getRadioState() == RadioState::ConnectionPending) {
wifi->connection_wait_flags.set(WIFI_CONNECTED_BIT);
// We resume auto-connecting only when there was an explicit request by the user for the connection
// TODO: Make thread-safe
wifi->pause_auto_connect = false; // Resume auto-connection
}
kernel::publishSystemEvent(kernel::SystemEvent::NetworkConnected);
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_SCAN_DONE) {
auto* event = static_cast<wifi_event_sta_scan_done_t*>(event_data);
LOG_I(TAG, "eventHandler: wifi scanning done (scan id %u)", event->scan_id);
bool copied_list = copy_scan_list(wifi);
auto state = wifi->getRadioState();
if (
state != RadioState::Off &&
state != RadioState::OffPending
) {
wifi->setScanActive(false);
esp_wifi_scan_stop();
}
publish_event(wifi_singleton, WifiEvent::ScanFinished);
LOG_I(TAG, "eventHandler: Finished scan");
if (copied_list && wifi_singleton->getRadioState() == RadioState::On && !wifi->pause_auto_connect) {
getMainDispatcher().dispatch([wifi]() { dispatchAutoConnect(wifi); });
}
}
}
static void dispatchEnable(std::shared_ptr<Wifi> wifi) {
LOG_I(TAG, "dispatchEnable()");
RadioState state = wifi->getRadioState();
if (
state == RadioState::On ||
state == RadioState::OnPending ||
state == RadioState::OffPending
) {
LOG_W(TAG, "Can't enable from current state");
return;
}
auto lock = wifi->radioMutex.asScopedLock();
if (lock.lock(50 / portTICK_PERIOD_MS)) {
LOG_I(TAG, "Enabling");
wifi->setRadioState(RadioState::OnPending);
publish_event(wifi, WifiEvent::RadioStateOnPending);
if (wifi->netif != nullptr) {
esp_wifi_clear_default_wifi_driver_and_handlers(wifi->netif);
esp_netif_destroy(wifi->netif);
}
wifi->netif = esp_netif_create_default_wifi_sta();
// Warning: this is the memory-intensive operation
// It uses over 117kB of RAM with default settings for S3 on IDF v5.1.2
wifi_init_config_t config = WIFI_INIT_CONFIG_DEFAULT();
esp_err_t init_result = esp_wifi_init(&config);
if (init_result != ESP_OK) {
LOG_E(TAG, "Wifi init failed");
if (init_result == ESP_ERR_NO_MEM) {
LOG_E(TAG, "Insufficient memory");
}
wifi->setRadioState(RadioState::Off);
publish_event(wifi, WifiEvent::RadioStateOff);
return;
}
esp_wifi_set_storage(WIFI_STORAGE_RAM);
// TODO: don't crash on check failure
ESP_ERROR_CHECK(esp_event_handler_instance_register(
WIFI_EVENT,
ESP_EVENT_ANY_ID,
&eventHandler,
nullptr,
&wifi->event_handler_any_id
));
// TODO: don't crash on check failure
ESP_ERROR_CHECK(esp_event_handler_instance_register(
IP_EVENT,
IP_EVENT_STA_GOT_IP,
&eventHandler,
nullptr,
&wifi->event_handler_got_ip
));
if (esp_wifi_set_mode(WIFI_MODE_STA) != ESP_OK) {
LOG_E(TAG, "Wifi mode setting failed");
wifi->setRadioState(RadioState::Off);
esp_wifi_deinit();
publish_event(wifi, WifiEvent::RadioStateOff);
return;
}
esp_err_t start_result = esp_wifi_start();
if (start_result != ESP_OK) {
LOG_E(TAG, "Wifi start failed");
if (start_result == ESP_ERR_NO_MEM) {
LOG_E(TAG, "Insufficient memory");
}
wifi->setRadioState(RadioState::Off);
esp_wifi_set_mode(WIFI_MODE_NULL);
esp_wifi_deinit();
publish_event(wifi, WifiEvent::RadioStateOff);
return;
}
wifi->setRadioState(RadioState::On);
publish_event(wifi, WifiEvent::RadioStateOn);
wifi->pause_auto_connect = false;
LOG_I(TAG, "Enabled");
} else {
LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED);
}
}
static void dispatchDisable(std::shared_ptr<Wifi> wifi) {
LOG_I(TAG, "dispatchDisable()");
auto lock = wifi->radioMutex.asScopedLock();
if (!lock.lock(50 / portTICK_PERIOD_MS)) {
LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "disable()");
return;
}
RadioState state = wifi->getRadioState();
if (
state == RadioState::Off ||
state == RadioState::OffPending ||
state == RadioState::OnPending
) {
LOG_W(TAG, "Can't disable from current state");
return;
}
LOG_I(TAG, "Disabling");
wifi->setRadioState(RadioState::OffPending);
publish_event(wifi, WifiEvent::RadioStateOffPending);
// Free up scan list memory
scan_list_free_safely(wifi_singleton);
// Detach netif from the internal WiFi event handlers before stopping.
// Those handlers call esp_netif_action_stop on WIFI_EVENT_STA_STOP, which
// queues esp_netif_stop_api to the lwIP thread. esp_netif_destroy later
// queues a second one; the first zeroes lwip_netif, and the second then
// crashes in netif_ip6_addr_set_parts (null pointer + 0x263 offset).
if (wifi->netif != nullptr) {
esp_wifi_clear_default_wifi_driver_and_handlers(wifi->netif);
}
// Note: handlers are already detached above, so we cannot safely return to
// RadioState::On from here — the netif would be missing its default WiFi
// event handlers and subsequent disable attempts would behave incorrectly.
// If stop fails, continue the teardown anyway so we end in a clean Off state.
if (esp_wifi_stop() != ESP_OK) {
LOG_E(TAG, "Failed to stop radio - continuing teardown");
}
if (esp_wifi_set_mode(WIFI_MODE_NULL) != ESP_OK) {
LOG_E(TAG, "Failed to unset mode");
}
if (esp_event_handler_instance_unregister(
WIFI_EVENT,
ESP_EVENT_ANY_ID,
wifi->event_handler_any_id
) != ESP_OK) {
LOG_E(TAG, "Failed to unregister id event handler");
}
if (esp_event_handler_instance_unregister(
IP_EVENT,
IP_EVENT_STA_GOT_IP,
wifi->event_handler_got_ip
) != ESP_OK) {
LOG_E(TAG, "Failed to unregister ip event handler");
}
if (esp_wifi_deinit() != ESP_OK) {
LOG_E(TAG, "Failed to deinit");
}
assert(wifi->netif != nullptr);
esp_netif_destroy(wifi->netif);
wifi->netif = nullptr;
wifi->setScanActive(false);
wifi->setRadioState(RadioState::Off);
publish_event(wifi, WifiEvent::RadioStateOff);
LOG_I(TAG, "Disabled");
}
static void dispatchScan(std::shared_ptr<Wifi> wifi) {
LOG_I(TAG, "dispatchScan()");
auto lock = wifi->radioMutex.asScopedLock();
if (!lock.lock(10 / portTICK_PERIOD_MS)) {
LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED);
return;
}
RadioState state = wifi->getRadioState();
if (state != RadioState::On && state != RadioState::ConnectionActive && state != RadioState::ConnectionPending) {
LOG_W(TAG, "Scan unavailable: wifi not enabled");
return;
}
if (wifi->isScanActive()) {
LOG_W(TAG, "Scan already pending");
return;
}
// TODO: Thread safety
wifi->last_scan_time = tt::kernel::getTicks();
if (esp_wifi_scan_start(nullptr, false) != ESP_OK) {
LOG_I(TAG, "Can't start scan");
return;
}
LOG_I(TAG, "Starting scan");
wifi->setScanActive(true);
publish_event(wifi, WifiEvent::ScanStarted);
}
static void dispatchConnect(std::shared_ptr<Wifi> wifi) {
LOG_I(TAG, "dispatchConnect()");
auto lock = wifi->radioMutex.asScopedLock();
if (!lock.lock(50 / portTICK_PERIOD_MS)) {
LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "dispatchConnect()");
return;
}
LOG_I(TAG, "Connecting to %s", wifi->connection_target.ssid.c_str());
// Stop radio first, if needed
RadioState radio_state = wifi->getRadioState();
if (
radio_state == RadioState::On ||
radio_state == RadioState::ConnectionActive ||
radio_state == RadioState::ConnectionPending
) {
LOG_I(TAG, "Connecting: Stopping radio first");
esp_err_t stop_result = esp_wifi_stop();
wifi->setScanActive(false);
if (stop_result != ESP_OK) {
LOG_E(TAG, "Connecting: Failed to disconnect (%s)", esp_err_to_name(stop_result));
return;
}
}
wifi->setScanActive(false);
wifi->setRadioState(RadioState::ConnectionPending);
publish_event(wifi, WifiEvent::ConnectionPending);
wifi_config_t config;
memset(&config, 0, sizeof(wifi_config_t));
config.sta.channel = wifi_singleton->connection_target.channel;
config.sta.scan_method = WIFI_FAST_SCAN;
config.sta.sort_method = WIFI_CONNECT_AP_BY_SIGNAL;
config.sta.threshold.rssi = -127;
config.sta.pmf_cfg.capable = true;
memcpy(config.sta.ssid, wifi_singleton->connection_target.ssid.c_str(), wifi_singleton->connection_target.ssid.size());
if (wifi_singleton->connection_target.password[0] != 0x00) {
memcpy(config.sta.password, wifi_singleton->connection_target.password.c_str(), wifi_singleton->connection_target.password.size());
config.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;
}
LOG_I(TAG, "esp_wifi_set_config()");
esp_err_t set_config_result = esp_wifi_set_config(WIFI_IF_STA, &config);
if (set_config_result != ESP_OK) {
wifi->setRadioState(RadioState::On);
LOG_E(TAG, "Failed to set wifi config (%s)", esp_err_to_name(set_config_result));
publish_event(wifi, WifiEvent::ConnectionFailed);
return;
}
LOG_I(TAG, "esp_wifi_start()");
esp_err_t wifi_start_result = esp_wifi_start();
if (wifi_start_result != ESP_OK) {
wifi->setRadioState(RadioState::On);
LOG_E(TAG, "Failed to start wifi to begin connecting (%s)", esp_err_to_name(wifi_start_result));
publish_event(wifi, WifiEvent::ConnectionFailed);
return;
}
/* Waiting until either the connection is established (WIFI_CONNECTED_BIT)
* or connection failed for the maximum number of re-tries (WIFI_FAIL_BIT).
* The bits are set by wifi_event_handler() */
uint32_t flags;
if (wifi_singleton->connection_wait_flags.wait(WIFI_FAIL_BIT | WIFI_CONNECTED_BIT, false, true, &flags, kernel::MAX_TICKS)) {
LOG_I(TAG, "Waiting for EventGroup by event_handler()");
if (flags & WIFI_CONNECTED_BIT) {
wifi->setSecureConnection(config.sta.password[0] != 0x00U);
wifi->setRadioState(RadioState::ConnectionActive);
publish_event(wifi, WifiEvent::ConnectionSuccess);
LOG_I(TAG, "Connected to %s", wifi->connection_target.ssid.c_str());
if (wifi->connection_target_remember) {
if (!settings::save(wifi->connection_target)) {
LOG_E(TAG, "Failed to store credentials");
} else {
LOG_I(TAG, "Stored credentials");
}
}
} else if (flags & WIFI_FAIL_BIT) {
wifi->setRadioState(RadioState::On);
publish_event(wifi, WifiEvent::ConnectionFailed);
LOG_I(TAG, "Failed to connect to %s", wifi->connection_target.ssid.c_str());
} else {
wifi->setRadioState(RadioState::On);
publish_event(wifi, WifiEvent::ConnectionFailed);
LOG_E(TAG, "UNEXPECTED EVENT");
}
wifi_singleton->connection_wait_flags.clear(WIFI_FAIL_BIT | WIFI_CONNECTED_BIT);
}
}
static void dispatchDisconnectButKeepActive(std::shared_ptr<Wifi> wifi) {
LOG_I(TAG, "dispatchDisconnectButKeepActive()");
auto lock = wifi->radioMutex.asScopedLock();
if (!lock.lock(50 / portTICK_PERIOD_MS)) {
LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED);
return;
}
esp_err_t stop_result = esp_wifi_stop();
if (stop_result != ESP_OK) {
LOG_E(TAG, "Failed to disconnect (%s)", esp_err_to_name(stop_result));
return;
}
wifi_config_t config;
memset(&config, 0, sizeof(wifi_config_t));
config.sta.channel = wifi_singleton->connection_target.channel;
config.sta.scan_method = WIFI_ALL_CHANNEL_SCAN;
config.sta.sort_method = WIFI_CONNECT_AP_BY_SIGNAL;
config.sta.threshold.rssi = -127;
config.sta.pmf_cfg.capable = true;
esp_err_t set_config_result = esp_wifi_set_config(WIFI_IF_STA, &config);
if (set_config_result != ESP_OK) {
// TODO: disable radio, because radio state is in limbo between off and on
wifi->setRadioState(RadioState::Off);
LOG_E(TAG, "failed to set wifi config (%s)", esp_err_to_name(set_config_result));
publish_event(wifi, WifiEvent::RadioStateOff);
return;
}
esp_err_t wifi_start_result = esp_wifi_start();
if (wifi_start_result != ESP_OK) {
// TODO: disable radio, because radio state is in limbo between off and on
wifi->setRadioState(RadioState::Off);
LOG_E(TAG, "failed to start wifi to begin connecting (%s)", esp_err_to_name(wifi_start_result));
publish_event(wifi, WifiEvent::RadioStateOff);
return;
}
wifi->setRadioState(RadioState::On);
publish_event(wifi, WifiEvent::Disconnected);
LOG_I(TAG, "Disconnected");
}
static bool shouldScanForAutoConnect(std::shared_ptr<Wifi> wifi) {
auto lock = wifi->dataMutex.asScopedLock();
if (!lock.lock(100 / portTICK_PERIOD_MS)) {
return false;
}
bool is_radio_in_scannable_state = wifi->getRadioState() == RadioState::On &&
!wifi->isScanActive() &&
!wifi->pause_auto_connect;
if (!is_radio_in_scannable_state) {
return false;
}
TickType_t current_time = kernel::getTicks();
bool scan_time_has_looped = (current_time < wifi->last_scan_time);
bool no_recent_scan = (current_time - wifi->last_scan_time) > (AUTO_SCAN_INTERVAL / portTICK_PERIOD_MS);
if (!scan_time_has_looped && !no_recent_scan) {
}
return scan_time_has_looped || no_recent_scan;
}
void onAutoConnectTimer() {
auto wifi = std::static_pointer_cast<Wifi>(wifi_singleton);
// Automatic scanning is done so we can automatically connect to access points
bool should_auto_scan = shouldScanForAutoConnect(wifi);
if (should_auto_scan) {
getMainDispatcher().dispatch([wifi]() { dispatchScan(wifi); });
}
}
std::string getIp() {
auto wifi = std::static_pointer_cast<Wifi>(wifi_singleton);
if (wifi == nullptr) return "127.0.0.1";
auto lock = wifi->dataMutex.asScopedLock();
if (!lock.lock(100 / portTICK_PERIOD_MS)) {
return "127.0.0.1";
}
return std::format("{}.{}.{}.{}", IP2STR(&wifi->ip_info.ip));
}
class WifiService final : public Service {
public:
bool onStart(ServiceContext& service) override {
assert(wifi_singleton == nullptr);
wifi_singleton = std::make_shared<Wifi>();
wifi_singleton->bootEventSubscription = kernel::subscribeSystemEvent(kernel::SystemEvent::BootSplash, [](auto) {
bootSplashInit();
});
auto timer_interval = std::min(2000, AUTO_SCAN_INTERVAL);
wifi_singleton->autoConnectTimer = std::make_unique<Timer>(Timer::Type::Periodic, timer_interval, [] { onAutoConnectTimer(); });
// We want to try and scan more often in case of startup or scan lock failure
wifi_singleton->autoConnectTimer->start();
return true;
}
void onStop(ServiceContext& service) override {
auto wifi = wifi_singleton;
assert(wifi != nullptr);
RadioState state = wifi->getRadioState();
if (state != RadioState::Off) {
dispatchDisable(wifi);
}
wifi->autoConnectTimer->stop();
wifi->autoConnectTimer = nullptr; // Must release as it holds a reference to this Wifi instance
// Acquire all mutexes
wifi->dataMutex.lock();
wifi->radioMutex.lock();
// Detach
wifi_singleton = nullptr;
// Release mutexes
wifi->dataMutex.unlock();
wifi->radioMutex.unlock();
// Release (hopefully) last Wifi instance by scope
}
};
extern const ServiceManifest manifest = {
.id = "wifi",
.createService = create<WifiService>
};
} // namespace
#endif // CONFIG_SOC_WIFI_SUPPORTED or CONFIG_SLAVE_SOC_WIFI_SUPPORTED
-168
View File
@@ -1,168 +0,0 @@
#ifdef ESP_PLATFORM
#include <sdkconfig.h>
#endif
#if not defined(CONFIG_SOC_WIFI_SUPPORTED) && not defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
#include <Tactility/service/wifi/Wifi.h>
#include <Tactility/PubSub.h>
#include <Tactility/RecursiveMutex.h>
#include <tactility/check.h>
#include <Tactility/service/Service.h>
#include <Tactility/service/ServiceManifest.h>
namespace tt::service::wifi {
struct Wifi {
/** @brief Locking mechanism for modifying the Wifi instance */
RecursiveMutex mutex;
/** @brief The public event bus */
std::shared_ptr<PubSub<WifiEvent>> pubsub = std::make_shared<PubSub<WifiEvent>>();
/** @brief The internal message queue */
bool scan_active = false;
bool secure_connection = false;
RadioState radio_state = RadioState::ConnectionActive;
};
static Wifi* wifi = nullptr;
// region Static
static void publish_event(WifiEvent event) {
wifi->pubsub->publish(event);
}
// endregion Static
// region Public functions
std::shared_ptr<PubSub<WifiEvent>> getPubsub() {
assert(wifi);
return wifi->pubsub;
}
RadioState getRadioState() {
return wifi->radio_state;
}
std::string getConnectionTarget() {
return "Home Wifi";
}
void scan() {
assert(wifi);
wifi->scan_active = false; // TODO: enable and then later disable automatically
}
bool isScanning() {
assert(wifi);
return wifi->scan_active;
}
void connect(const settings::WifiApSettings& ap, bool remember) {
assert(wifi);
// TODO: implement
}
void disconnect() {
assert(wifi);
}
void setScanRecords(uint16_t records) {
assert(wifi);
// TODO: implement
}
std::vector<ApRecord> getScanResults() {
check(wifi);
std::vector<ApRecord> records;
records.push_back((ApRecord) {
.ssid = "Home Wifi",
.rssi = -30,
.channel = 0,
.auth_mode = WIFI_AUTH_WPA2_PSK
});
records.push_back((ApRecord) {
.ssid = "No place like 127.0.0.1",
.rssi = -67,
.channel = 0,
.auth_mode = WIFI_AUTH_WPA2_PSK
});
records.push_back((ApRecord) {
.ssid = "Pretty fly for a Wi-Fi",
.rssi = -70,
.channel = 0,
.auth_mode = WIFI_AUTH_WPA2_PSK
});
records.push_back((ApRecord) {
.ssid = "An AP with a really, really long name",
.rssi = -80,
.channel = 0,
.auth_mode = WIFI_AUTH_WPA2_PSK
});
records.push_back((ApRecord) {
.ssid = "Bad Reception",
.rssi = -90,
.channel = 0,
.auth_mode = WIFI_AUTH_OPEN
});
return records;
}
void setEnabled(bool enabled) {
assert(wifi != nullptr);
if (enabled) {
wifi->radio_state = RadioState::On;
wifi->secure_connection = true;
} else {
wifi->radio_state = RadioState::Off;
}
}
bool isConnectionSecure() {
return wifi->secure_connection;
}
int getRssi() {
if (wifi->radio_state == RadioState::ConnectionActive) {
return -30;
} else {
return 0;
}
}
std::string getIp() {
return "192.168.1.2";
}
// endregion Public functions
class WifiService final : public Service {
public:
bool onStart(ServiceContext& service) override {
check(wifi == nullptr);
wifi = new Wifi();
return true;
}
void onStop(ServiceContext& service) override {
check(wifi != nullptr);
delete wifi;
wifi = nullptr;
}
};
extern const ServiceManifest manifest = {
.id = "wifi",
.createService = create<WifiService>
};
} // namespace
#endif // ESP_PLATFORM
+1 -1
View File
@@ -1,6 +1,6 @@
#include <Tactility/settings/Time.h>
#include <Tactility/kernel/SystemEvents.h>
#include <Tactility/SystemEvents.h>
#include <Tactility/Preferences.h>
#include <Tactility/settings/SystemSettings.h>