feat(esp_wifi): Add support for advanced roaming as a wifi app

- Adds support for advanced roaming as a wifi app.
This commit is contained in:
jgujarathi 2024-03-29 18:39:47 +05:30 committed by BOT
parent 71e6c10f7c
commit 9a88dab748
13 changed files with 1276 additions and 6 deletions

View File

@ -629,3 +629,4 @@ mainmenu "Espressif IoT Development Framework Configuration"
- CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH
- CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL
- CONFIG_ESP_WIFI_EAP_TLS1_3
- CONFIG_ESP_WIFI_ENABLE_ROAMING_APP

View File

@ -37,7 +37,10 @@ if(CONFIG_ESP_WIFI_ENABLED OR CONFIG_ESP_HOST_WIFI_ENABLED)
endif()
if(CONFIG_ESP_WIFI_NAN_ENABLE)
list(APPEND srcs "wifi_apps/src/nan_app.c")
list(APPEND srcs "wifi_apps/nan_app/src/nan_app.c")
endif()
if(CONFIG_ESP_WIFI_ENABLE_ROAMING_APP)
list(APPEND srcs "wifi_apps/roaming_app/src/roaming_app.c")
endif()
set(local_include_dirs include/local)
else()
@ -50,10 +53,13 @@ else()
endif()
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "include" "wifi_apps/include" ${local_include_dirs}
INCLUDE_DIRS "include" "wifi_apps/include" "wifi_apps/nan_app/include" ${local_include_dirs}
REQUIRES esp_event esp_phy esp_netif
PRIV_REQUIRES driver esptool_py esp_pm esp_timer nvs_flash
wpa_supplicant hal lwip esp_coex ${extra_priv_requires}
PRIV_INCLUDE_DIRS ../wpa_supplicant/src/ ../wpa_supplicant/esp_supplicant/src/
wifi_apps/roaming_app/include
LDFRAGMENTS "${ldfragments}")
if(CONFIG_ESP_WIFI_ENABLED OR CONFIG_ESP_HOST_WIFI_ENABLED)

View File

@ -574,6 +574,26 @@ menu "Wi-Fi"
help
Select this option to enable WiFi Multiband operation certification support.
config ESP_WIFI_ENABLE_ROAMING_APP
bool "Advanced support for Wi-Fi Roaming (Experimental)"
depends on IDF_EXPERIMENTAL_FEATURES
default n
select ESP_WIFI_SCAN_CACHE
help
Enable Espressif's roaming app to allow for efficient Wi-Fi roaming.
This includes configurable periodic environment scans, maintaining a cache of the
best APs, handling low rssi events etc.
Risk Warning
- Please note that this feature is still experimental and enabling this potentially can
lead to unpredictable scanning, connection and roaming attempts.
We are still working on tuning and optimising this feature to ensure reliable and stable use.
menu "Configure roaming App"
depends on ESP_WIFI_ENABLE_ROAMING_APP
rsource "wifi_apps/roaming_app/src/Kconfig.roaming"
endmenu
config ESP_WIFI_DPP_SUPPORT
bool "Enable DPP support"
default n

View File

@ -533,7 +533,7 @@ static void nan_app_action_ndp_confirm(void *arg, esp_event_base_t event_base, i
}
if (nan_find_ndl(evt->ndp_id, NULL) == NULL) {
/* As ndl isn't found, timeout has occured for NDP response and datapath request is rejected */
/* As ndl isn't found, timeout has occurred for NDP response and datapath request is rejected */
goto done;
}
if (evt->status == NDP_STATUS_REJECTED) {

View File

@ -0,0 +1,131 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "sdkconfig.h"
#include "esp_log.h"
#include "esp_err.h"
#include "esp_wifi_types.h"
#include "utils/common.h"
#include <sys/time.h>
#ifdef __cplusplus
extern "C" {
#endif
#define SUPPLICANT_CANDIDATE_LIST_EXPIRY 10
/* Global Roaming Configuration */
#define ROAMING_BACKOFF_TIME CONFIG_ESP_WIFI_ROAMING_BACKOFF_TIME
/* Low RSSI based roaming configuration */
#define LOW_RSSI_ROAMING_ENABLED CONFIG_ESP_WIFI_ROAMING_LOW_RSSI_ROAMING
#if LOW_RSSI_ROAMING_ENABLED
#define ROAMING_LOW_RSSI_THRESHOLD CONFIG_ESP_WIFI_ROAMING_LOW_RSSI_THRESHOLD
#define RSSI_THRESHOLD_REDUCTION_OFFSET CONFIG_ESP_WIFI_ROAMING_LOW_RSSI_OFFSET
#endif /*LOW_RSSI_ROAMING_ENABLED*/
/* Periodic Scan based Roaming configuration */
#define PERIODIC_SCAN_MONITORING CONFIG_ESP_WIFI_ROAMING_PERIODIC_SCAN_MONITOR
#if PERIODIC_SCAN_MONITORING
#define SCAN_MONITOR_INTERVAL CONFIG_ESP_WIFI_ROAMING_SCAN_MONITOR_INTERVAL
#define SCAN_MONITOR_RSSI_THRESHOLD CONFIG_ESP_WIFI_ROAMING_PERIODIC_SCAN_THRESHOLD
#define SCAN_ROAM_RSSI_DIFF CONFIG_ESP_WIFI_ROAMING_SCAN_ROAM_RSSI_DIFF
#endif /* PERIODIC_SCAN_MONITORING */
/* Scan configuration */
#define SCAN_TIME_MIN_DURATION CONFIG_ESP_WIFI_ROAMING_SCAN_MIN_SCAN_TIME
#define SCAN_TIME_MAX_DURATION CONFIG_ESP_WIFI_ROAMING_SCAN_MAX_SCAN_TIME
#define HOME_CHANNEL_DWELL_TIME CONFIG_ESP_WIFI_ROAMING_HOME_CHANNEL_DWELL_TIME
#define SCAN_PREFERRED_CHAN_LIST CONFIG_ESP_WIFI_ROAMING_SCAN_CHAN_LIST
#define DEFAULT_PREFERRED_SCAN_CHAN_LIST "None"
#define SCAN_RESULTS_USABILITY_WINDOW CONFIG_ESP_WIFI_ROAMING_SCAN_EXPIRY_WINDOW
#define MAX_CANDIDATE_COUNT CONFIG_ESP_WIFI_ROAMING_MAX_CANDIDATES
/* Legacy roaming configuration */
#define LEGACY_ROAM_ENABLED CONFIG_ESP_WIFI_ROAMING_LEGACY_ROAMING
#define BSS_TM_RETRY_COUNT CONFIG_ESP_WIFI_NETWORK_ASSISTED_ROAMING_RETRY_COUNT
/* Network Assisted Roaming */
#define NETWORK_ASSISTED_ROAMING_ENABLED CONFIG_ESP_WIFI_ROAMING_NETWORK_ASSISTED_ROAM
/* Periodic RRM configuration */
#define PERIODIC_RRM_MONITORING CONFIG_ESP_WIFI_ROAMING_PERIODIC_RRM_MONITORING
#if PERIODIC_RRM_MONITORING
#define RRM_MONITOR_TIME CONFIG_ESP_WIFI_ROAMING_RRM_MONITOR_TIME
#define RRM_MONITOR_RSSI_THRESHOLD CONFIG_ESP_WIFI_ROAMING_RRM_MONITOR_THRESHOLD
#endif /*PERIODIC_RRM_MONITORING*/
#define MAX_SCAN_CHAN_LIST_COUNT 14
#define MAX_NEIGHBOR_LEN 512
#define IS_PSK(authmode) \
(((authmode == WIFI_AUTH_WPA_PSK) || (authmode == WIFI_AUTH_WPA2_PSK) || \
(authmode == WIFI_AUTH_WPA_WPA2_PSK) || (authmode == WIFI_AUTH_WPA3_PSK) || \
(authmode == WIFI_AUTH_WPA2_WPA3_PSK) || (authmode == WIFI_AUTH_WAPI_PSK) ? 1 : 0))
#define OWE_COMPATIBLE(curr_auth, cand_auth) \
((((curr_auth == WIFI_AUTH_OPEN) || (curr_auth == WIFI_AUTH_OWE)) && ((cand_auth == WIFI_AUTH_OPEN) || (cand_auth == WIFI_AUTH_OWE)))? 1 : 0)
#define PSK_COMPATIBLE(curr_auth, cand_auth) \
((IS_PSK(curr_auth) && IS_PSK(cand_auth)) ? 1 : 0)
struct scanned_ap_info {
uint16_t current_count;
struct timeval time;
wifi_ap_record_t ap_records[MAX_CANDIDATE_COUNT];
};
struct cand_bss {
uint8_t channel;
uint8_t bssid[ETH_ALEN];
};
struct roaming_app {
wifi_scan_config_t scan_params;
bool scan_ongoing;
int8_t current_rssi_threshold;
char *btm_neighbor_list;
struct timeval last_roamed_time;
wifi_ap_record_t ap_info;
struct scanned_ap_info scanned_aps;
bool btm_support;
bool rrm_support;
#if LOW_RSSI_ROAMING_ENABLED
int8_t current_low_rssi_threshold;
#endif
#if LEGACY_ROAM_ENABLED && NETWORK_ASSISTED_ROAMING_ENABLED
uint8_t btm_attempt;
#endif
#if LEGACY_ROAM_ENABLED
bool force_roam_ongoing;
#endif
#if PERIODIC_RRM_MONITORING
bool periodic_rrm_active;
bool rrm_request_active;
#endif
#if PERIODIC_SCAN_MONITORING
bool periodic_scan_active;
#endif
};
void init_roaming_app(void);
void deinit_roaming_app(void);
#if PERIODIC_RRM_MONITORING
void roaming_app_periodic_rrm_internal_handler(void *data, void *ctx);
#endif /*PERIODIC_RRM_MONITORING*/
#if PERIODIC_SCAN_MONITORING
void roaming_app_periodic_scan_internal_handler(void *data, void *ctx);
#endif /*PERIODIC_SCAN_ROAM_MONITORING*/
void roaming_app_trigger_roam_internal_handler(void *data, void *ctx);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,174 @@
# Visible if ESP_WIFI_ENABLE_ROAMING_APP enabled in components/esp_wifi/Kconfig
menu "Roaming triggers"
config ESP_WIFI_ROAMING_LOW_RSSI_ROAMING
bool "Use Low RSSI to trigger roaming."
default y
help
Enable to use a RSSI threshold to trigger roaming.
config ESP_WIFI_ROAMING_LOW_RSSI_THRESHOLD
depends on ESP_WIFI_ROAMING_LOW_RSSI_ROAMING
int "WiFi RSSI threshold to trigger roaming"
range -99 -30
default -60
help
WiFi RSSI threshold to trigger roaming value in dBm (-99 to -1). Values under -30 dbm
might lead to a flood of low rssi events. This interferes with normal functioning and
TX/Rx performance.
config ESP_WIFI_ROAMING_LOW_RSSI_OFFSET
depends on ESP_WIFI_ROAMING_LOW_RSSI_ROAMING
int "Offset by which to reset the RSSI Threshold after attempt to roam."
range 0 99
default 5
help
Decide the offset by which to decrease the Low RSSI threshold set by ESP_WIFI_ROAMING_LOW_RSSI_THRESHOLD
after each failed attempt to roam. This allows for the station to keep scanning for better AP's after
the Low RSSI threshold is reached in a stepped manner, rather than only attempting to roam the first time
the current AP's RSSI breaches the set RSSI threshold.
config ESP_WIFI_ROAMING_PERIODIC_SCAN_MONITOR
bool "Conduct periodic scans to check if a better AP is available"
default y
help
Conduct periodic scans periodically to check if a better AP is available.
config ESP_WIFI_ROAMING_PERIODIC_SCAN_THRESHOLD
int "Threshold at which to begin periodic scanning for a better AP."
depends on ESP_WIFI_ROAMING_PERIODIC_SCAN_MONITOR
range -99 -1
default -50
help
Threshold at which the station will begin scanning to find an AP with better RSSI.
config ESP_WIFI_ROAMING_SCAN_MONITOR_INTERVAL
int "Time intervals (in seconds) at which station will initiate a scan"
depends on ESP_WIFI_ROAMING_PERIODIC_SCAN_MONITOR
range 1 1500
default 30
help
Intervals at which station will periodically scan to check if better AP is available
config ESP_WIFI_ROAMING_SCAN_ROAM_RSSI_DIFF
int "RSSI difference b/w current AP and candidate AP to initiate connection"
depends on ESP_WIFI_ROAMING_PERIODIC_SCAN_MONITOR
default 15
range 0 99
help
Minimum RSSI difference b/w current AP and a potential roaming candidate AP
to trigger a roaming attempt.
endmenu #"Roaming triggers"
menu "Roaming Methods"
config ESP_WIFI_ROAMING_LEGACY_ROAMING
bool "Support Legacy roaming approach"
default y
help
Roaming between APs that do not support 802.11kv.
This will allow station to roam even when connection is not BTM supported,
by forcefully disconnecting from current AP and connecting to better AP.
config ESP_WIFI_ROAMING_NETWORK_ASSISTED_ROAM
bool "Support Network Assisted roaming using 802.11kv"
depends on ESP_WIFI_11KV_SUPPORT
default y
help
Roaming between APs using network assisted Roaming.
This involves BSS Transition Management mechanisms outlined in 802.11v.
Note that this moves the responsibility to the AP's network, and hence isn't
guaranteed to cause the station to attempt to roam each time.
config ESP_WIFI_NETWORK_ASSISTED_ROAMING_RETRY_COUNT
int "Retry count after which to switch to legacy roaming"
depends on ESP_WIFI_ROAMING_NETWORK_ASSISTED_ROAM
depends on ESP_WIFI_ROAMING_LEGACY_ROAMING
range 1 5
default 2
help
Retry threshold after which the station should stop using Network Assisted
roaming methods and start using legacy roaming instead.
endmenu #"Roaming Methods"
menu "Scan Configuration"
config ESP_WIFI_ROAMING_SCAN_MIN_SCAN_TIME
int "Minimum duration (in milliseconds) of station's per channel active scan"
default 10
range 0 120
help
Minimum duration of active scanning per channel in milliseconds.
config ESP_WIFI_ROAMING_SCAN_MAX_SCAN_TIME
int "Maximum duration (in milliseconds) of station's per channel active scan time"
default 70
range 30 120
help
Maximum duration of active scanning per channel in milliseconds.
config ESP_WIFI_ROAMING_HOME_CHANNEL_DWELL_TIME
int "Home channel dwell time scanning between consecutive channels"
default 30
range 30 150
help
If connected, duration for which the station will return to it's home channel for Tx/Rx of
frames stored in buffers between scanning on consecutive channels.
config ESP_WIFI_ROAMING_SCAN_CHAN_LIST
string "Preferred channel list for scanning"
default "None"
help
Channels your wireless network operates on to allow for faster scanning.
Specify the channels(between 1-14) in a comma separated manner.
config ESP_WIFI_ROAMING_SCAN_EXPIRY_WINDOW
int "Scan results expiry window (in seconds)"
default 10
range 5 20
help
Duration for which the results from the most recent scans can be used
by the roaming app for determining the roaming candidates.
config ESP_WIFI_ROAMING_MAX_CANDIDATES
int "Max Candidates in the network"
default 3
range 3 20
help
Max candidates that can be considered while scanning as a part of the
network at one time.
endmenu #"Scan Configuration"
config ESP_WIFI_ROAMING_BACKOFF_TIME
int "Default time to wait between subsequent roaming attempts."
default 15
range 0 120
help
Time to wait (in seconds) by station before registering for the RSSI event again
or start continuous montoring to find better AP.
config ESP_WIFI_ROAMING_PERIODIC_RRM_MONITORING
bool "Send periodic neighbor report request to AP for internal list updation"
depends on ESP_WIFI_11KV_SUPPORT
default y
help
This option will enable station to keep sending RRM neighbor list request to AP and
update its internal list.
config ESP_WIFI_ROAMING_RRM_MONITOR_TIME
int "Time interval (in seconds) between neighbor report requests to an AP"
depends on ESP_WIFI_ROAMING_PERIODIC_RRM_MONITORING
default 60
range 0 1500
help
Enable this to send periodic neighbor report requests to the AP.
These neighbor report requests provide information about other APs in the same managed
network. This information is used for more intelligent roaming.
config ESP_WIFI_ROAMING_RRM_MONITOR_THRESHOLD
int "Threshold for sending periodic neighbor report requests"
depends on ESP_WIFI_ROAMING_PERIODIC_RRM_MONITORING
default -20
range -99 0
help
The RSSI threshold beyond which we start sending periodic neighbor report requests.

View File

@ -0,0 +1,92 @@
**Introduction**
The advanced Wi-Fi roaming app has been written with the intention of simplifying the process of developing applications that will function in a network environment that allows for roaming between the service areas of multiple compatible APs. It gathers basic approaches about how different roaming mechanisms and APIs can be integrated for an efficient solution to roaming and bundles into an easy to user yet highly configurable module.
**How to use:**
To enable the roaming app in the menuconfig, please navigate to Component Settings --> Wi-Fi and then enable “Advanced support for Wi-Fi Roaming”
**Configuring the advanced Wi-Fi roaming app :**
After enabling the roaming app in the menuconfig, this roaming app can be configured to best suit your application requirements and the network environment. The configurations are classified into Roaming Triggers (better understood as “Under what conditions to roam?”), Roaming methods (better understood as “How to Roam), and then some additional configurations such as scanning parameters, backoff times, periodic neighbor report requests etc.
**Roaming Triggers: (Roaming Module Settings --> Roaming Triggers)**
There are broadly two different Roaming triggers you can choose from:
1) Low RSSI triggered roaming :
If enabled, in this method the roaming app sets a Wi-Fi Threshold (configured by “Wi-Fi RSSI threshold to trigger roaming”), which when reached by the connection to the current AP, triggers a check for a better AP and if found will trigger roaming.
Every time the threshold is reached, a new threshold needs to be set at an even lower rssi threshold since if we fail to find a better AP the first time the RSSI threshold is reached there will be no further attempts to find better APs leading to possible disconnection as the only avenue for finding a better AP. The offset by which the next RSSI threshold can be set is decided by “Wi-Fi RSSI threshold to trigger roaming” which defaults to 5.
Also please note that if the AP we connect to, upon connecting itself has a worse RSSI than the threshold set in the configuration, the new RSSI threshold is set to the current RSSI - offset. Additionally, the RSSI threshold gets reset to the configured value if the RSSI ever goes below the configured threshold by the offset amount.
2) Periodic Scan based roaming:
Unlike Low RSSI triggered roaming, which is a bit reactive to the changing network environment, periodic scanning-based roaming (enabled by “Conduct periodic scans to check if a better AP is available”) allows for a more active approach to ensuring that you are connected to the best AP in the network. You can also decide the threshold after which you want to conduct the periodic scans with a default of 20dbm. (Configured by “Threshold at which to begin periodic scanning for a better AP”)
The intervals at which this periodic scan will take place can also be configured through “Time intervals at which station will initiate a scan”. This defaults to 30 seconds.The RSSI difference between a candidate AP and the current AP, which can be considered as acceptable to initiate roaming can also be configured as the “RSSI difference b/w current AP and a candidate AP to initiate roaming”
**Please note that at least one of the two Roaming Triggers needs to be enabled.**
**Roaming Methods**
Currently 2 methods of roaming are supported by the roaming app. (Roaming App Settings --> Roaming Methods)
1) Network Assisted Roaming:
Enabled by “Support Network Assisted Roaming using 802.11kv”, this method primarily uses the BSS transition Management mechanisms outlined in IEEE 802.11v. It uses Candidates received from neighbor report requests (if enabled, explained later.) and scanning results to Send BSS transition Management Queries to the AP it is currently associated to. Depending on the current radio environment and vendor implementation on the side of the AP, this could then lead to BSS Transition Management Requests and corresponding BSS Transition Management responses which could lead to a seamless transition from one AP to another. For a better understanding of the mechanisms involved and the general implementation please look up the IEEE 802.11v specification and upstream wpa_supplicants implementation.
Please note that for this to work as expected, the APs should support 802.11k & 802.11v and be setup in a network where they are aware of each other.
2) Legacy Roaming approach.
Enabled by “Support Legacy roaming approach”, this is designed for scenarios where there is no support for 802.11v amongst the APs of the network. In this scenario, upon determining the better AP to connect to, the station forcibly will disconnect and attempt to connect to the AP with the better AP.
Please note that if both approaches are enabled, there is an additional configuration (“Retry count after which to switch to legacy roaming”) that allows developers to decide how many times should the station attempt to roam using the Network Assisted roaming methods before switching to using the legacy approach.
**Please note that at least one of the two roaming methods needs to be selected.**
**Scan configuration**
The scan configuration allows for configuring the parameters for the scans that are conducted in various scenarios during the functioning of the roaming app. Please note that all scans conducted by the roaming app are active scans.
The minimum and maximum active scanning duration for each channel in milliseconds can be configured through “Minimum duration of active scan time for a station” & “Maximum duration of active scan time for a station”. If connected, the “Home channel dwell time between scanning consecutive channels” configuration decides for how long the station will return to the home channel for Tx of various frames in the buffer, and Rx of frames buffered by the AP.
Additionally, if channels of operation of the APs that the application designed will work with is known, you can provide this information as an ordered list of comma-separated channels. (configured using “Preferred channel list for scanning”) (for e.g. 1,6,9,11) Only those channels will be scanned in the order mentioned. Keeping in mind that network discovery/scanning is the process that takes up most of the time in roaming as well connecting to an AP, providing such a list could significantly increase the efficiency of the roaming app and process.
This module can trigger scans due to several reasons. The configuration “Scan results expiry window” decides the duration for which different modules will use the most recent scan results instead of triggering a new scan of their own.
**Backoff Time:**
Successive roaming attempts by multiple roaming triggers simultaneously could lead to performance degradation, hence a backoff time can be configured (“Default time to wait between subsequent roaming attempts”). A roaming attempt can be made using any method only once the configured backoff time has passed since the last roaming attempt.
**Periodic Neighbor Report Requests:**
Neighbor Report requests are a part of the IEEE 802.11k specification, and hence the intended network would need to support and be setup in a way to support its mechanisms. Periodic neighbor report requests provide vital information about the network and other APs in the vicinity that are candidate APs of the same network. This can be enabled using “Send Periodic Neighbor Report requests for updating the internal list”. he frequency of these requests is controlled by “Time interval between Periodic Neighbor report Requests”. There can also be a RSSI threshold (“Threshold for sending periodic neighbor report requests”) after which you wish to consider sending these requests however this is set by default to 0.
**Notes :**
1) Advanced roaming support is disabled by default.
2) When enabling the advanced roaming support , it is expected that the bssid to connect to is not specifically set by the application. This would defeat the purpose of roaming between different APs of the network. Hence if the BSSID is set (in wifi_config_t.sta) , it will be unset at the first disconnection/connection.
3) For roaming to work as expected, the APs between which the station is expected to roam must have the same or compatible authmode. These include :
Open <--> OWE
PSK based authmodes <--> PSK based authmodes (Does not include WEP)
Enterprise <--> Enterprise.
Also note that this module would also prevent roaming to an AP that does not clear the authmode threshold set by wifi_config_t.sta.
4) As a general guideline for configuring the values of the roaming app it would be helpful to understand the trade-off between roaming and general performance of the overall application. Roaming includes processes such as scanning, disconnecting, connecting, etc which take up time away from other tasks of application. Deciding the several configurations in this module needs to be done while prioritising between being connected to the best AP, seamless transfer between APs and performance based on the requirements of the end application. For example, setting the thresholds of processes outlined above to a lower value or reducing the intervals between the aforementioned periodic tasks would make the roaming more aggressive while interrupting the general operation of the whole application more frequently.

View File

@ -0,0 +1,822 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <inttypes.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_wifi.h"
#include "esp_wnm.h"
#include "esp_rrm.h"
#include "esp_mbo.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_mac.h"
#include "nvs_flash.h"
#include <sys/time.h>
#include "regex.h"
#include <stdio.h>
#include "esp_roaming.h"
#include "utils/common.h"
#include "esp_wifi_driver.h"
#include "utils/eloop.h"
#include "rom/ets_sys.h"
#include "common/ieee802_11_defs.h"
static struct roaming_app g_roaming_app;
typedef void (* scan_done_cb_t)(void *arg, ETS_STATUS status);
extern int esp_wifi_promiscuous_scan_start(wifi_scan_config_t *config, scan_done_cb_t cb);
static void *scan_results_lock = NULL;
#define ROAM_SCAN_RESULTS_LOCK() os_mutex_lock(scan_results_lock)
#define ROAM_SCAN_RESULTS_UNLOCK() os_mutex_unlock(scan_results_lock)
static void *neighbor_list_lock = NULL;
#define ROAM_NEIGHBOR_LIST_LOCK() os_mutex_lock(neighbor_list_lock)
#define ROAM_NEIGHBOR_LIST_UNLOCK() os_mutex_unlock(neighbor_list_lock)
static int wifi_post_roam_event(struct cand_bss *bss);
static void determine_best_ap(int8_t rssi_threshold);
static const char *ROAMING_TAG = "ROAM";
static inline long time_diff_sec(struct timeval *a, struct timeval *b)
{
return (a->tv_sec - b->tv_sec);
}
static void roaming_app_get_ap_info(wifi_ap_record_t *ap_info)
{
esp_wifi_sta_get_ap_info(ap_info);
#if LOW_RSSI_ROAMING_ENABLED
/*
* If the current rssi is below the configured rssi threshold for
* low rssi based roaming and the current rssi threshold is below that,
* we should reset the rssi threshold back to the configured rssi threshold */
if ((ap_info->rssi > ROAMING_LOW_RSSI_THRESHOLD) && (g_roaming_app.current_low_rssi_threshold < ROAMING_LOW_RSSI_THRESHOLD)) {
g_roaming_app.current_low_rssi_threshold = ROAMING_LOW_RSSI_THRESHOLD;
esp_wifi_set_rssi_threshold(ROAMING_LOW_RSSI_THRESHOLD);
ESP_LOGD(ROAMING_TAG, "Reset the low rssi threshold back to %d", ROAMING_LOW_RSSI_THRESHOLD);
}
#endif /*LOW_RSSI_ROAMING_ENABLED*/
}
#if LEGACY_ROAM_ENABLED
static void legacy_roam_clear_bssid_flag(void)
{
wifi_config_t *config = {0};
config = os_zalloc(sizeof(wifi_config_t));
if (!config) {
ESP_LOGE(ROAMING_TAG, "failed to allocate memory");
return;
}
esp_wifi_get_config(WIFI_IF_STA, config);
if (config->sta.bssid_set) {
config->sta.bssid_set = 0;
esp_wifi_set_config(WIFI_IF_STA, config);
}
os_free(config);
ESP_LOGD(ROAMING_TAG, "cleared bssid flag");
}
#endif /*LEGACY_ROAM_ENABLED*/
static int8_t initialize_roaming_event(void)
{
#if LEGACY_ROAM_ENABLED
/*
* Resetting the Bssid param as it is possible that a previous force
* roam has set config to connect to a specific bssid and now further
* roaming attempts using BTM could lead to a spiral of connecting to
* the previous AP */
if (g_roaming_app.force_roam_ongoing) {
legacy_roam_clear_bssid_flag();
}
#endif /*LEGACY_ROAM_ENABLED*/
return ESP_OK;
}
#if PERIODIC_RRM_MONITORING
static void init_periodic_rrm_event(void)
{
if (!neighbor_list_lock) {
neighbor_list_lock = os_recursive_mutex_create();
if (!neighbor_list_lock) {
ESP_LOGE(ROAMING_TAG, "%s: failed to create roaming neighbor list lock", __func__);
}
}
ESP_LOGV(ROAMING_TAG, "Initialised Periodic RRM Monitoring event!");
g_roaming_app.periodic_rrm_active = true;
if (eloop_register_timeout(RRM_MONITOR_TIME, 0, roaming_app_periodic_rrm_internal_handler, NULL, NULL)) {
ESP_LOGE(ROAMING_TAG, "Could not register periodic neighbor report event.");
}
}
#endif /*PERIODIC_RRM_MONITORING*/
#if PERIODIC_SCAN_MONITORING
static void init_periodic_scan_roam_event(void)
{
ESP_LOGV(ROAMING_TAG, "Initialised Periodic Scan Roam event!");
g_roaming_app.periodic_scan_active = true;
if (eloop_register_timeout(SCAN_MONITOR_INTERVAL, 0, roaming_app_periodic_scan_internal_handler, NULL, NULL)) {
ESP_LOGE(ROAMING_TAG, "Could not register periodic scan monitoring event");
}
}
#endif /*PERIODIC_SCAN_ROAM_MONITORING*/
static void roaming_app_disconnected_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
#if PERIODIC_RRM_MONITORING
g_roaming_app.periodic_rrm_active = false;
#endif /*PERIODIC_RRM_MONITORING*/
#if PERIODIC_SCAN_MONITORING
g_roaming_app.periodic_scan_active = false;
#endif /*PERIODIC_SCAN_MONITORING*/
wifi_event_sta_disconnected_t *disconn = event_data;
ESP_LOGD(ROAMING_TAG, "station got disconnected reason=%d", disconn->reason);
if (disconn->reason == WIFI_REASON_ROAMING) {
ESP_LOGD(ROAMING_TAG, "station roaming, do nothing");
} else {
#if LEGACY_ROAM_ENABLED
/*
* Resetting the Bssid param as it is possible that a previous force
* roam has set config to connect to a specific bssid and now further
* roaming attempts using BTM could lead to a spiral of connecting to
* the previous AP */
if (g_roaming_app.force_roam_ongoing) {
legacy_roam_clear_bssid_flag();
}
#endif /*LEGACY_ROAM_ENABLED*/
esp_wifi_connect();
}
}
static void roaming_app_connected_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
#if LOW_RSSI_ROAMING_ENABLED
roaming_app_get_ap_info(&g_roaming_app.ap_info);
g_roaming_app.scan_params.ssid = g_roaming_app.ap_info.ssid;
if (g_roaming_app.ap_info.rssi < ROAMING_LOW_RSSI_THRESHOLD) {
/* To ensure that the threshold is set to one offset below the current AP RSSI
* in case, the AP is already below the RSSI threshold */
g_roaming_app.current_low_rssi_threshold = g_roaming_app.ap_info.rssi - RSSI_THRESHOLD_REDUCTION_OFFSET;
} else {
g_roaming_app.current_low_rssi_threshold = ROAMING_LOW_RSSI_THRESHOLD;
}
ESP_LOGD(ROAMING_TAG, "setting rssi threshold as %d", g_roaming_app.current_low_rssi_threshold);
esp_wifi_set_rssi_threshold(g_roaming_app.current_low_rssi_threshold);
#endif /*LOW_RSSI_ROAMING_ENABLED*/
g_roaming_app.rrm_support = esp_rrm_is_rrm_supported_connection();
g_roaming_app.btm_support = esp_wnm_is_btm_supported_connection();
ESP_LOGD(ROAMING_TAG, "Station connected, RRM %ssupported, BTM %ssupported",
g_roaming_app.rrm_support ? " " : "not ",
g_roaming_app.btm_support ? " " : "not ");
gettimeofday(&g_roaming_app.last_roamed_time, NULL);
if (!initialize_roaming_event()) {
#if PERIODIC_RRM_MONITORING
if (g_roaming_app.rrm_support) {
init_periodic_rrm_event();
}
#endif /*PERIODIC_RRM_MONITORING*/
#if PERIODIC_SCAN_MONITORING
init_periodic_scan_roam_event();
#endif /*PERIODIC_SCAN_ROAM_MONITORING*/
ESP_LOGD(ROAMING_TAG, "Initialised initialise roaming events!");
} else {
ESP_LOGE(ROAMING_TAG, "Failed to Initialise roaming events");
}
#if LEGACY_ROAM_ENABLED
g_roaming_app.force_roam_ongoing = true;
#endif /*LEGACY_ROAM_ENABLED*/
}
#define MAX_NEIGHBOR_LEN 512
#if PERIODIC_RRM_MONITORING
static char * get_btm_neighbor_list(uint8_t *report, size_t report_len)
{
size_t len = 0;
const uint8_t *data;
int ret = 0;
/*
* Neighbor Report element (IEEE P802.11-REVmc/D5.0)
* BSSID[6]
* BSSID Information[4]
* Operating Class[1]
* Channel Number[1]
* PHY Type[1]
* Optional Subelements[variable]
*/
#define NR_IE_MIN_LEN (ETH_ALEN + 4 + 1 + 1 + 1)
if (!report || report_len == 0) {
ESP_LOGE(ROAMING_TAG, "RRM neighbor report is not valid");
return NULL;
}
char *buf = os_calloc(1, MAX_NEIGHBOR_LEN);
if (!buf) {
ESP_LOGE(ROAMING_TAG, "get_btm_list : Memory alloc failed for buf");
return NULL;
}
data = report;
while (report_len >= 2 + NR_IE_MIN_LEN) {
const uint8_t *nr;
uint8_t nr_len = data[1];
const uint8_t *pos = data, *end;
if (pos[0] != WLAN_EID_NEIGHBOR_REPORT ||
nr_len < NR_IE_MIN_LEN) {
ESP_LOGD(ROAMING_TAG, "CTRL: Invalid Neighbor Report element: id=%u len=%u",
data[0], nr_len);
ret = -1;
goto cleanup;
}
if (2U + nr_len > report_len) {
ESP_LOGD(ROAMING_TAG, "CTRL: Invalid Neighbor Report element: id=%u len=%zu nr_len=%u",
data[0], report_len, nr_len);
ret = -1;
goto cleanup;
}
pos += 2;
end = pos + nr_len;
nr = pos;
pos += NR_IE_MIN_LEN;
while (end - pos > 2) {
uint8_t s_len;
s_len = *pos++;
if (s_len > end - pos) {
ret = -1;
goto cleanup;
}
pos += s_len;
}
ESP_LOGD(ROAMING_TAG, "RMM neighbor report bssid=" MACSTR
" info=0x%" PRIx32 " op_class=%u chan=%u phy_type=%u",
MAC2STR(nr), WPA_GET_LE32(nr + ETH_ALEN),
nr[ETH_ALEN + 4], nr[ETH_ALEN + 5],
nr[ETH_ALEN + 6]);
/* neighbor start */
len += snprintf(buf + len, MAX_NEIGHBOR_LEN - len, " neighbor=");
/* bssid */
len += snprintf(buf + len, MAX_NEIGHBOR_LEN - len, MACSTR, MAC2STR(nr));
/* , */
len += snprintf(buf + len, MAX_NEIGHBOR_LEN - len, ",");
/* bssid info */
len += snprintf(buf + len, MAX_NEIGHBOR_LEN - len, "0x%04" PRIx32 "", WPA_GET_LE32(nr + ETH_ALEN));
len += snprintf(buf + len, MAX_NEIGHBOR_LEN - len, ",");
/* operating class */
len += snprintf(buf + len, MAX_NEIGHBOR_LEN - len, "%u", nr[ETH_ALEN + 4]);
len += snprintf(buf + len, MAX_NEIGHBOR_LEN - len, ",");
/* channel number */
len += snprintf(buf + len, MAX_NEIGHBOR_LEN - len, "%u", nr[ETH_ALEN + 5]);
len += snprintf(buf + len, MAX_NEIGHBOR_LEN - len, ",");
/* phy type */
len += snprintf(buf + len, MAX_NEIGHBOR_LEN - len, "%u", nr[ETH_ALEN + 6]);
/* optional elements, skip */
data = end;
report_len -= 2 + nr_len;
}
cleanup:
if (ret < 0) {
os_free(buf);
buf = NULL;
}
return buf;
}
static void roaming_app_neighbor_report_recv_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
{
if (!g_roaming_app.rrm_request_active) {
ESP_LOGV(ROAMING_TAG, "Not the response for our Neighbor Report Request");
return;
}
g_roaming_app.rrm_request_active = false;
if (!event_data) {
ESP_LOGE(ROAMING_TAG, "No data received for neighbor report");
return;
}
wifi_event_neighbor_report_t *neighbor_report_event = (wifi_event_neighbor_report_t*)event_data;
ESP_LOGD(ROAMING_TAG, "Received cb for Neighbor Report Request");
uint8_t *pos = (uint8_t *)neighbor_report_event->report;
if (!pos) {
ESP_LOGE(ROAMING_TAG, "Neighbor report is empty");
return;
}
uint8_t report_len = neighbor_report_event->report_len;
/* dump report info */
ESP_LOGD(ROAMING_TAG, "rrm: neighbor report len=%d", report_len);
ESP_LOG_BUFFER_HEXDUMP(ROAMING_TAG, pos, report_len, ESP_LOG_DEBUG);
ROAM_NEIGHBOR_LIST_LOCK();
if (g_roaming_app.btm_neighbor_list) {
os_free(g_roaming_app.btm_neighbor_list);
g_roaming_app.btm_neighbor_list = NULL;
}
/* create neighbor list */
g_roaming_app.btm_neighbor_list = get_btm_neighbor_list(pos + 1, report_len - 1);
ROAM_NEIGHBOR_LIST_UNLOCK();
}
#endif /*PERIODIC_RRM_MONITORING*/
#if LOW_RSSI_ROAMING_ENABLED
static void roaming_app_rssi_low_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
{
wifi_event_bss_rssi_low_t *event = event_data;
ESP_LOGI(ROAMING_TAG, "%s:bss rssi is=%ld", __func__, event->rssi);
roaming_app_get_ap_info(&g_roaming_app.ap_info);
determine_best_ap(0);
g_roaming_app.current_low_rssi_threshold -= RSSI_THRESHOLD_REDUCTION_OFFSET;
ESP_LOGD(ROAMING_TAG, "Resetting RSSI Threshold to %d", g_roaming_app.current_low_rssi_threshold);
esp_wifi_set_rssi_threshold(g_roaming_app.current_low_rssi_threshold);
}
#endif
#if NETWORK_ASSISTED_ROAMING_ENABLED
static void trigger_network_assisted_roam(void)
{
#if PERIODIC_RRM_MONITORING
ROAM_NEIGHBOR_LIST_LOCK();
#endif /*PERIODIC_RRM_MONITORING*/
if (esp_wnm_send_bss_transition_mgmt_query(REASON_RSSI, g_roaming_app.btm_neighbor_list, 1) < 0) {
ESP_LOGD(ROAMING_TAG, "failed to send btm query");
}
#if PERIODIC_RRM_MONITORING
ROAM_NEIGHBOR_LIST_UNLOCK();
#endif /*PERIODIC_RRM_MONITORING*/
ESP_LOGD(ROAMING_TAG, "Sent BTM Query");
gettimeofday(&g_roaming_app.last_roamed_time, NULL);
g_roaming_app.btm_attempt++;
}
#endif /*NETWORK_ASSISTED_ROAMING*/
#if LEGACY_ROAM_ENABLED
static void trigger_legacy_roam(struct cand_bss *bss)
{
wifi_config_t wifi_cfg = {0};
esp_wifi_get_config(WIFI_IF_STA, &wifi_cfg);
wifi_cfg.sta.channel = bss->channel;
wifi_cfg.sta.bssid_set = true;
os_memcpy(wifi_cfg.sta.bssid, bss->bssid, ETH_ALEN);
esp_wifi_internal_issue_disconnect(WIFI_REASON_BSS_TRANSITION_DISASSOC);
esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg);
esp_wifi_connect();
ESP_LOGI(ROAMING_TAG, "Disconnecting and connecting to "MACSTR" on account of better rssi",MAC2STR(bss->bssid));
gettimeofday(&g_roaming_app.last_roamed_time, NULL);
g_roaming_app.force_roam_ongoing = true;
}
#endif /*LEGACY_ROAM_ENABLED*/
void roaming_app_trigger_roam(struct cand_bss *bss)
{
struct timeval now;
gettimeofday(&now, NULL);
ESP_LOGD(ROAMING_TAG,"Processing trigger roaming request.");
if (time_diff_sec(&now, &g_roaming_app.last_roamed_time) < ROAMING_BACKOFF_TIME ) {
ESP_LOGD(ROAMING_TAG,"Ignoring request as time difference to last request is %ld",time_diff_sec(&now, &g_roaming_app.last_roamed_time));
goto free_bss;
}
#if NETWORK_ASSISTED_ROAMING_ENABLED
if (g_roaming_app.btm_support) {
#if LEGACY_ROAM_ENABLED && NETWORK_ASSISTED_ROAMING_ENABLED
if (g_roaming_app.btm_attempt <= BSS_TM_RETRY_COUNT) {
#endif
trigger_network_assisted_roam();
goto free_bss;
#if LEGACY_ROAM_ENABLED && NETWORK_ASSISTED_ROAMING_ENABLED
} else {
ESP_LOGD(ROAMING_TAG, "Not Sending BTM query as this method has failed too many times.");
g_roaming_app.btm_attempt = 0;
}
#endif
}
#endif /*NETWORK_ASSISTED_ROAMING_ENABLED*/
#if LEGACY_ROAM_ENABLED
trigger_legacy_roam(bss);
#endif /*LEGACY_ROAM_ENABLED*/
free_bss :
os_free(bss);
}
void roaming_app_trigger_roam_internal_handler(void *ctx, void *data)
{
if (!data) {
ESP_LOGE(ROAMING_TAG, "No data received for roaming event");
} else {
roaming_app_trigger_roam((struct cand_bss *)data);
}
}
static int wifi_post_roam_event(struct cand_bss *bss)
{
if (bss) {
struct cand_bss *cand_bss = (struct cand_bss *)os_zalloc(sizeof(struct cand_bss));
if (!cand_bss) {
ESP_LOGE(ROAMING_TAG, "Cannot allocate data for roaming event");
return -1;
}
os_memcpy(cand_bss, bss, sizeof(struct cand_bss));
/* trigger the roaming event */
if (eloop_register_timeout(0, 0, roaming_app_trigger_roam_internal_handler, NULL, (void*)cand_bss)) {
ESP_LOGE(ROAMING_TAG, "Could not register roaming event.");
os_free(cand_bss);
return -1;
}
} else {
ESP_LOGE(ROAMING_TAG, "Cannot trigger roaming event without any candidate APs");
return -1;
}
return 0;
}
void print_ap_records(struct scanned_ap_info *ap_info)
{
ESP_LOGD(ROAMING_TAG, "Scanned AP List");
for (int i = 0; i < ap_info->current_count ; i++) {
ESP_LOGD(ROAMING_TAG, "%d. ssid : %s bssid :"MACSTR" channel : %d rssi : %d authmode : %d", i,
ap_info->ap_records[i].ssid,MAC2STR(ap_info->ap_records[i].bssid),
ap_info->ap_records[i].primary,ap_info->ap_records[i].rssi, ap_info->ap_records[i].authmode);
}
}
#if PERIODIC_RRM_MONITORING
static void periodic_rrm_request(struct timeval *now)
{
roaming_app_get_ap_info(&g_roaming_app.ap_info);
if (esp_rrm_is_rrm_supported_connection() && (g_roaming_app.ap_info.rssi < RRM_MONITOR_RSSI_THRESHOLD)) {
if (esp_rrm_send_neighbor_report_request() < 0) {
ESP_LOGE(ROAMING_TAG, "failed to send neighbor report request");
return;
}
g_roaming_app.rrm_request_active = true;
}
}
#else
static void periodic_rrm_request(struct timeval *now) { }
#endif
static bool candidate_security_match(wifi_ap_record_t candidate)
{
wifi_auth_mode_t curr_auth = g_roaming_app.ap_info.authmode;
wifi_auth_mode_t cand_auth = candidate.authmode;
ESP_LOGV(ROAMING_TAG, "Cand authmode : %d, Current Authmode : %d", cand_auth, curr_auth);
if (cand_auth == curr_auth) {
ESP_LOGV(ROAMING_TAG, "Authmode matched!");
return true;
}
wifi_config_t wifi_cfg = {0};
esp_wifi_get_config(WIFI_IF_STA, &wifi_cfg);
if (wifi_cfg.sta.owe_enabled && OWE_COMPATIBLE(curr_auth, cand_auth)) {
/*
* OWE <--> Open allowed if threshold is Open
*/
if (wifi_cfg.sta.threshold.authmode == WIFI_AUTH_OPEN) {
ESP_LOGV(ROAMING_TAG, "transition between OWE and open permitted");
return true;
} else {
ESP_LOGV(ROAMING_TAG, "transition between OWE and open not permitted");
return false;
}
} else if (wifi_cfg.sta.threshold.authmode > cand_auth) {
/* If the authmode of the candidate AP is less than our threshold, it
* will fail during connection*/
ESP_LOGV(ROAMING_TAG, "Authmode threshold failure %d -> %d", wifi_cfg.sta.threshold.authmode, cand_auth);
return false;
} else if (PSK_COMPATIBLE(curr_auth, cand_auth)) {
/*
* PSK based authmodes are compatible with each other for roaming
*/
ESP_LOGV(ROAMING_TAG, "Roaming between a PSK APs");
return true;
}
return false;
}
static bool candidate_profile_match(wifi_ap_record_t candidate)
{
return candidate_security_match(candidate);
}
/* Remember to always call this function with the ROAM_SCAN_RESULTS_LOCK */
static void parse_scan_results_and_roam(void)
{
int8_t rssi_threshold = g_roaming_app.current_rssi_threshold;
uint8_t best_rssi_diff = rssi_threshold;
struct cand_bss *best_ap = NULL;
int8_t rssi_diff = 0;
uint8_t i;
int8_t best_ap_index = -1;
wifi_ap_record_t ap_info;
roaming_app_get_ap_info(&ap_info);
for (i = 0; i < g_roaming_app.scanned_aps.current_count; i++) {
rssi_diff = g_roaming_app.scanned_aps.ap_records[i].rssi - ap_info.rssi;
ESP_LOGD(ROAMING_TAG, "The difference between ("MACSTR", "MACSTR") with rssi (%d,%d) is : %d while the threshold is %d and the best rssi diff yet is %d, thecand_auth is %d",
MAC2STR(g_roaming_app.scanned_aps.ap_records[i].bssid),MAC2STR(ap_info.bssid),
g_roaming_app.scanned_aps.ap_records[i].rssi, ap_info.rssi,
rssi_diff, rssi_threshold, best_rssi_diff, g_roaming_app.scanned_aps.ap_records[i].authmode);
if ((memcmp(g_roaming_app.scanned_aps.ap_records[i].bssid, ap_info.bssid, ETH_ALEN) != 0) &&
candidate_profile_match(g_roaming_app.scanned_aps.ap_records[i]) && rssi_diff > best_rssi_diff ) {
best_rssi_diff = rssi_diff;
best_ap_index = i;
}
}
if (best_ap_index >= 0) {
best_ap = (struct cand_bss*)os_zalloc(sizeof(struct cand_bss));
if (!best_ap) {
ESP_LOGE(ROAMING_TAG,"Memory Allocation for candidate bss failed");
return;
}
os_memcpy(best_ap->bssid,g_roaming_app.scanned_aps.ap_records[best_ap_index].bssid , ETH_ALEN);
best_ap->channel = g_roaming_app.scanned_aps.ap_records[best_ap_index].primary;
}
if (best_ap) {
ESP_LOGI(ROAMING_TAG,"Found a better AP "MACSTR" at channel %d", MAC2STR(best_ap->bssid), best_ap->channel);
if (wifi_post_roam_event(best_ap)) {
ESP_LOGE(ROAMING_TAG, "Posting of roaming event failed");
}
os_free(best_ap);
} else {
ESP_LOGI(ROAMING_TAG, "Could not find a better AP with the threshold set to %d", g_roaming_app.current_rssi_threshold + 1);
}
}
static void scan_done_event_handler(void *arg, ETS_STATUS status)
{
if (status == ETS_OK) {
ROAM_SCAN_RESULTS_LOCK();
ESP_LOGD(ROAMING_TAG, "Scan Done properly");
g_roaming_app.scanned_aps.current_count = MAX_CANDIDATE_COUNT;
esp_wifi_scan_get_ap_records(&g_roaming_app.scanned_aps.current_count, g_roaming_app.scanned_aps.ap_records);
print_ap_records(&g_roaming_app.scanned_aps);
parse_scan_results_and_roam();
g_roaming_app.scan_ongoing = false;
ROAM_SCAN_RESULTS_UNLOCK();
} else {
ESP_LOGD(ROAMING_TAG, "Scan Done with error %d ", status);
}
}
static void conduct_scan(void)
{
/* Update scan time in global structure */
gettimeofday(&g_roaming_app.scanned_aps.time, NULL);
/* Issue scan */
os_memset(&g_roaming_app.scanned_aps, 0, sizeof(struct scanned_ap_info));
if (esp_wifi_promiscuous_scan_start(&g_roaming_app.scan_params, scan_done_event_handler) < 0) {
ESP_LOGE(ROAMING_TAG, "failed to issue scan");
return;
}
ESP_LOGI(ROAMING_TAG, "Issued Scan");
}
static void determine_best_ap(int8_t rssi_threshold)
{
struct timeval now;
gettimeofday(&now, NULL);
ROAM_SCAN_RESULTS_LOCK();
/* If the scan results are recent enough or a scan is already ongoing we should not trigger a new scan */
if (!g_roaming_app.scan_ongoing) {
g_roaming_app.scan_ongoing = true;
g_roaming_app.current_rssi_threshold = rssi_threshold;
if (time_diff_sec(&now,&g_roaming_app.scanned_aps.time) > SCAN_RESULTS_USABILITY_WINDOW) {
conduct_scan();
} else {
parse_scan_results_and_roam();
g_roaming_app.scan_ongoing = false;
}
} else if(rssi_threshold < g_roaming_app.current_rssi_threshold) {
g_roaming_app.current_rssi_threshold = rssi_threshold;
}
ROAM_SCAN_RESULTS_UNLOCK();
}
#if PERIODIC_SCAN_MONITORING
static void periodic_scan_roam(struct timeval *now)
{
#if NETWORK_ASSISTED_ROAMING_ENABLED && !LEGACY_ROAM_ENABLED
/*
* In case we support only network assisted roaming, there is no need to scan
* if we currently have over 10secs to go for backoff time to expire
* as the results produced by a scan at this time would not be used by
* supplicant to build candidate lists.
* */
if (time_diff_sec(now, &g_roaming_app.last_roamed_time) < ROAMING_BACKOFF_TIME - SUPPLICANT_CANDIDATE_LIST_EXPIRY) {
return;
}
#endif /*NETWORK_ASSISTED_ROAMING_ENABLED && !LEGACY_ROAM_ENABLED*/
/* If the current RSSI is not worse than the configured threshold
* for station initiated roam, then do not trigger roam */
roaming_app_get_ap_info(&g_roaming_app.ap_info);
if (g_roaming_app.ap_info.rssi > SCAN_MONITOR_RSSI_THRESHOLD) {
return;
}
determine_best_ap(SCAN_ROAM_RSSI_DIFF - 1);
}
#endif /*PERIODIC_SCAN_MONITORING*/
#if PERIODIC_RRM_MONITORING
void roaming_app_periodic_rrm_internal_handler(void *data, void *ctx)
{
struct timeval now;
if (g_roaming_app.periodic_rrm_active) {
ESP_LOGD(ROAMING_TAG,"Triggered periodic rrm event!");
gettimeofday(&now, NULL);
/* This will be done every RRM_MONITOR_TIME */
periodic_rrm_request(&now);
if (eloop_register_timeout(RRM_MONITOR_TIME, 0, roaming_app_periodic_rrm_internal_handler, NULL, NULL)) {
ESP_LOGE(ROAMING_TAG,"Could not register periodic neighbor report request event.");
}
}
}
#endif /*PERIODIC_RRM_MONITORING*/
#if PERIODIC_SCAN_MONITORING
void roaming_app_periodic_scan_internal_handler(void *data, void *ctx)
{
struct timeval now;
if (g_roaming_app.periodic_scan_active) {
ESP_LOGD(ROAMING_TAG,"Started the periodic scan roam event!");
gettimeofday(&now, NULL);
/* This will be done every SCAN_MONITOR_INTERVAL */
periodic_scan_roam(&now);
if (eloop_register_timeout(SCAN_MONITOR_INTERVAL, 0, roaming_app_periodic_scan_internal_handler, NULL, NULL)) {
ESP_LOGE(ROAMING_TAG,"Could not register periodic scan event.");
}
}
}
#endif /*PERIODIC_SCAN_ROAM_MONITORING*/
static bool validate_scan_chan_list(const char* scan_chan_list)
{
regex_t regex;
const char* pattern = "^[0-9]+(,[0-9]+)*$";
uint8_t ret = regcomp(&regex, pattern, REG_EXTENDED);
if (ret != 0) {
ESP_LOGE(ROAMING_TAG, "Regex compilation failed.");
return false;
}
ret = regexec(&regex, scan_chan_list, 0, NULL, 0);
regfree(&regex);
if (ret == REG_NOMATCH) {
return false;
}
/* Check for consecutive commas or comma at start/end */
if (strstr(scan_chan_list, ",,") != NULL || scan_chan_list[0] == ',' || scan_chan_list[strlen(scan_chan_list) - 1] == ',') {
return false;
}
return true;
}
static int8_t parse_scan_chan_list(void)
{
int8_t ret = 0;
char *scan_chan_string = NULL;
if (validate_scan_chan_list(SCAN_PREFERRED_CHAN_LIST) == false) {
ESP_LOGE(ROAMING_TAG, "scan chan list validation failed.");
ret = -1;
goto cleanup;
}
scan_chan_string = (char *)os_zalloc(sizeof(char) * strlen(SCAN_PREFERRED_CHAN_LIST) + 1);
if (scan_chan_string == NULL) {
ESP_LOGE(ROAMING_TAG, "Memory allocation failed.");
ret = -1;
goto cleanup;
}
strlcpy(scan_chan_string, SCAN_PREFERRED_CHAN_LIST, strlen(SCAN_PREFERRED_CHAN_LIST) + 1);
char* token;
token = strsep(&scan_chan_string, ",");
g_roaming_app.scan_params.channel_bitmap.ghz_2_channels = 0;
while (token != NULL) {
uint8_t channel = atoi(token);
/* Check if the number is within the required range */
if (channel >= 1 && channel <= 14) {
/* Check if the number is already present in the array */
g_roaming_app.scan_params.channel_bitmap.ghz_2_channels |= (1 << channel);
} else {
ESP_LOGE(ROAMING_TAG, "Channel out of range: %d", channel);
ret = -1;
goto cleanup;
}
token = strsep(&scan_chan_string, ",");
}
cleanup:
if (scan_chan_string) {
os_free(scan_chan_string);
}
return ret;
}
esp_err_t init_scan_params(void)
{
if (!scan_results_lock) {
scan_results_lock = os_recursive_mutex_create();
if (!scan_results_lock) {
ESP_LOGE(ROAMING_TAG, "%s: failed to create scan results lock", __func__);
return ESP_FAIL;
}
}
if (strcmp(DEFAULT_PREFERRED_SCAN_CHAN_LIST, SCAN_PREFERRED_CHAN_LIST)) {
ESP_ERROR_CHECK(parse_scan_chan_list());
}
g_roaming_app.scan_params.scan_type = 0;
g_roaming_app.scan_params.scan_time.active.min = SCAN_TIME_MIN_DURATION;
g_roaming_app.scan_params.scan_time.active.max = SCAN_TIME_MAX_DURATION;
g_roaming_app.scan_params.home_chan_dwell_time = HOME_CHANNEL_DWELL_TIME;
gettimeofday(&g_roaming_app.scanned_aps.time, NULL);
return ESP_OK;
}
void init_roaming_app(void)
{
#if !LOW_RSSI_ROAMING_ENABLED && !PERIODIC_SCAN_MONITORING
ESP_LOGE(ROAMING_TAG, "No roaming method enabled. Roaming app cannot be initialized");
return;
#endif
#if !NETWORK_ASSISTED_ROAMING_ENABLED && !LEGACY_ROAM_ENABLED
ESP_LOGE(ROAMING_TAG, "No roaming method enabled. Roaming app cannot be initialized");
return;
#endif
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_CONNECTED, roaming_app_connected_event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, roaming_app_disconnected_event_handler, NULL));
#if LOW_RSSI_ROAMING_ENABLED
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_BSS_RSSI_LOW,
&roaming_app_rssi_low_handler, NULL));
#endif /*ROAMING_LOW_RSSI_THRESHOLD*/
ESP_ERROR_CHECK(init_scan_params());
#if PERIODIC_RRM_MONITORING
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_NEIGHBOR_REP,
&roaming_app_neighbor_report_recv_handler, NULL));
#endif /*PERIODIC_RRM_MONITORING*/
ESP_LOGI(ROAMING_TAG, "Roaming app initialization done");
}
void deinit_roaming_app(void)
{
#if !LOW_RSSI_ROAMING_ENABLED && !PERIODIC_SCAN_MONITORING
ESP_LOGE(ROAMING_TAG, "No roaming trigger enabled. Roaming app cannot be de-initialized");
return;
#endif
#if !NETWORK_ASSISTED_ROAMING_ENABLED && !LEGACY_ROAM_ENABLED
ESP_LOGE(ROAMING_TAG, "No roaming trigger enabled. Roaming app cannot be de-initialized");
return;
#endif
/* Unregister Event handlers */
ESP_ERROR_CHECK(esp_event_handler_unregister(WIFI_EVENT, WIFI_EVENT_STA_CONNECTED, roaming_app_connected_event_handler));
ESP_ERROR_CHECK(esp_event_handler_unregister(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, roaming_app_disconnected_event_handler));
#if LOW_RSSI_ROAMING_ENABLED
ESP_ERROR_CHECK(esp_event_handler_unregister(WIFI_EVENT, WIFI_EVENT_STA_BSS_RSSI_LOW,
&roaming_app_rssi_low_handler));
#endif /*ROAMING_LOW_RSSI_THRESHOLD*/
#if PERIODIC_RRM_MONITORING
ESP_ERROR_CHECK(esp_event_handler_unregister(WIFI_EVENT, WIFI_EVENT_STA_NEIGHBOR_REP,
&roaming_app_neighbor_report_recv_handler));
#endif /*PERIODIC_RRM_MONITORING*/
/* Disabling the periodic scan and RRM events */
#if PERIODIC_RRM_MONITORING
g_roaming_app.periodic_rrm_active = false;
#endif /*PERIODIC_RRM_MONITORING*/
#if PERIODIC_SCAN_MONITORING
g_roaming_app.periodic_scan_active = false;
#endif /*PERIODIC_SCAN_MONITORING*/
os_mutex_delete(neighbor_list_lock);
neighbor_list_lock = NULL;
os_mutex_delete(scan_results_lock);
scan_results_lock = NULL;
}

View File

@ -226,6 +226,7 @@ idf_component_register(SRCS "${srcs}" "${esp_srcs}" "${tls_src}" "${roaming_src}
"${crypto_src}" "${mbo_src}" "${dpp_src}" "${wps_registrar_src}"
INCLUDE_DIRS include port/include esp_supplicant/include
PRIV_INCLUDE_DIRS src src/utils esp_supplicant/src src/crypto
../esp_wifi/wifi_apps/roaming_app/include
LDFRAGMENTS ${linker_fragments}
PRIV_REQUIRES mbedtls esp_timer esp_wifi)

View File

@ -23,6 +23,9 @@
#include "rsn_supp/wpa_i.h"
#include "rsn_supp/wpa.h"
#include "esp_private/wifi.h"
#if CONFIG_ESP_WIFI_ENABLE_ROAMING_APP
#include "esp_roaming.h"
#endif
/* Utility Functions */
esp_err_t esp_supplicant_str_to_mac(const char *str, uint8_t dest[6])
@ -300,7 +303,7 @@ static int ieee80211_handle_rx_frm(u8 type, u8 *frame, size_t len, u8 *sender,
#ifdef CONFIG_MBO
bool mbo_bss_profile_match(u8 *bssid)
{
/* Incase supplicant wants drivers to skip this BSS, return false */
/* In case supplicant wants drivers to skip this BSS, return false */
struct wpa_bss *bss = wpa_bss_get_bssid(&g_wpa_supp, bssid);
if (!bss) {
return true;
@ -419,6 +422,9 @@ void esp_supplicant_common_deinit(void)
}
s_supplicant_task_init_done = false;
#endif /* CONFIG_SUPPLICANT_TASK */
#if CONFIG_ESP_WIFI_ENABLE_ROAMING_APP
deinit_roaming_app();
#endif
#endif /* defined(CONFIG_IEEE80211KV) || defined(CONFIG_IEEE80211R) */
}
@ -732,6 +738,16 @@ static uint8_t get_extended_caps_ie(uint8_t *ie, size_t len)
return ext_caps_ie_len + 2;
}
#else /* CONFIG_IEEE80211KV */
bool esp_rrm_is_rrm_supported_connection(void)
{
return false;
}
bool esp_wnm_is_btm_supported_connection(void)
{
return false;
}
#endif /* CONFIG_IEEE80211KV */
void esp_set_scan_ie(void)

View File

@ -39,6 +39,9 @@
#include "ap/sta_info.h"
#include "wps/wps_defs.h"
#include "wps/wps.h"
#if CONFIG_ESP_WIFI_ENABLE_ROAMING_APP
#include "esp_roaming.h"
#endif
#ifdef CONFIG_DPP
#include "common/dpp.h"
@ -469,6 +472,10 @@ int esp_supplicant_init(void)
ret = esp_wifi_internal_wapi_init();
#endif
#if CONFIG_ESP_WIFI_ENABLE_ROAMING_APP
init_roaming_app();
#endif
return ret;
}

View File

@ -219,7 +219,7 @@ INPUT = \
$(PROJECT_PATH)/components/wpa_supplicant/esp_supplicant/include/esp_rrm.h \
$(PROJECT_PATH)/components/wpa_supplicant/esp_supplicant/include/esp_wnm.h \
$(PROJECT_PATH)/components/wpa_supplicant/esp_supplicant/include/esp_wps.h \
$(PROJECT_PATH)/components/esp_wifi/wifi_apps/include/esp_nan.h \
$(PROJECT_PATH)/components/esp_wifi/wifi_apps/nan_app/include/esp_nan.h \
$(PROJECT_PATH)/components/esp-tls/esp_tls_errors.h \
$(PROJECT_PATH)/components/esp-tls/esp_tls.h \
$(PROJECT_PATH)/components/fatfs/diskio/diskio_impl.h \
@ -310,7 +310,7 @@ INPUT = \
$(PROJECT_PATH)/components/wpa_supplicant/esp_supplicant/include/esp_dpp.h \
$(PROJECT_PATH)/components/wpa_supplicant/esp_supplicant/include/esp_supplicant_utils.h \
## Target specific headers are in seperate Doxyfile files
## Target specific headers are in separate Doxyfile files
@INCLUDE = $(PROJECT_PATH)/docs/doxygen/Doxyfile_$(IDF_TARGET)
## Get warnings for functions that have no documentation for their parameters or return value