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); +}