From ee72310e8f95a528b966245e3ddad5fba6acf067 Mon Sep 17 00:00:00 2001 From: "Deomid \"rojer\" Ryabkov" Date: Mon, 10 May 2021 06:36:15 +0100 Subject: [PATCH] STA scan and connection rewrite Implement our own AP selection algorithm, do not rely on SDK. The algorithm tries to connect to AP with best signal but doesn't get stuck on one (or a subset) of them. If there is an AP that can be connected to, it should eventually be found. Also implement roaming: if device is connected to an AP with RSII less than wifi.sta_roam_rssi_thr, it will periodically (wifi.sta_roam_interval) scan for a better AP and try to reconnect. AP must be at least 5 dbm better than the one we are connected to. Roaming is not seamless, i.e. connection is lost during attempt. Scanning also introduces blips, so if connection stability is more important, roaming should not be used. --- README.md | 4 +- include/mgos_wifi_hal.h | 9 +- include/mgos_wifi_sta.h | 32 ++ mos.yml | 13 +- src/cc32xx/cc32xx_wifi.c | 4 + src/esp32/esp32_wifi.c | 38 +-- src/esp8266/esp_wifi.c | 50 +-- src/mgos_wifi.c | 224 +------------ src/mgos_wifi_sta.c | 660 +++++++++++++++++++++++++++++++++++++++ 9 files changed, 772 insertions(+), 262 deletions(-) create mode 100644 include/mgos_wifi_sta.h create mode 100644 src/mgos_wifi_sta.c diff --git a/README.md b/README.md index da85cbe..52fae02 100644 --- a/README.md +++ b/README.md @@ -40,9 +40,7 @@ which contains configuration settings for the Access Point mode, and #### Multiple Station Configurations -Station configurations will be tried starting from `sta_cfg_idx` and each one that is enabled will be given `sta_connect_timeout` seconds to connect. Successfully connected station's index will be saved in `sta_cfg_idx` so next boot will start with previously used configuration. - -Setting `sta_connect_timeout` to 0 disables this logic. +Up to 3 different station configurations are allowed and those that are enabled will be considered for connection. ### Access Point configuration diff --git a/include/mgos_wifi_hal.h b/include/mgos_wifi_hal.h index 71a4f08..b646599 100644 --- a/include/mgos_wifi_hal.h +++ b/include/mgos_wifi_hal.h @@ -19,15 +19,14 @@ * HAL wifi interface, to be implemented by ports. */ -#ifndef CS_MOS_LIBS_WIFI_SRC_MGOS_WIFI_HAL_H_ -#define CS_MOS_LIBS_WIFI_SRC_MGOS_WIFI_HAL_H_ +#pragma once #include "mgos_net.h" #include "mgos_wifi.h" #ifdef __cplusplus extern "C" { -#endif /* __cplusplus */ +#endif bool mgos_wifi_dev_ap_setup(const struct mgos_config_wifi_ap *cfg); @@ -66,6 +65,4 @@ void mgos_wifi_dev_deinit(void); #ifdef __cplusplus } -#endif /* __cplusplus */ - -#endif /* CS_MOS_LIBS_WIFI_SRC_MGOS_WIFI_HAL_H_ */ +#endif diff --git a/include/mgos_wifi_sta.h b/include/mgos_wifi_sta.h new file mode 100644 index 0000000..af9ab2a --- /dev/null +++ b/include/mgos_wifi_sta.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) Mongoose OS Contributors + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "mgos_sys_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +bool mgos_wifi_sta_add_cfg(const struct mgos_config_wifi_sta *cfg); +void mgos_wifi_sta_clear_cfgs(void); +void mgos_wifi_sta_init(void); + +#ifdef __cplusplus +} +#endif diff --git a/mos.yml b/mos.yml index f8d48d4..65afde6 100644 --- a/mos.yml +++ b/mos.yml @@ -34,6 +34,7 @@ config_schema: - ["wifi.sta.enable", "b", {title: "Connect to existing WiFi"}] - ["wifi.sta.ssid", "s", {title: "SSID"}] - ["wifi.sta.pass", "s", {title: "Password", type: "password"}] + - ["wifi.sta.bssid", "s", {title: "Specific AP to connect to"}] - ["wifi.sta.user", "s", {title: "Username for WPA-PEAP mode"}] - ["wifi.sta.anon_identity", "s", {title: "Anonymous identity for WPA mode"}] - ["wifi.sta.cert", "s", {title: "Client certificate for WPA-TTLS mode"}] @@ -49,12 +50,10 @@ config_schema: - ["wifi.sta1.enable", false] - ["wifi.sta2", "wifi.sta", {title: "WiFi Station Config 2"}] - ["wifi.sta2.enable", false] - # Station configurations will be tried starting from sta_cfg_idx and each one that is enabled - # will be given sta_connect_timeout seconds to connect. Successfully connected station's index - # will be saved in sta_cfg_idx so next boot will start with previously used configuration. - # Setting sta_connect_timeout to 0 will disable this logic. - - ["wifi.sta_cfg_idx", "i", 0, {title: "WiFi station config index to use, 0, 1 or 2"}] - - ["wifi.sta_connect_timeout", "i", 30, {title: "Timeout for connection, seconds"}] + - ["wifi.sta_rssi_thr", "i", -95, {title: "Do not consider APs with weaker signal"}] + - ["wifi.sta_connect_timeout", "i", 15, {title: "Timeout for connection, seconds"}] + - ["wifi.sta_roam_rssi_thr", "i", -80, {title: "If connected to AP with weaker signal, try to find a better one."}] + - ["wifi.sta_roam_interval", "i", 900, {title: "Scan for better APs at this interval. Set to 0 to disable."}] build_vars: MGOS_WIFI_ENABLE_AP_STA: 0 @@ -84,7 +83,6 @@ conds: - origin: https://github.com/mongoose-os-libs/lwip config_schema: - ["wifi.ap.keep_enabled", "b", true, {title: "Keep AP enabled when station is on"}] - - ["wifi.sta_all_chan_scan", "b", true, {title: "true to scan all channels or false to perform fast scan"}] # min/max values are from enum RATE_11{B,G,N}_ID in the SDK, see user_interface.h for declarations. - ["wifi.tx_rate_limit_11b", "i", -1, {title: "TX rate limit for 11B mode, ((max << 8) | min)"}] - ["wifi.tx_rate_limit_11g", "i", -1, {title: "TX rate limit for 11G mode, ((max << 8) | min)"}] @@ -99,7 +97,6 @@ conds: - ["wifi.ap.bandwidth_20mhz", "b", false, {title: "enable 20MHz bandwidth AP operation"}] - ["wifi.ap.protocol", "s", "BGN", {title: "802.11 Wi-Fi Protocol for AP Mode, defaults to BGN, can be any combination of BGNLR. Note LR only works between 2 ESP32 devices."}] - ["wifi.sta_ps_mode", "i", 0, {title: "Power save mode for station: 0 - none, 1 - min, 2 - max."}] - - ["wifi.sta_all_chan_scan", "b", true, {title: "true to scan all channels or false to perform fast scan"}] - ["wifi.sta.protocol", "s", "BGN", {title: "802.11 Wi-Fi Protocol for STA Mode, defaults to BGN, can be any combination of BGNLR. Note LR only works between 2 ESP32 devices."}] - ["wifi.sta.listen_interval_ms", "i", 0, {title: "DTIM Listen Interval (ms)"}] cdefs: diff --git a/src/cc32xx/cc32xx_wifi.c b/src/cc32xx/cc32xx_wifi.c index 23350c8..46b5029 100644 --- a/src/cc32xx/cc32xx_wifi.c +++ b/src/cc32xx/cc32xx_wifi.c @@ -138,6 +138,7 @@ static bool ensure_role_sta(void) { if (s_current_role == ROLE_STA) return true; if (!restart_nwp(ROLE_STA)) return false; _u32 scan_interval = WIFI_SCAN_INTERVAL_SECONDS; + /* Setting scan policy also initiates scan. */ sl_WlanPolicySet(SL_WLAN_POLICY_SCAN, 1 /* enable */, (_u8 *) &scan_interval, sizeof(scan_interval)); return true; @@ -620,6 +621,9 @@ bool mgos_wifi_dev_start_scan(void) { if (!ensure_role_sta()) goto out; + /* TODO: Set scan policy to initate scan, right now we are getting + * results from a previous scan. */ + for (i = 0; (n = sl_WlanGetNetworkList(i, 2, info)) > 0; i += 2) { res = (struct mgos_wifi_scan_result *) realloc( res, (num_res + n) * sizeof(*res)); diff --git a/src/esp32/esp32_wifi.c b/src/esp32/esp32_wifi.c index 48fce37..126bfeb 100644 --- a/src/esp32/esp32_wifi.c +++ b/src/esp32/esp32_wifi.c @@ -304,8 +304,7 @@ static esp_err_t wifi_sta_set_host_name( bool mgos_wifi_dev_sta_setup(const struct mgos_config_wifi_sta *cfg) { bool result = false; esp_err_t r; - wifi_config_t wcfg; - memset(&wcfg, 0, sizeof(wcfg)); + wifi_config_t wcfg = {0}; wifi_sta_config_t *stacfg = &wcfg.sta; s_user_sta_enabled = cfg->enable; @@ -321,10 +320,21 @@ bool mgos_wifi_dev_sta_setup(const struct mgos_config_wifi_sta *cfg) { /* In case already connected, disconnect. */ esp_wifi_disconnect(); - stacfg->scan_method = - (wifi_scan_method_t) mgos_sys_config_get_wifi_sta_all_chan_scan(); - strncpy((char *) stacfg->ssid, cfg->ssid, sizeof(stacfg->ssid)); + + if (!mgos_conf_str_empty(cfg->bssid)) { + unsigned int bssid[6] = {0}; + if (sscanf(cfg->bssid, "%02x:%02x:%02x:%02x:%02x:%02x", &bssid[0], + &bssid[1], &bssid[2], &bssid[3], &bssid[4], &bssid[5]) != 6) { + LOG(LL_ERROR, ("Invalid BSSID!")); + return false; + } + for (int i = 0; i < 6; i++) { + stacfg->bssid[i] = bssid[i]; + } + stacfg->bssid_set = true; + } + if (mgos_conf_str_empty(cfg->user) /* Not using EAP */ && !mgos_conf_str_empty(cfg->pass)) { strncpy((char *) stacfg->password, cfg->pass, sizeof(stacfg->password)); @@ -618,23 +628,13 @@ int mgos_wifi_sta_get_rssi(void) { } bool mgos_wifi_dev_start_scan(void) { - esp_err_t r = ESP_OK; - wifi_mode_t cur_mode = esp32_wifi_get_mode(); - if (cur_mode != WIFI_MODE_STA && cur_mode != WIFI_MODE_APSTA) { - r = esp32_wifi_add_mode(WIFI_MODE_STA); - if (r == ESP_OK) r = esp32_wifi_ensure_start(); - } + esp_err_t r = esp32_wifi_add_mode(WIFI_MODE_STA); + if (r == ESP_OK) r = esp32_wifi_ensure_start(); if (r == ESP_OK) { wifi_scan_config_t scan_cfg = { .scan_type = WIFI_SCAN_TYPE_ACTIVE, - .scan_time = - { - .active = - { - .min = 150, - .max = 200, - }, - }, + .scan_time.active.min = 100, + .scan_time.active.max = 150, }; if (s_connecting) { esp_wifi_disconnect(); diff --git a/src/esp8266/esp_wifi.c b/src/esp8266/esp_wifi.c index b3988ac..ff3143f 100644 --- a/src/esp8266/esp_wifi.c +++ b/src/esp8266/esp_wifi.c @@ -236,13 +236,7 @@ static void esp_wifi_set_rate_limits(const struct mgos_config_wifi *cfg) { } bool mgos_wifi_dev_sta_setup(const struct mgos_config_wifi_sta *cfg) { - struct station_config sta_cfg = { -#if ESP_SDK_VERSION_MAJOR >= 3 - .all_channel_scan = mgos_sys_config_get_wifi_sta_all_chan_scan(), -#else - 0 -#endif - }; + struct station_config sta_cfg = {0}; if (!cfg->enable) { return mgos_wifi_remove_mode(STATION_MODE); @@ -254,7 +248,18 @@ bool mgos_wifi_dev_sta_setup(const struct mgos_config_wifi_sta *cfg) { if (!mgos_wifi_add_mode(STATION_MODE)) return false; - sta_cfg.bssid_set = 0; + if (cfg->bssid != NULL) { + unsigned int bssid[6] = {0}; + if (sscanf(cfg->bssid, "%02x:%02x:%02x:%02x:%02x:%02x", &bssid[0], + &bssid[1], &bssid[2], &bssid[3], &bssid[4], &bssid[5]) != 6) { + LOG(LL_ERROR, ("Invalid BSSID!")); + return false; + } + for (int i = 0; i < 6; i++) { + sta_cfg.bssid[i] = bssid[i]; + } + sta_cfg.bssid_set = true; + } strncpy((char *) sta_cfg.ssid, cfg->ssid, sizeof(sta_cfg.ssid)); if (!mgos_conf_str_empty(cfg->ip) && !mgos_conf_str_empty(cfg->netmask)) { @@ -470,19 +475,23 @@ void wifi_scan_done(void *arg, STATUS status) { mgos_wifi_dev_scan_cb(-1, NULL); return; } - STAILQ_HEAD(, bss_info) *info = arg; - struct mgos_wifi_scan_result *res = NULL; - struct bss_info *p; int n = 0; - STAILQ_FOREACH(p, info, next) n++; - res = calloc(n, sizeof(*res)); - if (n > 0 && res == NULL) { + struct bss_info *info = (struct bss_info *) arg; + for (struct bss_info *p = info; p != NULL; p = p->next.stqe_next) { + n++; + } + if (n == 0) { + mgos_wifi_dev_scan_cb(0, NULL); + return; + } + struct mgos_wifi_scan_result *res = calloc(n, sizeof(*res)); + if (res == NULL) { LOG(LL_ERROR, ("Out of memory")); mgos_wifi_dev_scan_cb(-1, NULL); return; } struct mgos_wifi_scan_result *r = res; - STAILQ_FOREACH(p, info, next) { + for (struct bss_info *p = info; p != NULL; p = p->next.stqe_next) { strncpy(r->ssid, (const char *) p->ssid, sizeof(r->ssid)); memcpy(r->bssid, p->bssid, sizeof(r->bssid)); r->ssid[sizeof(r->ssid) - 1] = '\0'; @@ -514,10 +523,13 @@ void wifi_scan_done(void *arg, STATUS status) { bool mgos_wifi_dev_start_scan(void) { /* Scanning requires station. If in AP-only mode, switch to AP+STA. */ - if (wifi_get_opmode() == SOFTAP_MODE) { - wifi_set_opmode_current(STATIONAP_MODE); - } - return wifi_station_scan(NULL, wifi_scan_done); + if (!mgos_wifi_add_mode(STATION_MODE)) return false; + struct scan_config cfg = { + .scan_type = WIFI_SCAN_TYPE_ACTIVE, + .scan_time.active.min = 100, + .scan_time.active.max = 150, + }; + return wifi_station_scan(&cfg, wifi_scan_done); } void mgos_wifi_dev_init(void) { diff --git a/src/mgos_wifi.c b/src/mgos_wifi.c index 1d8c974..777972b 100644 --- a/src/mgos_wifi.c +++ b/src/mgos_wifi.c @@ -34,7 +34,7 @@ #include "mongoose.h" -#define NUM_STA_CFG 3 +#include "mgos_wifi_sta.h" struct cb_info { void *cb; @@ -44,90 +44,36 @@ struct cb_info { static SLIST_HEAD(s_scan_cbs, cb_info) s_scan_cbs; static bool s_scan_in_progress = false; -enum mgos_wifi_status s_sta_status = MGOS_WIFI_DISCONNECTED; -static char *s_sta_ssid = NULL; -static int8_t s_sta_should_reconnect = 0; - struct mgos_rlock_type *s_wifi_lock = NULL; -static int s_cur_sta_cfg_idx = -1; -static struct mgos_config_wifi *s_cur_cfg = NULL; -static mgos_timer_id s_connect_timer_id = MGOS_INVALID_TIMER_ID; - -static inline void wifi_lock(void) { +void wifi_lock(void) { mgos_rlock(s_wifi_lock); } -static inline void wifi_unlock(void) { +void wifi_unlock(void) { mgos_runlock(s_wifi_lock); } -static const struct mgos_config_wifi_sta *mgos_wifi_get_sta_cfg( - const struct mgos_config_wifi *cfg, int idx) { - if (cfg == NULL) return NULL; - switch (idx) { - case 0: - return &cfg->sta; - case 1: - return &cfg->sta1; - case 2: - return &cfg->sta2; - } - return NULL; -} - -static int mgos_wifi_get_next_sta_cfg_idx(const struct mgos_config_wifi *cfg, - int start) { - if (cfg == NULL) return -1; - int idx = start; - char *msg = NULL; - do { - const struct mgos_config_wifi_sta *sta_cfg = - mgos_wifi_get_sta_cfg(cfg, idx); - if (sta_cfg && sta_cfg->enable) { - if (mgos_wifi_validate_sta_cfg(sta_cfg, &msg)) { - LOG(LL_INFO, ("WiFi STA: Using config %d (%s)", idx, sta_cfg->ssid)); - return idx; - } else { - free(msg); - msg = NULL; - } - } - idx = (idx + 1) % NUM_STA_CFG; - } while (idx != start); - return -1; -} - -static void mgos_wifi_sta_connect_timeout_timer_cb(void *arg); - static void mgos_wifi_event_cb(void *arg) { - bool reconnect = false, net_event = true; + bool net_event = true; struct mgos_wifi_dev_event_info *dei = (struct mgos_wifi_dev_event_info *) arg; void *ev_arg = NULL; enum mgos_net_event nev = MGOS_NET_EV_DISCONNECTED; switch (dei->ev) { case MGOS_WIFI_EV_STA_DISCONNECTED: { - if ((s_sta_status == MGOS_WIFI_CONNECTED || - s_sta_status == MGOS_WIFI_IP_ACQUIRED) && - s_sta_should_reconnect == 1) { - reconnect = s_sta_should_reconnect; - } ev_arg = &dei->sta_disconnected; - s_sta_status = MGOS_WIFI_DISCONNECTED; nev = MGOS_NET_EV_DISCONNECTED; LOG(LL_INFO, ("WiFi STA: Disconnected, reason: %d", dei->sta_disconnected.reason)); break; } case MGOS_WIFI_EV_STA_CONNECTING: { - s_sta_status = MGOS_WIFI_CONNECTING; nev = MGOS_NET_EV_CONNECTING; break; } case MGOS_WIFI_EV_STA_CONNECTED: { ev_arg = &dei->sta_connected; - s_sta_status = MGOS_WIFI_CONNECTED; nev = MGOS_NET_EV_CONNECTED; struct mgos_wifi_sta_connected_arg *ea = &dei->sta_connected; ea->rssi = mgos_wifi_sta_get_rssi(); @@ -138,16 +84,6 @@ static void mgos_wifi_event_cb(void *arg) { break; } case MGOS_WIFI_EV_STA_IP_ACQUIRED: { - s_sta_status = MGOS_WIFI_IP_ACQUIRED; - mgos_clear_timer(s_connect_timer_id); - s_connect_timer_id = MGOS_INVALID_TIMER_ID; - if (s_cur_cfg && s_cur_cfg->sta_cfg_idx != s_cur_sta_cfg_idx) { - LOG(LL_INFO, ("WiFi STA: New current config: %d", s_cur_sta_cfg_idx)); - s_cur_cfg->sta_cfg_idx = s_cur_sta_cfg_idx; - if (s_cur_cfg == mgos_sys_config_get_wifi()) { - save_cfg(&mgos_sys_config, NULL); - } - } nev = MGOS_NET_EV_IP_ACQUIRED; break; } @@ -173,10 +109,6 @@ static void mgos_wifi_event_cb(void *arg) { } free(dei); - - if (reconnect && s_sta_should_reconnect == 1) { - mgos_wifi_connect(); - } } void mgos_wifi_dev_event_cb(const struct mgos_wifi_dev_event_info *dei) { @@ -247,36 +179,20 @@ static bool validate_wifi_cfg(const struct mgos_config *cfg, char **msg) { mgos_wifi_validate_sta_cfg(&cfg->wifi.sta, msg)); } -static void set_reconnect_timer(void) { - if (s_cur_cfg == NULL || s_cur_sta_cfg_idx < 0 || - s_cur_cfg->sta_connect_timeout <= 0 || - s_connect_timer_id != MGOS_INVALID_TIMER_ID) { - return; - } - s_connect_timer_id = - mgos_set_timer(s_cur_cfg->sta_connect_timeout * 1000, 0, - mgos_wifi_sta_connect_timeout_timer_cb, NULL); -} - bool mgos_wifi_setup_sta(const struct mgos_config_wifi_sta *cfg) { + int ret = true; char *err_msg = NULL; if (!mgos_wifi_validate_sta_cfg(cfg, &err_msg)) { LOG(LL_ERROR, ("WiFi STA: %s", err_msg)); free(err_msg); return false; } - wifi_lock(); - bool ret = mgos_wifi_dev_sta_setup(cfg); + mgos_wifi_sta_clear_cfgs(); + ret = mgos_wifi_sta_add_cfg(cfg); if (ret && cfg->enable) { LOG(LL_INFO, ("WiFi STA: Connecting to %s", cfg->ssid)); - free(s_sta_ssid); - s_sta_ssid = strdup(cfg->ssid); ret = mgos_wifi_connect(); - } else { - set_reconnect_timer(); } - if (ret) s_cur_cfg = NULL; - wifi_unlock(); return ret; } @@ -307,84 +223,6 @@ bool mgos_wifi_setup_ap(const struct mgos_config_wifi_ap *cfg) { return ret; } -bool mgos_wifi_connect(void) { - if (s_sta_should_reconnect < 0) return false; - wifi_lock(); - s_sta_should_reconnect = 1; - bool ret = mgos_wifi_dev_sta_connect(); - if (ret) { - struct mgos_wifi_dev_event_info dei = { - .ev = MGOS_WIFI_EV_STA_CONNECTING, - }; - mgos_wifi_dev_event_cb(&dei); - } - set_reconnect_timer(); - wifi_unlock(); - return ret; -} - -bool mgos_wifi_disconnect(void) { - if (s_cur_sta_cfg_idx < 0) return false; // Not inited. - wifi_lock(); - if (s_sta_should_reconnect >= 0) { - s_sta_should_reconnect = 0; - } - bool ret = true; - if (s_sta_status != MGOS_WIFI_DISCONNECTED) { - ret = mgos_wifi_dev_sta_disconnect(); - } - if (ret) { - mgos_clear_timer(s_connect_timer_id); - s_connect_timer_id = MGOS_INVALID_TIMER_ID; - } - wifi_unlock(); - return ret; -} - -static void mgos_wifi_sta_connect_timeout_timer_cb(void *arg) { - wifi_lock(); - int new_idx = -1; - struct mgos_config_wifi *cfg = s_cur_cfg; - s_connect_timer_id = MGOS_INVALID_TIMER_ID; - LOG(LL_ERROR, ("WiFi STA: Connect timeout")); - if (cfg == NULL) goto out; - new_idx = mgos_wifi_get_next_sta_cfg_idx( - cfg, (s_cur_sta_cfg_idx + 1) % NUM_STA_CFG); - /* Note: even if the config didn't change, disconnect and - * reconnect to re-init WiFi. */ - mgos_wifi_disconnect(); - mgos_wifi_setup_sta(mgos_wifi_get_sta_cfg(cfg, new_idx)); - s_cur_cfg = cfg; - s_cur_sta_cfg_idx = new_idx; -out: - wifi_unlock(); - (void) arg; -} - -enum mgos_wifi_status mgos_wifi_get_status(void) { - return s_sta_status; -} - -char *mgos_wifi_get_status_str(void) { - const char *s = NULL; - enum mgos_wifi_status st = mgos_wifi_get_status(); - switch (st) { - case MGOS_WIFI_DISCONNECTED: - s = "disconnected"; - break; - case MGOS_WIFI_CONNECTING: - s = "connecting"; - break; - case MGOS_WIFI_CONNECTED: - s = "connected"; - break; - case MGOS_WIFI_IP_ACQUIRED: - s = "got ip"; - break; - } - return (s != NULL ? strdup(s) : NULL); -} - struct scan_result_info { int num_res; struct mgos_wifi_scan_result *res; @@ -434,10 +272,6 @@ void mgos_wifi_scan(mgos_wifi_scan_cb_t cb, void *arg) { wifi_unlock(); } -char *mgos_wifi_get_connected_ssid(void) { - return (s_sta_status == MGOS_WIFI_IP_ACQUIRED ? strdup(s_sta_ssid) : NULL); -} - bool mgos_wifi_setup(struct mgos_config_wifi *cfg) { bool result = false, trigger_ap = false; int gpio = cfg->ap.trigger_on_gpio; @@ -451,9 +285,9 @@ bool mgos_wifi_setup(struct mgos_config_wifi *cfg) { const struct mgos_config_wifi_ap dummy_ap_cfg = {.enable = false}; const struct mgos_config_wifi_sta dummy_sta_cfg = {.enable = false}; - int sta_cfg_idx = mgos_wifi_get_next_sta_cfg_idx(cfg, cfg->sta_cfg_idx); - const struct mgos_config_wifi_sta *sta_cfg = - mgos_wifi_get_sta_cfg(cfg, sta_cfg_idx); + const struct mgos_config_wifi_sta *sta_cfg = mgos_sys_config_get_wifi_sta(); + const struct mgos_config_wifi_sta *sta_cfg1 = mgos_sys_config_get_wifi_sta1(); + const struct mgos_config_wifi_sta *sta_cfg2 = mgos_sys_config_get_wifi_sta2(); if (trigger_ap || (cfg->ap.enable && sta_cfg == NULL)) { struct mgos_config_wifi_ap ap_cfg; @@ -468,11 +302,15 @@ bool mgos_wifi_setup(struct mgos_config_wifi *cfg) { LOG(LL_INFO, ("WiFi mode: %s", "AP+STA")); result = (mgos_wifi_setup_ap(&cfg->ap) && mgos_wifi_setup_sta(sta_cfg)); #endif - } else if (sta_cfg != NULL) { + } else if (sta_cfg->enable || sta_cfg1->enable || sta_cfg2->enable) { LOG(LL_INFO, ("WiFi mode: %s", "STA")); /* Disable AP if it was enabled. */ mgos_wifi_setup_ap(&dummy_ap_cfg); - result = mgos_wifi_setup_sta(sta_cfg); + mgos_wifi_sta_clear_cfgs(); + result |= mgos_wifi_sta_add_cfg(sta_cfg); + result |= mgos_wifi_sta_add_cfg(sta_cfg1); + result |= mgos_wifi_sta_add_cfg(sta_cfg2); + if (result) mgos_wifi_connect(); } else { LOG(LL_INFO, ("WiFi mode: %s", "off")); mgos_wifi_setup_sta(&dummy_sta_cfg); @@ -480,12 +318,6 @@ bool mgos_wifi_setup(struct mgos_config_wifi *cfg) { result = true; } - s_cur_cfg = cfg; - s_cur_sta_cfg_idx = sta_cfg_idx; - mgos_clear_timer(s_connect_timer_id); - s_connect_timer_id = MGOS_INVALID_TIMER_ID; - set_reconnect_timer(); - return result; } @@ -521,26 +353,6 @@ static void mgos_wifi_dns_ev_handler(struct mg_connection *c, int ev, (void) user_data; } -static void mgos_wifi_disconnect_cb(void *arg) { - mgos_wifi_disconnect(); - s_sta_should_reconnect = -1; - (void) arg; -} - -static void mgos_wifi_reboot_after_ev_handler(int ev, void *evd, void *cb_arg) { - const struct mgos_event_reboot_after_arg *arg = - (struct mgos_event_reboot_after_arg *) evd; - int64_t time_to_reboot_ms = - (arg->reboot_at_uptime_micros - mgos_uptime_micros()) / 1000; - if (time_to_reboot_ms > 50) { - mgos_set_timer(time_to_reboot_ms - 50, 0, mgos_wifi_disconnect_cb, NULL); - } else { - mgos_wifi_disconnect_cb(NULL); - } - (void) ev; - (void) cb_arg; -} - bool mgos_wifi_init(void) { s_wifi_lock = mgos_rlock_create(); mgos_event_register_base(MGOS_WIFI_EV_BASE, "wifi"); @@ -562,9 +374,7 @@ bool mgos_wifi_init(void) { mg_set_protocol_dns(dns_conn); } - mgos_event_add_handler(MGOS_EVENT_REBOOT_AFTER, - mgos_wifi_reboot_after_ev_handler, NULL); - + mgos_wifi_sta_init(); return true; } diff --git a/src/mgos_wifi_sta.c b/src/mgos_wifi_sta.c new file mode 100644 index 0000000..38a415a --- /dev/null +++ b/src/mgos_wifi_sta.c @@ -0,0 +1,660 @@ +/* + * Copyright (c) Mongoose OS Contributors + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "mgos_wifi_sta.h" + +#include "mgos.h" +#include "mgos_wifi.h" +#include "mgos_wifi_hal.h" + +#ifndef MGOS_WIFI_STA_AP_ATTEMPTS +#define MGOS_WIFI_STA_AP_ATTEMPTS 3 +#endif + +#ifndef MGOS_WIFI_STA_FAILING_AP_RETRY_SECONDS +#define MGOS_WIFI_STA_FAILING_AP_RETRY_SECONDS (60 * 60) // 1 hour +#endif + +#ifndef MGOS_WIFI_STA_AP_HISTORY_SIZE +#define MGOS_WIFI_STA_AP_HISTORY_SIZE 20 +#endif + +#ifndef MGOS_WIFI_STA_ROAM_RSSI_HYST +#define MGOS_WIFI_STA_ROAM_RSSI_HYST 5 +#endif + +#ifndef MGOS_WIFI_STA_MAX_AP_QUEUE_LEN +#define MGOS_WIFI_STA_MAX_AP_QUEUE_LEN 2 +#endif + +void wifi_lock(void); +void wifi_unlock(void); + +enum wifi_sta_state { + WIFI_STA_IDLE = 0, + WIFI_STA_INIT = 1, + WIFI_STA_SCAN = 2, + WIFI_STA_SCANNING = 3, + WIFI_STA_WAIT_CONNECT = 4, + WIFI_STA_CONNECT = 5, + WIFI_STA_CONNECTING = 6, + WIFI_STA_CONNECTED = 7, + WIFI_STA_IP_ACQUIRED = 8, + WIFI_STA_SHUTDOWN = 9, // Shutting down, do not reconnect. +}; + +int8_t s_num_cfgs = 0; +struct mgos_config_wifi_sta **s_cfgs = NULL; +const struct mgos_config_wifi_sta *s_cur_cfg = NULL; + +struct wifi_ap_entry { + const struct mgos_config_wifi_sta *cfg; + uint8_t bssid[6]; + int8_t rssi; + uint8_t num_attempts; + int64_t last_attempt; + SLIST_ENTRY(wifi_ap_entry) next; +}; + +const struct wifi_ap_entry *s_cur_entry = NULL; +static enum wifi_sta_state s_state = WIFI_STA_IDLE; +static mgos_timer_id s_connect_timer_id = MGOS_INVALID_TIMER_ID; +// AP is either on the queue or on the history list, not both. +static SLIST_HEAD(s_ap_queue, wifi_ap_entry) s_ap_queue; +static SLIST_HEAD(s_ap_history, wifi_ap_entry) s_ap_history; +static int64_t s_last_roam_attempt = 0; +static bool s_roaming = false; + +static void mgos_wifi_sta_run(int wifi_ev, void *ev_data, bool timeout); + +static bool is_sys_cfg(const struct mgos_config_wifi_sta *cfg) { + return (cfg == mgos_sys_config_get_wifi_sta() || + cfg == mgos_sys_config_get_wifi_sta1() || + cfg == mgos_sys_config_get_wifi_sta2()); +} + +static void mgos_wifi_sta_free_cfg(struct mgos_config_wifi_sta *cfg) { + if (is_sys_cfg(cfg)) return; + mgos_config_wifi_sta_free(cfg); +} + +static struct wifi_ap_entry *mgos_wifi_sta_find_history_entry( + const uint8_t *bssid) { + struct wifi_ap_entry *ape = NULL; + SLIST_FOREACH(ape, &s_ap_history, next) { + if (memcmp(ape->bssid, bssid, sizeof(ape->bssid)) == 0) return ape; + } + return NULL; +} + +static void mgos_wifi_sta_remove_history_entry(struct wifi_ap_entry *ape) { + SLIST_REMOVE(&s_ap_history, ape, wifi_ap_entry, next); +} + +static void mgos_wifi_sta_add_history_entry(struct wifi_ap_entry *ape) { + SLIST_INSERT_HEAD(&s_ap_history, ape, next); + int num = 0; + struct wifi_ap_entry *oldest = SLIST_FIRST(&s_ap_history); + SLIST_FOREACH(ape, &s_ap_history, next) { + num++; + if (ape->last_attempt < oldest->last_attempt) oldest = ape; + } + if (num > MGOS_WIFI_STA_AP_HISTORY_SIZE) { + mgos_wifi_sta_remove_history_entry(oldest); + free(oldest); + } +} + +static const char *mgos_wifi_sta_bssid_to_str(const uint8_t *bssid, + char *bssid_s) { + snprintf(bssid_s, 20, "%02x:%02x:%02x:%02x:%02x:%02x", bssid[0], bssid[1], + bssid[2], bssid[3], bssid[4], bssid[5]); + return bssid_s; +} + +static bool check_ap(const struct mgos_wifi_scan_result *e, + const struct mgos_config_wifi_sta **sta_cfg, + const struct wifi_ap_entry *hape, const char **reason) { + *sta_cfg = NULL; + for (int i = 0; i < s_num_cfgs; i++) { + const struct mgos_config_wifi_sta *cfg = s_cfgs[i]; + if (!cfg->enable) continue; + if (strcmp(cfg->ssid, e->ssid) != 0) continue; + // Check if auth mode matches. + bool have_pass = !mgos_conf_str_empty(cfg->pass); + bool is_eap = + (!mgos_conf_str_empty(cfg->cert) || !mgos_conf_str_empty(cfg->user)); + switch (e->auth_mode) { + case MGOS_WIFI_AUTH_MODE_OPEN: + if (have_pass || is_eap) continue; + break; + case MGOS_WIFI_AUTH_MODE_WPA2_ENTERPRISE: + if (!is_eap) continue; + break; + case MGOS_WIFI_AUTH_MODE_WEP: + case MGOS_WIFI_AUTH_MODE_WPA_PSK: + case MGOS_WIFI_AUTH_MODE_WPA2_PSK: + case MGOS_WIFI_AUTH_MODE_WPA_WPA2_PSK: + if (!have_pass) continue; + break; + } + // If the config specifies a particular BSSID, check it. + if (!mgos_conf_str_empty(cfg->bssid)) { + char bssid_s[20]; + mgos_wifi_sta_bssid_to_str(e->bssid, bssid_s); + if (strcasecmp(cfg->bssid, bssid_s) != 0) { + continue; + } + } + *sta_cfg = cfg; + break; + } + if (*sta_cfg == NULL) { + *reason = "no matching config"; + return false; + } + if (e->rssi < mgos_sys_config_get_wifi_sta_rssi_thr()) { + *reason = "too weak"; + return false; + } + if (hape != NULL && hape->num_attempts >= MGOS_WIFI_STA_AP_ATTEMPTS && + (mgos_uptime_micros() - hape->last_attempt < + (MGOS_WIFI_STA_FAILING_AP_RETRY_SECONDS * 1000000LL))) { + *reason = "bad history"; + return false; + } + *reason = "ok"; + return true; +} + +static void mgos_wifi_sta_connect_timeout_timer_cb(void *arg) { + wifi_lock(); + mgos_wifi_sta_run(-1 /* wifi_ev */, NULL /* evd */, true /* timeout */); + wifi_unlock(); + (void) arg; +} + +static void set_timeout_n(int timeout, bool run_now) { + mgos_clear_timer(s_connect_timer_id); + s_connect_timer_id = mgos_set_timer( + timeout, MGOS_TIMER_REPEAT, mgos_wifi_sta_connect_timeout_timer_cb, NULL); + if (run_now) { + mgos_wifi_sta_run(-1 /* wifi_ev */, NULL /* evd */, false /* timeout */); + } +} + +static void set_timeout(bool run_now) { + set_timeout_n(mgos_sys_config_get_wifi_sta_connect_timeout() * 1000, run_now); +} + +static void mgos_wifi_sta_build_queue(int num_res, + struct mgos_wifi_scan_result *res, + bool check_history) { + for (int i = 0; i < num_res; i++) { + const struct mgos_wifi_scan_result *e = &res[i]; + const struct mgos_config_wifi_sta *cfg = NULL; + const char *reason = NULL; + struct wifi_ap_entry *eape = mgos_wifi_sta_find_history_entry(e->bssid); + bool ok = check_ap(e, &cfg, (check_history ? eape : NULL), &reason); + /* Check if we already have this queued. */ + int len = 0; + struct wifi_ap_entry *pape = NULL; + if (ok) { + struct wifi_ap_entry *ape = NULL; + SLIST_FOREACH(ape, &s_ap_queue, next) { + if (memcmp(ape->bssid, e->bssid, sizeof(e->bssid)) == 0) { + ok = false; + reason = "dup"; + break; + } + len++; + /* Among bad ones, prefer those with fewer attempts. + * This will have the effect of cycling through all available ones + * even when there are more than the queue can hold. */ + if (ape->num_attempts >= MGOS_WIFI_STA_AP_ATTEMPTS && eape != NULL && + eape->num_attempts >= MGOS_WIFI_STA_AP_ATTEMPTS && + eape->num_attempts != ape->num_attempts) { + if (eape->num_attempts > ape->num_attempts) { + pape = ape; + } + continue; + } + /* Stronger signal APs stay at the front of the queue. */ + if (ape->rssi >= e->rssi) { + pape = ape; + } + } + } + if (ok) { + if (eape == NULL) { + eape = calloc(1, sizeof(*eape)); + memcpy(eape->bssid, e->bssid, sizeof(eape->bssid)); + } else { + mgos_wifi_sta_remove_history_entry(eape); + } + if (eape == NULL) return; + eape->cfg = cfg; + eape->rssi = e->rssi; + if (pape != NULL) { + SLIST_INSERT_AFTER(pape, eape, next); + } else { + SLIST_INSERT_HEAD(&s_ap_queue, eape, next); + } + len++; + while (len > MGOS_WIFI_STA_MAX_AP_QUEUE_LEN) { + len = 0; + pape = NULL; + struct wifi_ap_entry *ape = NULL; + SLIST_FOREACH(ape, &s_ap_queue, next) { + if (SLIST_NEXT(ape, next) == NULL) { + if (pape != NULL) { + SLIST_REMOVE_AFTER(pape, next); + } else { + SLIST_REMOVE_HEAD(&s_ap_queue, next); + } + // If evicted entry has been tried before, put it back on the + // history list. If it's a completely new AP that didn't make it, + // just drop it on the floor, we'll find it again next time. + if (ape->num_attempts > 0) { + mgos_wifi_sta_add_history_entry(ape); + } else { + free(ape); + if (eape == ape) eape = NULL; + } + break; + } + pape = ape; + len++; + } + } + } + LOG(LL_DEBUG, + (" %d: SSID: %-32s, BSSID: %02x:%02x:%02x:%02x:%02x:%02x " + "auth: %d, ch: %3d, RSSI: %2d att %d - %d %s %p", + i, e->ssid, e->bssid[0], e->bssid[1], e->bssid[2], e->bssid[3], + e->bssid[4], e->bssid[5], e->auth_mode, e->channel, e->rssi, + (eape ? eape->num_attempts : -1), ok, reason, eape)); + (void) reason; + } +} + +void mgos_wifi_sta_scan_cb(int num_res, struct mgos_wifi_scan_result *res, + void *arg) { + if (s_state != WIFI_STA_SCANNING) return; + LOG(LL_DEBUG, ("WiFi scan result: %d entries", num_res)); + if (num_res < 0) { + s_state = WIFI_STA_SCAN; + return; + } + mgos_wifi_sta_build_queue(num_res, res, true /* check_history */); + if (SLIST_EMPTY(&s_ap_queue)) { + /* No good quality APs left to try, keep trying bad ones. */ + LOG(LL_DEBUG, ("Second pass")); + mgos_wifi_sta_build_queue(num_res, res, false /* check_history */); + } + if (!SLIST_EMPTY(&s_ap_queue)) { + int i = 0; + struct wifi_ap_entry *ape = NULL; + LOG(LL_DEBUG, ("AP queue:")); + SLIST_FOREACH(ape, &s_ap_queue, next) { + const uint8_t *bssid = &ape->bssid[0]; + LOG(LL_DEBUG, (" %d: %02x:%02x:%02x:%02x:%02x:%02x %d %d", i, bssid[0], + bssid[1], bssid[2], bssid[3], bssid[4], bssid[5], + ape->rssi, ape->num_attempts)); + i++; + } + } + s_state = WIFI_STA_CONNECT; + set_timeout(true /* run_now */); + (void) arg; +} + +static void mgos_wifi_sta_empty_queue(void) { + while (!SLIST_EMPTY(&s_ap_queue)) { + struct wifi_ap_entry *ape = SLIST_FIRST(&s_ap_queue); + SLIST_REMOVE_HEAD(&s_ap_queue, next); + mgos_wifi_sta_add_history_entry(ape); + } +} + +static void mgos_wifi_sta_run(int wifi_ev, void *ev_data, bool timeout) { + LOG(LL_DEBUG, ("State %d ev %d timeout %d", s_state, wifi_ev, timeout)); + if (wifi_ev == MGOS_WIFI_EV_STA_DISCONNECTED) { + s_roaming = false; + s_cur_entry = NULL; + } + switch (s_state) { + case WIFI_STA_IDLE: + break; + case WIFI_STA_INIT: + mgos_wifi_dev_sta_disconnect(); + s_state = WIFI_STA_SCAN; + set_timeout(true /* run_now */); + s_roaming = false; + s_cur_entry = NULL; + break; + case WIFI_STA_SCAN: + LOG(LL_DEBUG, ("Starting scan")); + mgos_wifi_sta_empty_queue(); + s_state = WIFI_STA_SCANNING; + mgos_wifi_scan(mgos_wifi_sta_scan_cb, NULL); + break; + case WIFI_STA_SCANNING: + if (timeout) { + s_state = WIFI_STA_SCAN; + set_timeout(true /* run_now */); + } + break; + case WIFI_STA_WAIT_CONNECT: + if (!timeout) { + set_timeout_n(1000, false /* run_now */); + break; + } + s_state = WIFI_STA_CONNECT; + set_timeout_n(1000, true /* run_now */); + break; + case WIFI_STA_CONNECT: { + struct wifi_ap_entry *ape = SLIST_FIRST(&s_ap_queue); + if (s_roaming) { + s_roaming = false; + // If we are roaming and have no good candidate, go back. + int cur_rssi = mgos_wifi_sta_get_rssi(); + bool ok = false; + if (ape == NULL) { + LOG(LL_DEBUG, ("No alternative APs found")); + } else if (s_cur_entry != NULL && memcmp(s_cur_entry->bssid, ape->bssid, + sizeof(ape->bssid)) == 0) { + LOG(LL_DEBUG, ("Best AP is the same")); + } else if (ape->rssi <= mgos_sys_config_get_wifi_sta_roam_rssi_thr() || + (ape->rssi - MGOS_WIFI_STA_ROAM_RSSI_HYST) < cur_rssi) { + LOG(LL_DEBUG, ("Best AP is not good enough (RSSI %d vs %d)", + ape->rssi, cur_rssi)); + } else { + ok = true; + } + if (!ok) { + s_state = WIFI_STA_IP_ACQUIRED; + set_timeout(true /* run_now */); + break; + } + // We have a better AP candidate, disconnect and try to roam. + char bssid_s[20]; + LOG(LL_INFO, ("Trying to switch to %s (RSSI %d -> %d)", + mgos_wifi_sta_bssid_to_str(ape->bssid, bssid_s), cur_rssi, + ape->rssi)); + mgos_wifi_dev_sta_disconnect(); + // We need to allow some time for connection to terminate. + s_cur_entry = NULL; + s_state = WIFI_STA_WAIT_CONNECT; + set_timeout_n(1000, false /* run_now */); + break; + } + if (ape == NULL) { + LOG(LL_DEBUG, ("No more candidate APs")); + s_state = WIFI_STA_SCAN; + set_timeout(true /* run_now */); + break; + } + ape->num_attempts++; + uint8_t *bssid = &ape->bssid[0]; + LOG(LL_INFO, + ("Trying %s AP %02x:%02x:%02x:%02x:%02x:%02x RSSI %d attempt %d", + ape->cfg->ssid, bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], + bssid[5], ape->rssi, ape->num_attempts)); + ape->last_attempt = mgos_uptime_micros(); + char bssid_s[20]; + mgos_wifi_sta_bssid_to_str(bssid, bssid_s); + struct mgos_config_wifi_sta sta_cfg = *ape->cfg; + sta_cfg.bssid = bssid_s; + mgos_wifi_dev_sta_setup(&sta_cfg); + mgos_wifi_dev_sta_connect(); + s_state = WIFI_STA_CONNECTING; + set_timeout(true /* run_now */); + break; + } + case WIFI_STA_CONNECTING: { + if (wifi_ev == MGOS_WIFI_EV_STA_DISCONNECTED || timeout) { + LOG(LL_INFO, ("Connect failed")); + // Remove the queue entry that failed. + struct wifi_ap_entry *ape = SLIST_FIRST(&s_ap_queue); + SLIST_REMOVE_HEAD(&s_ap_queue, next); + mgos_wifi_sta_add_history_entry(ape); + // Stop connection attempts and let things settle before moving on. + mgos_wifi_dev_sta_disconnect(); + s_cur_entry = NULL; + s_state = WIFI_STA_WAIT_CONNECT; + set_timeout_n(1000, false /* run_now */); + break; + } + if (wifi_ev == MGOS_WIFI_EV_STA_CONNECTED) { + s_cur_entry = SLIST_FIRST(&s_ap_queue); + s_state = WIFI_STA_CONNECTED; + } + break; + } + case WIFI_STA_CONNECTED: { + if (wifi_ev == MGOS_WIFI_EV_STA_IP_ACQUIRED) { + struct wifi_ap_entry *ape = SLIST_FIRST(&s_ap_queue); + ape->num_attempts = 0; + mgos_wifi_sta_empty_queue(); + s_last_roam_attempt = mgos_uptime_micros(); + s_state = WIFI_STA_IP_ACQUIRED; + break; + } + int cur_rssi = mgos_wifi_sta_get_rssi(); + if (timeout || wifi_ev == MGOS_WIFI_EV_STA_DISCONNECTED || + cur_rssi == 0) { + s_state = WIFI_STA_INIT; + set_timeout_n(1000, false /* run_now */); + break; + } + break; + } + case WIFI_STA_IP_ACQUIRED: { + int cur_rssi = mgos_wifi_sta_get_rssi(); + if (wifi_ev == MGOS_WIFI_EV_STA_DISCONNECTED || cur_rssi == 0) { + s_state = WIFI_STA_INIT; + set_timeout_n(1000, false /* run_now */); + break; + } + if (cur_rssi < mgos_sys_config_get_wifi_sta_roam_rssi_thr()) { + if (mgos_uptime_micros() - s_last_roam_attempt > + mgos_sys_config_get_wifi_sta_roam_interval() * 1000000) { + LOG(LL_INFO, + ("Current RSSI %d, will scan for a better AP", cur_rssi)); + s_roaming = true; + s_state = WIFI_STA_SCAN; + set_timeout(true /* run_now */); + s_last_roam_attempt = mgos_uptime_micros(); + break; + } + } + break; + } + case WIFI_STA_SHUTDOWN: + break; + } +} + +static void mgos_wifi_ev_handler(int ev, void *evd, void *cb_arg) { + wifi_lock(); + mgos_wifi_sta_run(ev, evd, false /* timeout */); + wifi_unlock(); + (void) cb_arg; +} + +bool mgos_wifi_connect(void) { + int ret = true; + wifi_lock(); + switch (s_state) { + case WIFI_STA_SHUTDOWN: + ret = false; + break; + case WIFI_STA_IDLE: + s_state = WIFI_STA_INIT; + set_timeout(true /* run_now */); + break; + case WIFI_STA_INIT: + case WIFI_STA_SCAN: + case WIFI_STA_SCANNING: + case WIFI_STA_WAIT_CONNECT: + case WIFI_STA_CONNECT: + case WIFI_STA_CONNECTING: + case WIFI_STA_CONNECTED: + case WIFI_STA_IP_ACQUIRED: + break; + } + wifi_unlock(); + return ret; +} + +bool mgos_wifi_disconnect(void) { + wifi_lock(); + bool ret = true; + if (s_state == WIFI_STA_SHUTDOWN) { + wifi_unlock(); + return true; + } + bool disconnect = (s_state != WIFI_STA_IDLE); + mgos_clear_timer(s_connect_timer_id); + s_connect_timer_id = MGOS_INVALID_TIMER_ID; + s_state = WIFI_STA_IDLE; + if (disconnect) { + ret = mgos_wifi_dev_sta_disconnect(); + s_cur_entry = NULL; + } + wifi_unlock(); + return ret; +} + +enum mgos_wifi_status mgos_wifi_get_status(void) { + switch (s_state) { + case WIFI_STA_IDLE: + case WIFI_STA_SHUTDOWN: + return MGOS_WIFI_DISCONNECTED; + case WIFI_STA_SCAN: + case WIFI_STA_SCANNING: + if (s_roaming) { + return MGOS_WIFI_IP_ACQUIRED; + } else { + return MGOS_WIFI_CONNECTING; + } + case WIFI_STA_INIT: + case WIFI_STA_WAIT_CONNECT: + case WIFI_STA_CONNECT: + case WIFI_STA_CONNECTING: + return MGOS_WIFI_CONNECTING; + case WIFI_STA_CONNECTED: + return MGOS_WIFI_CONNECTED; + case WIFI_STA_IP_ACQUIRED: + return MGOS_WIFI_IP_ACQUIRED; + } + return MGOS_WIFI_DISCONNECTED; +} + +char *mgos_wifi_get_status_str(void) { + const char *s = NULL; + enum mgos_wifi_status st = mgos_wifi_get_status(); + switch (st) { + case MGOS_WIFI_DISCONNECTED: + s = "disconnected"; + break; + case MGOS_WIFI_CONNECTING: + s = "connecting"; + break; + case MGOS_WIFI_CONNECTED: + s = "connected"; + break; + case MGOS_WIFI_IP_ACQUIRED: + s = "got ip"; + break; + } + return (s != NULL ? strdup(s) : NULL); +} + +static void mgos_wifi_shutdown_cb(void *arg) { + mgos_wifi_disconnect(); + wifi_lock(); + s_state = WIFI_STA_SHUTDOWN; + wifi_unlock(); + (void) arg; +} + +static void mgos_wifi_reboot_after_ev_handler(int ev, void *evd, void *cb_arg) { + const struct mgos_event_reboot_after_arg *arg = + (struct mgos_event_reboot_after_arg *) evd; + int64_t time_to_reboot_ms = + (arg->reboot_at_uptime_micros - mgos_uptime_micros()) / 1000; + if (time_to_reboot_ms > 50) { + mgos_set_timer(time_to_reboot_ms - 50, 0, mgos_wifi_shutdown_cb, NULL); + } else { + mgos_wifi_shutdown_cb(NULL); + } + (void) ev; + (void) cb_arg; +} + +bool mgos_wifi_sta_add_cfg(const struct mgos_config_wifi_sta *cfg) { + if (!cfg->enable) return false; + if (!mgos_wifi_validate_sta_cfg(cfg, NULL)) return false; + struct mgos_config_wifi_sta *cfg2; + if (is_sys_cfg(cfg)) { + cfg2 = (struct mgos_config_wifi_sta *) cfg; + } else { + cfg2 = calloc(1, sizeof(*cfg)); + if (cfg2 == NULL) return false; + if (!mgos_config_wifi_sta_copy(cfg, cfg2)) return false; + } + struct mgos_config_wifi_sta **cfgs = + realloc(s_cfgs, (s_num_cfgs + 1) * sizeof(*s_cfgs)); + if (cfgs == NULL) return false; + cfgs[s_num_cfgs] = cfg2; + s_cfgs = cfgs; + s_num_cfgs++; + return true; +} + +void mgos_wifi_sta_clear_cfgs(void) { + s_cur_entry = NULL; + while (!SLIST_EMPTY(&s_ap_queue)) { + struct wifi_ap_entry *ape = SLIST_FIRST(&s_ap_queue); + SLIST_REMOVE_HEAD(&s_ap_queue, next); + free(ape); + } + while (!SLIST_EMPTY(&s_ap_history)) { + struct wifi_ap_entry *ape = SLIST_FIRST(&s_ap_history); + SLIST_REMOVE_HEAD(&s_ap_history, next); + free(ape); + } + for (int i = 0; i < s_num_cfgs; i++) { + mgos_wifi_sta_free_cfg(s_cfgs[i]); + } + s_num_cfgs = 0; + free(s_cfgs); + s_cfgs = NULL; +} + +char *mgos_wifi_get_connected_ssid(void) { + if (s_cur_entry == NULL) return NULL; + return strdup(s_cur_entry->cfg->ssid); +} + +void mgos_wifi_sta_init(void) { + mgos_event_add_group_handler(MGOS_WIFI_EV_BASE, mgos_wifi_ev_handler, NULL); + mgos_event_add_handler(MGOS_EVENT_REBOOT_AFTER, + mgos_wifi_reboot_after_ev_handler, NULL); +}