Added WiFi kernel drivers and refactored Wifi service (#557)
+ other improvements
This commit is contained in:
committed by
GitHub
parent
dbb96a891c
commit
1c2806bddf
@@ -6,7 +6,7 @@ idf_component_register(
|
||||
SRCS ${SOURCES}
|
||||
INCLUDE_DIRS "include/"
|
||||
PRIV_INCLUDE_DIRS "private/"
|
||||
REQUIRES TactilityKernel driver esp_driver_i2c vfs fatfs
|
||||
REQUIRES TactilityKernel driver esp_driver_i2c vfs fatfs esp_wifi esp_netif esp_event
|
||||
)
|
||||
|
||||
idf_component_optional_requires(PRIVATE bt usb espressif__usb_host_hid espressif__usb_host_msc)
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
description: ESP32 WiFi driver wrapper that marshals state-changing calls onto the WiFi task's CPU core
|
||||
|
||||
compatible: "espressif,esp32-wifi-pinned"
|
||||
|
||||
properties:
|
||||
_unused:
|
||||
type: int
|
||||
default: 0
|
||||
@@ -0,0 +1,8 @@
|
||||
description: ESP32 WiFi driver
|
||||
|
||||
compatible: "espressif,esp32-wifi"
|
||||
|
||||
properties:
|
||||
_unused:
|
||||
type: int
|
||||
default: 0
|
||||
@@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <tactility/bindings/bindings.h>
|
||||
#include <tactility/drivers/esp32_wifi.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
DEFINE_DEVICETREE(esp32_wifi, struct Esp32WifiConfig)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <tactility/bindings/bindings.h>
|
||||
#include <tactility/drivers/esp32_wifi_pinned.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
DEFINE_DEVICETREE(esp32_wifi_pinned, struct Esp32WifiPinnedConfig)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** No device-tree configuration required for the ESP32 WiFi driver. */
|
||||
struct Esp32WifiConfig {
|
||||
int _unused; /**< Placeholder — driver reads all config from Kconfig. */
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** No device-tree configuration required for the ESP32 pinned WiFi driver. */
|
||||
struct Esp32WifiPinnedConfig {
|
||||
int _unused; /**< Placeholder — driver has no configurable properties. */
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,567 @@
|
||||
#ifdef ESP_PLATFORM
|
||||
#include <sdkconfig.h>
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_SOC_WIFI_SUPPORTED) || defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
|
||||
|
||||
#include <esp_event.h>
|
||||
#include <esp_netif.h>
|
||||
#include <esp_wifi.h>
|
||||
#include <esp_wifi_default.h>
|
||||
|
||||
#include <tactility/concurrent/mutex.h>
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/drivers/esp32_wifi.h>
|
||||
#include <tactility/drivers/wifi.h>
|
||||
#include <tactility/error_esp32.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
|
||||
#define TAG "esp32_wifi"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr size_t WIFI_MAX_CALLBACKS = 4;
|
||||
constexpr uint16_t WIFI_SCAN_RECORD_LIMIT = 32;
|
||||
|
||||
struct WifiCallbackEntry {
|
||||
WifiEventCallback fn = nullptr;
|
||||
void* ctx = nullptr;
|
||||
};
|
||||
|
||||
struct Esp32WifiCtx {
|
||||
Device* device = nullptr;
|
||||
|
||||
Mutex mutex{};
|
||||
WifiRadioState radioState = WIFI_RADIO_STATE_OFF;
|
||||
WifiStationState stationState = WIFI_STATION_STATE_DISCONNECTED;
|
||||
bool scanning = false;
|
||||
char targetSsid[33] = {};
|
||||
esp_netif_ip_info_t ipInfo{};
|
||||
wifi_ap_record_t scanResults[WIFI_SCAN_RECORD_LIMIT] = {};
|
||||
uint16_t scanResultCount = 0;
|
||||
|
||||
esp_netif_t* netif = nullptr;
|
||||
esp_event_handler_instance_t wifiEventHandler = nullptr;
|
||||
esp_event_handler_instance_t ipEventHandler = nullptr;
|
||||
|
||||
Mutex callbackMutex{};
|
||||
WifiCallbackEntry callbacks[WIFI_MAX_CALLBACKS] = {};
|
||||
size_t callbackCount = 0;
|
||||
};
|
||||
|
||||
#define GET_CTX(device) (static_cast<Esp32WifiCtx*>(device_get_driver_data(device)))
|
||||
|
||||
WifiAuthenticationType to_wifi_authentication_type(wifi_auth_mode_t mode) {
|
||||
switch (mode) {
|
||||
case WIFI_AUTH_OPEN: return WIFI_AUTHENTICATION_TYPE_OPEN;
|
||||
case WIFI_AUTH_WEP: return WIFI_AUTHENTICATION_TYPE_WEP;
|
||||
case WIFI_AUTH_WPA_PSK: return WIFI_AUTHENTICATION_TYPE_WPA_PSK;
|
||||
case WIFI_AUTH_WPA2_PSK: return WIFI_AUTHENTICATION_TYPE_WPA2_PSK;
|
||||
case WIFI_AUTH_WPA_WPA2_PSK: return WIFI_AUTHENTICATION_TYPE_WPA_WPA2_PSK;
|
||||
case WIFI_AUTH_WPA2_ENTERPRISE: return WIFI_AUTHENTICATION_TYPE_WPA2_ENTERPRISE;
|
||||
case WIFI_AUTH_WPA3_PSK: return WIFI_AUTHENTICATION_TYPE_WPA3_PSK;
|
||||
case WIFI_AUTH_WPA2_WPA3_PSK: return WIFI_AUTHENTICATION_TYPE_WPA2_WPA3_PSK;
|
||||
case WIFI_AUTH_WAPI_PSK: return WIFI_AUTHENTICATION_TYPE_WAPI_PSK;
|
||||
case WIFI_AUTH_OWE: return WIFI_AUTHENTICATION_TYPE_OWE;
|
||||
case WIFI_AUTH_WPA3_ENT_192: return WIFI_AUTHENTICATION_TYPE_WPA3_ENT_192;
|
||||
case WIFI_AUTH_WPA3_EXT_PSK: return WIFI_AUTHENTICATION_TYPE_WPA3_EXT_PSK;
|
||||
case WIFI_AUTH_WPA3_EXT_PSK_MIXED_MODE: return WIFI_AUTHENTICATION_TYPE_WPA3_EXT_PSK_MIXED_MODE;
|
||||
default: return WIFI_AUTHENTICATION_TYPE_OPEN;
|
||||
}
|
||||
}
|
||||
|
||||
void fire_event(Esp32WifiCtx* 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);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- ESP-IDF event handling (runs on the esp_event task) ----
|
||||
|
||||
void on_wifi_or_ip_event(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
|
||||
auto* ctx = static_cast<Esp32WifiCtx*>(arg);
|
||||
|
||||
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
|
||||
mutex_lock(&ctx->mutex);
|
||||
bool was_pending = ctx->stationState == WIFI_STATION_STATE_CONNECTION_PENDING;
|
||||
ctx->stationState = WIFI_STATION_STATE_DISCONNECTED;
|
||||
memset(&ctx->ipInfo, 0, sizeof(ctx->ipInfo));
|
||||
mutex_unlock(&ctx->mutex);
|
||||
|
||||
WifiEvent state_event = {};
|
||||
state_event.type = WIFI_EVENT_TYPE_STATION_STATE_CHANGED;
|
||||
state_event.station_state = WIFI_STATION_STATE_DISCONNECTED;
|
||||
fire_event(ctx, state_event);
|
||||
|
||||
if (was_pending) {
|
||||
WifiEvent result_event = {};
|
||||
result_event.type = WIFI_EVENT_TYPE_STATION_CONNECTION_RESULT;
|
||||
result_event.connection_error = WIFI_STATION_CONNECTION_ERROR_TARGET_NOT_FOUND;
|
||||
fire_event(ctx, result_event);
|
||||
}
|
||||
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
|
||||
auto* got_ip = static_cast<ip_event_got_ip_t*>(event_data);
|
||||
|
||||
mutex_lock(&ctx->mutex);
|
||||
ctx->ipInfo = got_ip->ip_info;
|
||||
ctx->stationState = WIFI_STATION_STATE_CONNECTED;
|
||||
mutex_unlock(&ctx->mutex);
|
||||
|
||||
WifiEvent state_event = {};
|
||||
state_event.type = WIFI_EVENT_TYPE_STATION_STATE_CHANGED;
|
||||
state_event.station_state = WIFI_STATION_STATE_CONNECTED;
|
||||
fire_event(ctx, state_event);
|
||||
|
||||
WifiEvent result_event = {};
|
||||
result_event.type = WIFI_EVENT_TYPE_STATION_CONNECTION_RESULT;
|
||||
result_event.connection_error = WIFI_STATION_CONNECTION_ERROR_NONE;
|
||||
fire_event(ctx, result_event);
|
||||
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_SCAN_DONE) {
|
||||
mutex_lock(&ctx->mutex);
|
||||
ctx->scanning = false;
|
||||
uint16_t count = WIFI_SCAN_RECORD_LIMIT;
|
||||
esp_err_t err = esp_wifi_scan_get_ap_records(&count, ctx->scanResults);
|
||||
ctx->scanResultCount = (err == ESP_OK) ? count : 0;
|
||||
mutex_unlock(&ctx->mutex);
|
||||
|
||||
WifiEvent event = {};
|
||||
event.type = WIFI_EVENT_TYPE_SCAN_FINISHED;
|
||||
fire_event(ctx, event);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Work, always run inline on the caller (see note at the top) ----
|
||||
|
||||
error_t bring_up_wifi(Esp32WifiCtx* ctx) {
|
||||
ctx->netif = esp_netif_create_default_wifi_sta();
|
||||
if (ctx->netif == nullptr) {
|
||||
LOG_E(TAG, "Failed to create default STA netif");
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
// Warning: this is the memory-intensive operation. It uses over 100kB of
|
||||
// RAM with default settings.
|
||||
wifi_init_config_t init_config = WIFI_INIT_CONFIG_DEFAULT();
|
||||
esp_err_t err = esp_wifi_init(&init_config);
|
||||
if (err != ESP_OK) {
|
||||
LOG_E(TAG, "esp_wifi_init failed: %s", esp_err_to_name(err));
|
||||
esp_netif_destroy(ctx->netif);
|
||||
ctx->netif = nullptr;
|
||||
return esp_err_to_error(err);
|
||||
}
|
||||
|
||||
esp_wifi_set_storage(WIFI_STORAGE_RAM);
|
||||
|
||||
err = esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &on_wifi_or_ip_event, ctx, &ctx->wifiEventHandler);
|
||||
if (err != ESP_OK) {
|
||||
LOG_E(TAG, "Failed to register WIFI_EVENT handler: %s", esp_err_to_name(err));
|
||||
esp_wifi_deinit();
|
||||
esp_netif_destroy(ctx->netif);
|
||||
ctx->netif = nullptr;
|
||||
return esp_err_to_error(err);
|
||||
}
|
||||
|
||||
err = esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &on_wifi_or_ip_event, ctx, &ctx->ipEventHandler);
|
||||
if (err != ESP_OK) {
|
||||
LOG_E(TAG, "Failed to register IP_EVENT handler: %s", esp_err_to_name(err));
|
||||
esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, ctx->wifiEventHandler);
|
||||
ctx->wifiEventHandler = nullptr;
|
||||
esp_wifi_deinit();
|
||||
esp_netif_destroy(ctx->netif);
|
||||
ctx->netif = nullptr;
|
||||
return esp_err_to_error(err);
|
||||
}
|
||||
|
||||
err = esp_wifi_set_mode(WIFI_MODE_STA);
|
||||
if (err != ESP_OK) {
|
||||
LOG_E(TAG, "esp_wifi_set_mode failed: %s", esp_err_to_name(err));
|
||||
esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, ctx->ipEventHandler);
|
||||
esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, ctx->wifiEventHandler);
|
||||
ctx->ipEventHandler = nullptr;
|
||||
ctx->wifiEventHandler = nullptr;
|
||||
esp_wifi_deinit();
|
||||
esp_netif_destroy(ctx->netif);
|
||||
ctx->netif = nullptr;
|
||||
return esp_err_to_error(err);
|
||||
}
|
||||
|
||||
err = esp_wifi_start();
|
||||
if (err != ESP_OK) {
|
||||
LOG_E(TAG, "esp_wifi_start failed: %s", esp_err_to_name(err));
|
||||
esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, ctx->ipEventHandler);
|
||||
esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, ctx->wifiEventHandler);
|
||||
ctx->ipEventHandler = nullptr;
|
||||
ctx->wifiEventHandler = nullptr;
|
||||
esp_wifi_set_mode(WIFI_MODE_NULL);
|
||||
esp_wifi_deinit();
|
||||
esp_netif_destroy(ctx->netif);
|
||||
ctx->netif = nullptr;
|
||||
return esp_err_to_error(err);
|
||||
}
|
||||
|
||||
mutex_lock(&ctx->mutex);
|
||||
ctx->radioState = WIFI_RADIO_STATE_ON;
|
||||
mutex_unlock(&ctx->mutex);
|
||||
|
||||
LOG_I(TAG, "WiFi radio on");
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
void bring_down_wifi(Esp32WifiCtx* ctx) {
|
||||
mutex_lock(&ctx->mutex);
|
||||
bool was_connected = ctx->stationState != WIFI_STATION_STATE_DISCONNECTED;
|
||||
bool was_scanning = ctx->scanning;
|
||||
ctx->stationState = WIFI_STATION_STATE_DISCONNECTED;
|
||||
ctx->scanning = false;
|
||||
mutex_unlock(&ctx->mutex);
|
||||
|
||||
if (was_scanning) {
|
||||
esp_wifi_scan_stop();
|
||||
}
|
||||
if (was_connected) {
|
||||
esp_wifi_disconnect();
|
||||
}
|
||||
|
||||
// Detach netif from the internal WiFi event handlers before stopping,
|
||||
// otherwise esp_netif_destroy() can race with esp_wifi_stop()'s own
|
||||
// netif teardown (see esp32_ble/WifiEsp.cpp for the same issue).
|
||||
if (ctx->netif != nullptr) {
|
||||
esp_wifi_clear_default_wifi_driver_and_handlers(ctx->netif);
|
||||
}
|
||||
|
||||
esp_wifi_stop();
|
||||
esp_wifi_set_mode(WIFI_MODE_NULL);
|
||||
|
||||
if (ctx->wifiEventHandler != nullptr) {
|
||||
esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, ctx->wifiEventHandler);
|
||||
ctx->wifiEventHandler = nullptr;
|
||||
}
|
||||
if (ctx->ipEventHandler != nullptr) {
|
||||
esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, ctx->ipEventHandler);
|
||||
ctx->ipEventHandler = nullptr;
|
||||
}
|
||||
|
||||
esp_wifi_deinit();
|
||||
|
||||
if (ctx->netif != nullptr) {
|
||||
esp_netif_destroy(ctx->netif);
|
||||
ctx->netif = nullptr;
|
||||
}
|
||||
|
||||
mutex_lock(&ctx->mutex);
|
||||
ctx->radioState = WIFI_RADIO_STATE_OFF;
|
||||
mutex_unlock(&ctx->mutex);
|
||||
|
||||
LOG_I(TAG, "WiFi radio off");
|
||||
}
|
||||
|
||||
// ---- WifiApi ----
|
||||
|
||||
error_t api_get_radio_state(Device* device, WifiRadioState* state) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr || state == nullptr) return ERROR_INVALID_ARGUMENT;
|
||||
mutex_lock(&ctx->mutex);
|
||||
*state = ctx->radioState;
|
||||
mutex_unlock(&ctx->mutex);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
error_t api_get_station_state(Device* device, WifiStationState* state) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr || state == nullptr) return ERROR_INVALID_ARGUMENT;
|
||||
mutex_lock(&ctx->mutex);
|
||||
*state = ctx->stationState;
|
||||
mutex_unlock(&ctx->mutex);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
error_t api_get_access_point_state(Device* device, WifiAccessPointState* state) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr || state == nullptr) return ERROR_INVALID_ARGUMENT;
|
||||
// Access point mode isn't implemented by this driver.
|
||||
*state = WIFI_ACCESS_POINT_STATE_STOPPED;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
bool api_is_scanning(Device* device) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr) return false;
|
||||
mutex_lock(&ctx->mutex);
|
||||
bool scanning = ctx->scanning;
|
||||
mutex_unlock(&ctx->mutex);
|
||||
return scanning;
|
||||
}
|
||||
|
||||
error_t api_scan(Device* device) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr) return ERROR_INVALID_STATE;
|
||||
|
||||
mutex_lock(&ctx->mutex);
|
||||
if (ctx->radioState != WIFI_RADIO_STATE_ON || ctx->scanning) {
|
||||
mutex_unlock(&ctx->mutex);
|
||||
return ERROR_INVALID_STATE;
|
||||
}
|
||||
mutex_unlock(&ctx->mutex);
|
||||
|
||||
esp_err_t err = esp_wifi_scan_start(nullptr, false);
|
||||
if (err != ESP_OK) {
|
||||
return esp_err_to_error(err);
|
||||
}
|
||||
|
||||
mutex_lock(&ctx->mutex);
|
||||
ctx->scanning = true;
|
||||
mutex_unlock(&ctx->mutex);
|
||||
|
||||
WifiEvent event = {};
|
||||
event.type = WIFI_EVENT_TYPE_SCAN_STARTED;
|
||||
fire_event(ctx, event);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
error_t api_get_scan_results(Device* device, WifiApRecord* results, size_t* num_results) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr || results == nullptr || num_results == nullptr) return ERROR_INVALID_ARGUMENT;
|
||||
|
||||
mutex_lock(&ctx->mutex);
|
||||
size_t count = std::min<size_t>(*num_results, ctx->scanResultCount);
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
const wifi_ap_record_t& src = ctx->scanResults[i];
|
||||
WifiApRecord& dst = results[i];
|
||||
memset(dst.ssid, 0, sizeof(dst.ssid));
|
||||
memcpy(dst.ssid, src.ssid, std::min(sizeof(dst.ssid) - 1, sizeof(src.ssid)));
|
||||
dst.rssi = src.rssi;
|
||||
dst.channel = src.primary;
|
||||
dst.authentication_type = to_wifi_authentication_type(src.authmode);
|
||||
}
|
||||
*num_results = count;
|
||||
mutex_unlock(&ctx->mutex);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
error_t api_station_get_ipv4_address(Device* device, char* ipv4) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr || ipv4 == nullptr) return ERROR_INVALID_ARGUMENT;
|
||||
mutex_lock(&ctx->mutex);
|
||||
esp_ip4addr_ntoa(&ctx->ipInfo.ip, ipv4, 16);
|
||||
mutex_unlock(&ctx->mutex);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
error_t api_station_get_target_ssid(Device* device, char* ssid) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr || ssid == nullptr) return ERROR_INVALID_ARGUMENT;
|
||||
mutex_lock(&ctx->mutex);
|
||||
constexpr size_t ssid_buffer_size = sizeof(ctx->targetSsid);
|
||||
strncpy(ssid, ctx->targetSsid, ssid_buffer_size);
|
||||
mutex_unlock(&ctx->mutex);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
error_t api_station_connect(Device* device, const char* ssid, const char* password, int32_t channel) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr || ssid == nullptr) return ERROR_INVALID_ARGUMENT;
|
||||
|
||||
mutex_lock(&ctx->mutex);
|
||||
bool radio_on = ctx->radioState == WIFI_RADIO_STATE_ON;
|
||||
bool was_connected = ctx->stationState != WIFI_STATION_STATE_DISCONNECTED;
|
||||
mutex_unlock(&ctx->mutex);
|
||||
|
||||
if (!radio_on) {
|
||||
return ERROR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (was_connected) {
|
||||
esp_wifi_disconnect();
|
||||
}
|
||||
|
||||
wifi_config_t config {};
|
||||
config.sta.channel = static_cast<uint8_t>(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;
|
||||
strncpy(reinterpret_cast<char*>(config.sta.ssid), ssid, sizeof(config.sta.ssid) - 1);
|
||||
if (password != nullptr && password[0] != '\0') {
|
||||
strncpy(reinterpret_cast<char*>(config.sta.password), password, sizeof(config.sta.password) - 1);
|
||||
config.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;
|
||||
}
|
||||
|
||||
esp_err_t err = esp_wifi_set_config(WIFI_IF_STA, &config);
|
||||
if (err != ESP_OK) {
|
||||
return esp_err_to_error(err);
|
||||
}
|
||||
|
||||
mutex_lock(&ctx->mutex);
|
||||
strncpy(ctx->targetSsid, ssid, sizeof(ctx->targetSsid) - 1);
|
||||
ctx->targetSsid[sizeof(ctx->targetSsid) - 1] = '\0';
|
||||
ctx->stationState = WIFI_STATION_STATE_CONNECTION_PENDING;
|
||||
mutex_unlock(&ctx->mutex);
|
||||
|
||||
err = esp_wifi_connect();
|
||||
if (err != ESP_OK) {
|
||||
mutex_lock(&ctx->mutex);
|
||||
ctx->stationState = WIFI_STATION_STATE_DISCONNECTED;
|
||||
mutex_unlock(&ctx->mutex);
|
||||
return esp_err_to_error(err);
|
||||
}
|
||||
|
||||
WifiEvent event = {};
|
||||
event.type = WIFI_EVENT_TYPE_STATION_STATE_CHANGED;
|
||||
event.station_state = WIFI_STATION_STATE_CONNECTION_PENDING;
|
||||
fire_event(ctx, event);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
error_t api_station_disconnect(Device* device) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr) return ERROR_INVALID_STATE;
|
||||
|
||||
mutex_lock(&ctx->mutex);
|
||||
bool was_connected = ctx->stationState != WIFI_STATION_STATE_DISCONNECTED;
|
||||
mutex_unlock(&ctx->mutex);
|
||||
|
||||
if (!was_connected) {
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
// The DISCONNECTED state change is published by the WIFI_EVENT handler
|
||||
// once ESP-IDF confirms the disconnect, so we don't fire it here.
|
||||
esp_err_t err = esp_wifi_disconnect();
|
||||
return err == ESP_OK ? ERROR_NONE : esp_err_to_error(err);
|
||||
}
|
||||
|
||||
error_t api_station_get_rssi(Device* device, int32_t* rssi) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr || rssi == nullptr) return ERROR_INVALID_ARGUMENT;
|
||||
|
||||
int native_rssi = 0;
|
||||
esp_err_t err = esp_wifi_sta_get_rssi(&native_rssi);
|
||||
if (err != ESP_OK) {
|
||||
return esp_err_to_error(err);
|
||||
}
|
||||
*rssi = native_rssi;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
error_t api_add_event_callback(Device* device, void* callback_context, WifiEventCallback callback) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr || callback == nullptr) return ERROR_INVALID_ARGUMENT;
|
||||
|
||||
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);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
error_t api_remove_event_callback(Device* device, WifiEventCallback callback) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr || callback == nullptr) return ERROR_INVALID_ARGUMENT;
|
||||
|
||||
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_unlock(&ctx->callbackMutex);
|
||||
return ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
const WifiApi esp32_wifi_api = {
|
||||
.get_radio_state = api_get_radio_state,
|
||||
.get_station_state = api_get_station_state,
|
||||
.get_access_point_state = api_get_access_point_state,
|
||||
.is_scanning = api_is_scanning,
|
||||
.scan = api_scan,
|
||||
.get_scan_results = api_get_scan_results,
|
||||
.station_get_ipv4_address = api_station_get_ipv4_address,
|
||||
.station_get_target_ssid = api_station_get_target_ssid,
|
||||
.station_connect = api_station_connect,
|
||||
.station_disconnect = api_station_disconnect,
|
||||
.station_get_rssi = api_station_get_rssi,
|
||||
.add_event_callback = api_add_event_callback,
|
||||
.remove_event_callback = api_remove_event_callback
|
||||
};
|
||||
|
||||
// ---- 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) {
|
||||
auto* ctx = new(std::nothrow) Esp32WifiCtx();
|
||||
if (ctx == nullptr) return ERROR_OUT_OF_MEMORY;
|
||||
|
||||
ctx->device = device;
|
||||
mutex_construct(&ctx->mutex);
|
||||
mutex_construct(&ctx->callbackMutex);
|
||||
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;
|
||||
}
|
||||
|
||||
error_t stop_device(Device* device) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr) return ERROR_NONE;
|
||||
|
||||
bring_down_wifi(ctx);
|
||||
|
||||
device_set_driver_data(device, nullptr);
|
||||
mutex_destruct(&ctx->callbackMutex);
|
||||
mutex_destruct(&ctx->mutex);
|
||||
delete ctx;
|
||||
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
|
||||
extern Module platform_esp32_module;
|
||||
|
||||
Driver esp32_wifi_driver = {
|
||||
.name = "esp32_wifi",
|
||||
.compatible = (const char*[]) { "espressif,esp32-wifi", nullptr },
|
||||
.start_device = start_device,
|
||||
.stop_device = stop_device,
|
||||
.api = (const void*)&esp32_wifi_api,
|
||||
.device_type = &WIFI_TYPE,
|
||||
.owner = &platform_esp32_module,
|
||||
.internal = nullptr
|
||||
};
|
||||
|
||||
} // extern "C"
|
||||
|
||||
#endif // CONFIG_SOC_WIFI_SUPPORTED or CONFIG_SLAVE_SOC_WIFI_SUPPORTED
|
||||
@@ -0,0 +1,336 @@
|
||||
#ifdef ESP_PLATFORM
|
||||
#include <sdkconfig.h>
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_SOC_WIFI_SUPPORTED) || defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
|
||||
|
||||
#include <esp_wifi.h> // for WIFI_TASK_CORE_ID
|
||||
|
||||
#include <tactility/concurrent/dispatcher.h>
|
||||
#include <tactility/concurrent/event_group.h>
|
||||
#include <tactility/concurrent/thread.h>
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/drivers/esp32_wifi_pinned.h>
|
||||
#include <tactility/drivers/wifi.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <new>
|
||||
|
||||
#define TAG "esp32_wifi_pinned"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint32_t CALL_DONE_FLAG = 1U;
|
||||
constexpr configSTACK_DEPTH_TYPE PINNED_THREAD_STACK_SIZE = 4096;
|
||||
|
||||
// This driver wraps "espressif,esp32-wifi" (created as a child device) and
|
||||
// marshals every call that mutates WiFi state onto a dedicated task pinned to
|
||||
// WIFI_TASK_CORE_ID. ESP-IDF's WiFi driver misbehaves (hangs waiting on its
|
||||
// internal task semaphore, tripping the watchdog) when those calls originate
|
||||
// from an arbitrary caller task/core, so this driver guarantees the correct
|
||||
// core regardless of who calls the WifiApi.
|
||||
struct Esp32WifiPinnedCtx {
|
||||
Device* device = nullptr;
|
||||
Device* child = nullptr;
|
||||
Thread* thread = nullptr;
|
||||
DispatcherHandle_t dispatcher = nullptr;
|
||||
volatile bool running = false;
|
||||
};
|
||||
|
||||
#define GET_CTX(device) (static_cast<Esp32WifiPinnedCtx*>(device_get_driver_data(device)))
|
||||
|
||||
struct MarshalledCall {
|
||||
const std::function<void()>* work;
|
||||
EventGroupHandle_t done;
|
||||
};
|
||||
|
||||
void marshal_trampoline(void* context) {
|
||||
auto* call = static_cast<MarshalledCall*>(context);
|
||||
(*call->work)();
|
||||
event_group_set(call->done, CALL_DONE_FLAG);
|
||||
}
|
||||
|
||||
/** Runs `work` synchronously on the pinned WiFi task, blocking the caller until it completes. */
|
||||
error_t run_on_pinned_thread(Esp32WifiPinnedCtx* ctx, const std::function<void()>& work) {
|
||||
if (ctx == nullptr || ctx->thread == nullptr) {
|
||||
return ERROR_INVALID_STATE;
|
||||
}
|
||||
|
||||
// Avoid deadlocking when already running on the pinned thread (e.g. during device_start()).
|
||||
if (thread_get_current() == ctx->thread) {
|
||||
work();
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
MarshalledCall call = { .work = &work, .done = nullptr };
|
||||
event_group_construct(&call.done);
|
||||
|
||||
error_t err = dispatcher_dispatch(ctx->dispatcher, &call, marshal_trampoline);
|
||||
if (err != ERROR_NONE) {
|
||||
event_group_destruct(&call.done);
|
||||
return err;
|
||||
}
|
||||
|
||||
event_group_wait(call.done, CALL_DONE_FLAG, false, true, nullptr, portMAX_DELAY);
|
||||
event_group_destruct(&call.done);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
void request_stop(void* context) {
|
||||
static_cast<Esp32WifiPinnedCtx*>(context)->running = false;
|
||||
}
|
||||
|
||||
int32_t pinned_thread_main(void* context) {
|
||||
auto* ctx = static_cast<Esp32WifiPinnedCtx*>(context);
|
||||
while (ctx->running) {
|
||||
dispatcher_consume(ctx->dispatcher);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- Work that must run on the pinned thread (see start_device/stop_device) ----
|
||||
|
||||
error_t start_child_work(Esp32WifiPinnedCtx* ctx) {
|
||||
ctx->child = new(std::nothrow) Device();
|
||||
if (ctx->child == nullptr) {
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
std::memset(ctx->child, 0, sizeof(Device));
|
||||
ctx->child->parent = ctx->device;
|
||||
ctx->child->name = ctx->device->name;
|
||||
|
||||
error_t err = device_construct_add_start(ctx->child, "espressif,esp32-wifi");
|
||||
if (err != ERROR_NONE) {
|
||||
LOG_E(TAG, "%s: failed to start child wifi device: %s", ctx->device->name, error_to_string(err));
|
||||
delete ctx->child;
|
||||
ctx->child = nullptr;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
void stop_child_work(Esp32WifiPinnedCtx* ctx) {
|
||||
if (ctx->child == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (device_is_ready(ctx->child)) {
|
||||
device_stop(ctx->child);
|
||||
}
|
||||
if (device_is_added(ctx->child)) {
|
||||
device_remove(ctx->child);
|
||||
}
|
||||
device_destruct(ctx->child);
|
||||
delete ctx->child;
|
||||
ctx->child = nullptr;
|
||||
}
|
||||
|
||||
// ---- WifiApi: read-only calls forward straight to the child, no core affinity needed ----
|
||||
|
||||
error_t api_get_radio_state(Device* device, WifiRadioState* state) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr || ctx->child == nullptr) return ERROR_INVALID_STATE;
|
||||
return wifi_get_radio_state(ctx->child, state);
|
||||
}
|
||||
|
||||
error_t api_get_station_state(Device* device, WifiStationState* state) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr || ctx->child == nullptr) return ERROR_INVALID_STATE;
|
||||
return wifi_get_station_state(ctx->child, state);
|
||||
}
|
||||
|
||||
error_t api_get_access_point_state(Device* device, WifiAccessPointState* state) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr || ctx->child == nullptr) return ERROR_INVALID_STATE;
|
||||
return wifi_get_access_point_state(ctx->child, state);
|
||||
}
|
||||
|
||||
bool api_is_scanning(Device* device) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr || ctx->child == nullptr) return false;
|
||||
return wifi_is_scanning(ctx->child);
|
||||
}
|
||||
|
||||
error_t api_get_scan_results(Device* device, WifiApRecord* results, size_t* num_results) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr || ctx->child == nullptr) return ERROR_INVALID_STATE;
|
||||
return wifi_get_scan_results(ctx->child, results, num_results);
|
||||
}
|
||||
|
||||
error_t api_station_get_ipv4_address(Device* device, char* ipv4) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr || ctx->child == nullptr) return ERROR_INVALID_STATE;
|
||||
return wifi_station_get_ipv4_address(ctx->child, ipv4);
|
||||
}
|
||||
|
||||
error_t api_station_get_target_ssid(Device* device, char* ssid) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr || ctx->child == nullptr) return ERROR_INVALID_STATE;
|
||||
return wifi_station_get_target_ssid(ctx->child, ssid);
|
||||
}
|
||||
|
||||
error_t api_station_get_rssi(Device* device, int32_t* rssi) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr || ctx->child == nullptr) return ERROR_INVALID_STATE;
|
||||
return wifi_station_get_rssi(ctx->child, rssi);
|
||||
}
|
||||
|
||||
error_t api_add_event_callback(Device* device, void* callback_context, WifiEventCallback callback) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr || ctx->child == nullptr) return ERROR_INVALID_STATE;
|
||||
return wifi_add_event_callback(ctx->child, callback_context, callback);
|
||||
}
|
||||
|
||||
error_t api_remove_event_callback(Device* device, WifiEventCallback callback) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr || ctx->child == nullptr) return ERROR_INVALID_STATE;
|
||||
return wifi_remove_event_callback(ctx->child, callback);
|
||||
}
|
||||
|
||||
// ---- WifiApi: state-changing calls are marshalled onto the pinned WiFi task ----
|
||||
|
||||
error_t api_scan(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_scan(child);
|
||||
});
|
||||
return err != ERROR_NONE ? err : result;
|
||||
}
|
||||
|
||||
error_t api_station_connect(Device* device, const char* ssid, const char* password, int32_t channel) {
|
||||
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, ssid, password, channel, &result]() {
|
||||
result = wifi_station_connect(child, ssid, password, channel);
|
||||
});
|
||||
return err != ERROR_NONE ? err : result;
|
||||
}
|
||||
|
||||
error_t api_station_disconnect(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_station_disconnect(child);
|
||||
});
|
||||
return err != ERROR_NONE ? err : result;
|
||||
}
|
||||
|
||||
const WifiApi esp32_wifi_pinned_api = {
|
||||
.get_radio_state = api_get_radio_state,
|
||||
.get_station_state = api_get_station_state,
|
||||
.get_access_point_state = api_get_access_point_state,
|
||||
.is_scanning = api_is_scanning,
|
||||
.scan = api_scan,
|
||||
.get_scan_results = api_get_scan_results,
|
||||
.station_get_ipv4_address = api_station_get_ipv4_address,
|
||||
.station_get_target_ssid = api_station_get_target_ssid,
|
||||
.station_connect = api_station_connect,
|
||||
.station_disconnect = api_station_disconnect,
|
||||
.station_get_rssi = api_station_get_rssi,
|
||||
.add_event_callback = api_add_event_callback,
|
||||
.remove_event_callback = api_remove_event_callback
|
||||
};
|
||||
|
||||
// ---- Driver lifecycle ----
|
||||
// Starting/stopping the child (which brings the ESP-IDF WiFi stack up/down)
|
||||
// also happens on the pinned thread, since esp_wifi_init/start/stop/deinit
|
||||
// are subject to the same core-affinity requirement as the state-changing
|
||||
// WifiApi calls above.
|
||||
|
||||
error_t start_device(Device* device) {
|
||||
auto* ctx = new(std::nothrow) Esp32WifiPinnedCtx();
|
||||
if (ctx == nullptr) return ERROR_OUT_OF_MEMORY;
|
||||
ctx->device = device;
|
||||
device_set_driver_data(device, ctx);
|
||||
|
||||
ctx->dispatcher = dispatcher_alloc();
|
||||
ctx->running = true;
|
||||
ctx->thread = thread_alloc_full(
|
||||
"esp32_wifi_pinned",
|
||||
PINNED_THREAD_STACK_SIZE,
|
||||
pinned_thread_main,
|
||||
ctx,
|
||||
WIFI_TASK_CORE_ID
|
||||
);
|
||||
if (ctx->thread == nullptr || thread_start(ctx->thread) != ERROR_NONE) {
|
||||
LOG_E(TAG, "%s: failed to start pinned wifi task", device->name);
|
||||
if (ctx->thread != nullptr) thread_free(ctx->thread);
|
||||
dispatcher_free(ctx->dispatcher);
|
||||
device_set_driver_data(device, nullptr);
|
||||
delete ctx;
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
error_t result = ERROR_NONE;
|
||||
error_t err = run_on_pinned_thread(ctx, [ctx, &result]() {
|
||||
result = start_child_work(ctx);
|
||||
});
|
||||
if (err == ERROR_NONE) {
|
||||
err = result;
|
||||
}
|
||||
|
||||
if (err != ERROR_NONE) {
|
||||
dispatcher_dispatch(ctx->dispatcher, ctx, request_stop);
|
||||
thread_join(ctx->thread, portMAX_DELAY, 10);
|
||||
thread_free(ctx->thread);
|
||||
dispatcher_free(ctx->dispatcher);
|
||||
device_set_driver_data(device, nullptr);
|
||||
delete ctx;
|
||||
return err;
|
||||
}
|
||||
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
error_t stop_device(Device* device) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr) return ERROR_NONE;
|
||||
|
||||
error_t err = run_on_pinned_thread(ctx, [ctx]() {
|
||||
stop_child_work(ctx);
|
||||
});
|
||||
if (err != ERROR_NONE) {
|
||||
LOG_E(TAG, "%s: failed to stop child wifi device on pinned task: %s", device->name, error_to_string(err));
|
||||
}
|
||||
|
||||
dispatcher_dispatch(ctx->dispatcher, ctx, request_stop);
|
||||
thread_join(ctx->thread, portMAX_DELAY, 10);
|
||||
thread_free(ctx->thread);
|
||||
dispatcher_free(ctx->dispatcher);
|
||||
|
||||
device_set_driver_data(device, nullptr);
|
||||
delete ctx;
|
||||
return err;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
|
||||
extern Module platform_esp32_module;
|
||||
|
||||
Driver esp32_wifi_pinned_driver = {
|
||||
.name = "esp32_wifi_pinned",
|
||||
.compatible = (const char*[]) { "espressif,esp32-wifi-pinned", nullptr },
|
||||
.start_device = start_device,
|
||||
.stop_device = stop_device,
|
||||
.api = (const void*)&esp32_wifi_pinned_api,
|
||||
.device_type = &WIFI_TYPE,
|
||||
.owner = &platform_esp32_module,
|
||||
.internal = nullptr
|
||||
};
|
||||
|
||||
} // extern "C"
|
||||
|
||||
#endif // CONFIG_SOC_WIFI_SUPPORTED or CONFIG_SLAVE_SOC_WIFI_SUPPORTED
|
||||
@@ -22,6 +22,10 @@ extern Driver esp32_sdspi_driver;
|
||||
extern Driver esp32_spi_driver;
|
||||
extern Driver esp32_uart_driver;
|
||||
extern Driver esp32_grove_driver;
|
||||
#if defined(CONFIG_SOC_WIFI_SUPPORTED) || defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
|
||||
extern Driver esp32_wifi_driver;
|
||||
extern Driver esp32_wifi_pinned_driver;
|
||||
#endif
|
||||
#if defined(CONFIG_BT_NIMBLE_ENABLED)
|
||||
extern Driver esp32_bluetooth_driver;
|
||||
extern Driver esp32_ble_serial_driver;
|
||||
@@ -49,6 +53,10 @@ static error_t start() {
|
||||
check(driver_construct_add(&esp32_spi_driver) == ERROR_NONE);
|
||||
check(driver_construct_add(&esp32_uart_driver) == ERROR_NONE);
|
||||
check(driver_construct_add(&esp32_grove_driver) == ERROR_NONE);
|
||||
#if defined(CONFIG_SOC_WIFI_SUPPORTED) || defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
|
||||
check(driver_construct_add(&esp32_wifi_driver) == ERROR_NONE);
|
||||
check(driver_construct_add(&esp32_wifi_pinned_driver) == ERROR_NONE);
|
||||
#endif
|
||||
#if defined(CONFIG_BT_NIMBLE_ENABLED)
|
||||
check(driver_construct_add(&esp32_bluetooth_driver) == ERROR_NONE);
|
||||
check(driver_construct_add(&esp32_ble_serial_driver) == ERROR_NONE);
|
||||
@@ -67,6 +75,10 @@ static error_t start() {
|
||||
static error_t stop() {
|
||||
/* We crash when destruct fails, because if a single driver fails to destruct,
|
||||
* there is no guarantee that the previously destroyed drivers can be recovered */
|
||||
#if defined(CONFIG_SOC_WIFI_SUPPORTED) || defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
|
||||
check(driver_remove_destruct(&esp32_wifi_pinned_driver) == ERROR_NONE);
|
||||
check(driver_remove_destruct(&esp32_wifi_driver) == ERROR_NONE);
|
||||
#endif
|
||||
#if SOC_USB_OTG_SUPPORTED
|
||||
check(driver_remove_destruct(&esp32_usbhost_msc_driver) == ERROR_NONE);
|
||||
check(driver_remove_destruct(&esp32_usbhost_midi_driver) == ERROR_NONE);
|
||||
|
||||
@@ -0,0 +1,349 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Mock WiFi driver for the simulator: fakes a radio with a fixed list of
|
||||
// access points instead of talking to real hardware. Behaviour is modeled
|
||||
// after Tactility/Source/service/wifi/WifiMock.cpp (the old HAL's equivalent
|
||||
// mock) - actions succeed instantly, there's no real scan or connect delay.
|
||||
|
||||
#include <tactility/concurrent/mutex.h>
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/drivers/wifi.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
|
||||
#define TAG "mock_wifi"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr size_t WIFI_MAX_CALLBACKS = 4;
|
||||
|
||||
struct MockApRecord {
|
||||
const char* ssid;
|
||||
int8_t rssi;
|
||||
WifiAuthenticationType authentication_type;
|
||||
};
|
||||
|
||||
// Same fixture data as the old WifiMock.cpp, so simulator UI testing sees
|
||||
// familiar results.
|
||||
constexpr MockApRecord MOCK_SCAN_RESULTS[] = {
|
||||
{ "Home Wifi", -30, WIFI_AUTHENTICATION_TYPE_WPA2_PSK },
|
||||
{ "No place like 127.0.0.1", -67, WIFI_AUTHENTICATION_TYPE_WPA2_PSK },
|
||||
{ "Pretty fly for a Wi-Fi", -70, WIFI_AUTHENTICATION_TYPE_WPA2_PSK },
|
||||
{ "An AP with a really, really long name", -80, WIFI_AUTHENTICATION_TYPE_WPA2_PSK },
|
||||
{ "Bad Reception", -90, WIFI_AUTHENTICATION_TYPE_OPEN },
|
||||
};
|
||||
constexpr size_t MOCK_SCAN_RESULT_COUNT = sizeof(MOCK_SCAN_RESULTS) / sizeof(MOCK_SCAN_RESULTS[0]);
|
||||
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;
|
||||
|
||||
Mutex mutex {};
|
||||
WifiRadioState radioState = WIFI_RADIO_STATE_OFF;
|
||||
WifiStationState stationState = WIFI_STATION_STATE_DISCONNECTED;
|
||||
bool scanning = false;
|
||||
char targetSsid[33] = {};
|
||||
|
||||
Mutex callbackMutex {};
|
||||
WifiCallbackEntry callbacks[WIFI_MAX_CALLBACKS] = {};
|
||||
size_t callbackCount = 0;
|
||||
};
|
||||
|
||||
#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);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- WifiApi ----
|
||||
|
||||
error_t apiGetRadioState(Device* device, WifiRadioState* state) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr || state == nullptr) return ERROR_INVALID_ARGUMENT;
|
||||
mutex_lock(&ctx->mutex);
|
||||
*state = ctx->radioState;
|
||||
mutex_unlock(&ctx->mutex);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
error_t apiGetStationState(Device* device, WifiStationState* state) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr || state == nullptr) return ERROR_INVALID_ARGUMENT;
|
||||
mutex_lock(&ctx->mutex);
|
||||
*state = ctx->stationState;
|
||||
mutex_unlock(&ctx->mutex);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
error_t apiGetAccessPointState(Device* device, WifiAccessPointState* state) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr || state == nullptr) return ERROR_INVALID_ARGUMENT;
|
||||
// Access point mode isn't implemented by this mock.
|
||||
*state = WIFI_ACCESS_POINT_STATE_STOPPED;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
bool apiIsScanning(Device* device) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr) return false;
|
||||
mutex_lock(&ctx->mutex);
|
||||
bool scanning = ctx->scanning;
|
||||
mutex_unlock(&ctx->mutex);
|
||||
return scanning;
|
||||
}
|
||||
|
||||
error_t apiScan(Device* device) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr) return ERROR_INVALID_STATE;
|
||||
|
||||
mutex_lock(&ctx->mutex);
|
||||
if (ctx->radioState != WIFI_RADIO_STATE_ON || ctx->scanning) {
|
||||
mutex_unlock(&ctx->mutex);
|
||||
return ERROR_INVALID_STATE;
|
||||
}
|
||||
ctx->scanning = true;
|
||||
mutex_unlock(&ctx->mutex);
|
||||
|
||||
WifiEvent started_event = {};
|
||||
started_event.type = WIFI_EVENT_TYPE_SCAN_STARTED;
|
||||
fireEvent(ctx, started_event);
|
||||
|
||||
// No real radio, so the "scan" completes instantly.
|
||||
mutex_lock(&ctx->mutex);
|
||||
ctx->scanning = false;
|
||||
mutex_unlock(&ctx->mutex);
|
||||
|
||||
WifiEvent finished_event = {};
|
||||
finished_event.type = WIFI_EVENT_TYPE_SCAN_FINISHED;
|
||||
fireEvent(ctx, finished_event);
|
||||
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
error_t apiGetScanResults(Device* device, WifiApRecord* results, size_t* num_results) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr || results == nullptr || num_results == nullptr) return ERROR_INVALID_ARGUMENT;
|
||||
|
||||
size_t count = std::min(*num_results, MOCK_SCAN_RESULT_COUNT);
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
const MockApRecord& src = MOCK_SCAN_RESULTS[i];
|
||||
WifiApRecord& dst = results[i];
|
||||
memset(dst.ssid, 0, sizeof(dst.ssid));
|
||||
strncpy(dst.ssid, src.ssid, sizeof(dst.ssid) - 1);
|
||||
dst.rssi = src.rssi;
|
||||
dst.channel = 1;
|
||||
dst.authentication_type = src.authentication_type;
|
||||
}
|
||||
*num_results = count;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
error_t apiStationGetIpv4Address(Device* device, char* ipv4) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr || ipv4 == nullptr) return ERROR_INVALID_ARGUMENT;
|
||||
|
||||
mutex_lock(&ctx->mutex);
|
||||
bool connected = ctx->stationState == WIFI_STATION_STATE_CONNECTED;
|
||||
mutex_unlock(&ctx->mutex);
|
||||
|
||||
strncpy(ipv4, connected ? MOCK_IPV4_ADDRESS : "0.0.0.0", 16);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
error_t apiStationGetTargetSsid(Device* device, char* ssid) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr || ssid == nullptr) return ERROR_INVALID_ARGUMENT;
|
||||
mutex_lock(&ctx->mutex);
|
||||
strncpy(ssid, ctx->targetSsid, sizeof(ctx->targetSsid));
|
||||
mutex_unlock(&ctx->mutex);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
error_t apiStationConnect(Device* device, const char* ssid, const char* password, int32_t channel) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr || ssid == nullptr) return ERROR_INVALID_ARGUMENT;
|
||||
|
||||
mutex_lock(&ctx->mutex);
|
||||
if (ctx->radioState != WIFI_RADIO_STATE_ON) {
|
||||
mutex_unlock(&ctx->mutex);
|
||||
return ERROR_INVALID_STATE;
|
||||
}
|
||||
strncpy(ctx->targetSsid, ssid, sizeof(ctx->targetSsid) - 1);
|
||||
ctx->targetSsid[sizeof(ctx->targetSsid) - 1] = '\0';
|
||||
ctx->stationState = WIFI_STATION_STATE_CONNECTION_PENDING;
|
||||
mutex_unlock(&ctx->mutex);
|
||||
|
||||
WifiEvent pending_event = {};
|
||||
pending_event.type = WIFI_EVENT_TYPE_STATION_STATE_CHANGED;
|
||||
pending_event.station_state = WIFI_STATION_STATE_CONNECTION_PENDING;
|
||||
fireEvent(ctx, pending_event);
|
||||
|
||||
// No real radio to negotiate with, so the mock always succeeds instantly.
|
||||
mutex_lock(&ctx->mutex);
|
||||
ctx->stationState = WIFI_STATION_STATE_CONNECTED;
|
||||
mutex_unlock(&ctx->mutex);
|
||||
|
||||
WifiEvent connected_event = {};
|
||||
connected_event.type = WIFI_EVENT_TYPE_STATION_STATE_CHANGED;
|
||||
connected_event.station_state = WIFI_STATION_STATE_CONNECTED;
|
||||
fireEvent(ctx, connected_event);
|
||||
|
||||
WifiEvent result_event = {};
|
||||
result_event.type = WIFI_EVENT_TYPE_STATION_CONNECTION_RESULT;
|
||||
result_event.connection_error = WIFI_STATION_CONNECTION_ERROR_NONE;
|
||||
fireEvent(ctx, result_event);
|
||||
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
error_t apiStationDisconnect(Device* device) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr) return ERROR_INVALID_STATE;
|
||||
|
||||
mutex_lock(&ctx->mutex);
|
||||
bool was_connected = ctx->stationState != WIFI_STATION_STATE_DISCONNECTED;
|
||||
ctx->stationState = WIFI_STATION_STATE_DISCONNECTED;
|
||||
mutex_unlock(&ctx->mutex);
|
||||
|
||||
if (was_connected) {
|
||||
WifiEvent event = {};
|
||||
event.type = WIFI_EVENT_TYPE_STATION_STATE_CHANGED;
|
||||
event.station_state = WIFI_STATION_STATE_DISCONNECTED;
|
||||
fireEvent(ctx, event);
|
||||
}
|
||||
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
error_t apiStationGetRssi(Device* device, int32_t* rssi) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr || rssi == nullptr) return ERROR_INVALID_ARGUMENT;
|
||||
|
||||
mutex_lock(&ctx->mutex);
|
||||
bool connected = ctx->stationState == WIFI_STATION_STATE_CONNECTED;
|
||||
mutex_unlock(&ctx->mutex);
|
||||
|
||||
if (!connected) return ERROR_INVALID_STATE;
|
||||
*rssi = MOCK_CONNECTED_RSSI;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
error_t apiAddEventCallback(Device* device, void* callback_context, WifiEventCallback callback) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr || callback == nullptr) return ERROR_INVALID_ARGUMENT;
|
||||
|
||||
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);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
error_t apiRemoveEventCallback(Device* device, WifiEventCallback callback) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr || callback == nullptr) return ERROR_INVALID_ARGUMENT;
|
||||
|
||||
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_unlock(&ctx->callbackMutex);
|
||||
return ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
const WifiApi posix_wifi_api = {
|
||||
.get_radio_state = apiGetRadioState,
|
||||
.get_station_state = apiGetStationState,
|
||||
.get_access_point_state = apiGetAccessPointState,
|
||||
.is_scanning = apiIsScanning,
|
||||
.scan = apiScan,
|
||||
.get_scan_results = apiGetScanResults,
|
||||
.station_get_ipv4_address = apiStationGetIpv4Address,
|
||||
.station_get_target_ssid = apiStationGetTargetSsid,
|
||||
.station_connect = apiStationConnect,
|
||||
.station_disconnect = apiStationDisconnect,
|
||||
.station_get_rssi = apiStationGetRssi,
|
||||
.add_event_callback = apiAddEventCallback,
|
||||
.remove_event_callback = apiRemoveEventCallback
|
||||
};
|
||||
|
||||
// ---- 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.
|
||||
|
||||
error_t startDevice(Device* device) {
|
||||
auto* ctx = new(std::nothrow) PosixWifiCtx();
|
||||
if (ctx == nullptr) return ERROR_OUT_OF_MEMORY;
|
||||
|
||||
ctx->device = device;
|
||||
mutex_construct(&ctx->mutex);
|
||||
mutex_construct(&ctx->callbackMutex);
|
||||
ctx->radioState = WIFI_RADIO_STATE_ON;
|
||||
|
||||
device_set_driver_data(device, ctx);
|
||||
|
||||
LOG_I(TAG, "WiFi radio on (mock)");
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
error_t stopDevice(Device* device) {
|
||||
auto* ctx = GET_CTX(device);
|
||||
if (ctx == nullptr) return ERROR_NONE;
|
||||
|
||||
device_set_driver_data(device, nullptr);
|
||||
mutex_destruct(&ctx->callbackMutex);
|
||||
mutex_destruct(&ctx->mutex);
|
||||
delete ctx;
|
||||
|
||||
LOG_I(TAG, "WiFi radio off (mock)");
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
|
||||
extern Module platform_posix_module;
|
||||
|
||||
Driver posix_wifi_driver = {
|
||||
.name = "mock_wifi",
|
||||
.compatible = (const char*[]) { "posix,mock-wifi", nullptr },
|
||||
.start_device = startDevice,
|
||||
.stop_device = stopDevice,
|
||||
.api = (const void*)&posix_wifi_api,
|
||||
.device_type = &WIFI_TYPE,
|
||||
.owner = &platform_posix_module,
|
||||
.internal = nullptr
|
||||
};
|
||||
|
||||
} // extern "C"
|
||||
@@ -1,15 +1,19 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <tactility/check.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
extern Driver posix_wifi_driver;
|
||||
|
||||
static error_t start() {
|
||||
/* NO-OP for now */
|
||||
check(driver_construct_add(&posix_wifi_driver) == ERROR_NONE);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t stop() {
|
||||
/* NO-OP for now */
|
||||
check(driver_remove_destruct(&posix_wifi_driver) == ERROR_NONE);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user