/// © MiroZ 2024 #include "Wifi.h" #include "errors.h" #include "Settings.h" #include #include #include "utilities.h" #include "app_config.h" #include static const char *TAG = "Wifi"; #define IGNORE_SSID_MINS 20 using namespace std::placeholders; Wifi::Wifi() { esp_log_level_set("wifi", ESP_LOG_WARN); esp_log_level_set("wifi_init", ESP_LOG_INFO); WiFi.onEvent(std::bind(&Wifi::wifi_event, this, _1, _2)); #ifdef USE_TIMER esp_timer_create_args_t timer; timer.arg = this; timer.callback = &timerCallback; timer.dispatch_method = ESP_TIMER_TASK; timer.skip_unhandled_events = true; timer.name = "wifi countdown timer"; ESP_ERROR_CHECK(esp_timer_create(&timer, &m_timer)); ESP_ERROR_CHECK(esp_timer_start_periodic(m_timer, 60000000)); // 1 min #endif } #ifdef USE_TIMER void Wifi::timerCallback(void* arg) { for(int n = 0; n < SETTINGS_NUM_WIFI_ENTRIES; n++) { if(((Wifi*)arg)->m_ignored[n] > 0) ((Wifi*)arg)->m_ignored[n]--; } } #endif void Wifi::wifi_event(arduino_event_id_t event, arduino_event_info_t info) { ESP_LOGI(TAG, "event: %s (%d)", WiFi.eventName(event), (int)event); if(event == ARDUINO_EVENT_WIFI_STA_DISCONNECTED) { wifi_err_reason_t reason = (wifi_err_reason_t)info.wifi_sta_disconnected.reason; ESP_LOGI(TAG, "reason: %s", WiFi.disconnectReasonName(reason)); } } void Wifi::task() { while(true) { if(!WiFi.isConnected()) { m_wifi_status = WIFI_STATUS::PENDING; m_wifi_status = startConnecting(); delay(500); while(m_pause) { delay(100); } } delay(15000); } } void Wifi::pause(bool pause) { m_pause = pause; } /// @brief Connect to provisioned wifi /// @param index index of wifi in settings /// @return WIFI_STATUS::CONNECTED/NOT_CONENCTED connected Wifi::WIFI_STATUS Wifi::connectTo(int index) { IPAddress local_IP(0, 0, 0, 0); IPAddress gateway(0, 0, 0, 0); IPAddress subnet(SETTINGS.wifi.subnet_mask); IPAddress primaryDNS(SETTINGS.wifi.dns_primary); IPAddress secondaryDNS(SETTINGS.wifi.dns_secondary); ESP_LOGI(TAG, "Connecting to %s...", SETTINGS.wifi.entry[index].ssid); delay(1000); // set hostname uint8_t mac[6]; WiFi.macAddress(mac); char buffer[32]; sprintf(buffer, "wellhub-tag%02x%02x%02x", mac[3], mac[4], mac[5]); WiFi.hostname(buffer); WiFi.mode(WIFI_STA); WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS); WiFi.begin(SETTINGS.wifi.entry[index].ssid, SETTINGS.wifi.entry[index].pwd); int status = WiFi.waitForConnectResult(10000); if(status == WL_CONNECTED) { ESP_LOGI(TAG, "Connected"); return WIFI_STATUS::CONNECTED; } delay(1000); WiFi.disconnect(); ESP_LOGW(TAG, "Failed to connect, status: %d", status); return WIFI_STATUS::NOT_CONNECTED; } int Wifi::start() { if(m_task == nullptr) { uint8_t mac[8]; esp_read_mac(mac, ESP_MAC_WIFI_STA); ESP_LOGW(TAG, "Starting wifi, mac: %02x:%02x:%02x:%02x:%02x:%02x ...", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); m_task = TaskMgr::getInstance().createTask(std::bind(&Wifi::task, this), WIFI_TASK_NAME, WIFI_TASK_STACK_SIZE, WIFI_TASK_PRIORITY, WIFI_TASK_CORE); } return 0; } Wifi::WIFI_STATUS Wifi::waitForConnection() { Wifi::WIFI_STATUS st; while((st = status()) == Wifi::WIFI_STATUS::PENDING) delay(500); return st; } /// @brief Connects to wifi. Returns immediately if already connected. /// @return WH_OK | WH_ERR_WIFI_NOT_PROVISIONED Wifi::WIFI_STATUS Wifi::startConnecting() { if(SETTINGS.wifi.num == 0) { ESP_LOGE(TAG, "Not provisioned!"); return WIFI_STATUS::NOT_PROVISIONED; } if(WiFi.isConnected()) return WIFI_STATUS::CONNECTED; // if only one network is provisioned if(SETTINGS.wifi.num == 1) { SETTINGS.wifi.selected = 0; for(int n = 0; n < 7; n++) { if(connectTo(SETTINGS.wifi.selected) == WIFI_STATUS::CONNECTED) return WIFI_STATUS::CONNECTED; WiFi.disconnect(); } ESP_LOGE(TAG, "Not connected"); return WIFI_STATUS::NOT_CONNECTED; } // multiple networks are provisioned. Use either specific network or scan for highest rssi memset(m_ignored, 0, SETTINGS_NUM_WIFI_ENTRIES); int ssid_ix = SETTINGS.wifi.selected; if(SETTINGS.wifi.always_scan_before_connect || SETTINGS.wifi.selected >= SETTINGS.wifi.num) ssid_ix = scan(); if(ssid_ix < 0) return WIFI_STATUS::NOT_PROVISIONED; if(connectTo(ssid_ix) == WIFI_STATUS::CONNECTED) { SETTINGS.wifi.selected = ssid_ix; return WIFI_STATUS::CONNECTED; } // didn't work. go through them all giving priority to one with highest rssi m_ignored[ssid_ix] = IGNORE_SSID_MINS; do { ssid_ix = scan(); if(ssid_ix >= 0) { if(connectTo(ssid_ix) == WIFI_STATUS::CONNECTED) return WIFI_STATUS::CONNECTED; else m_ignored[ssid_ix] = IGNORE_SSID_MINS; } else break; } while(true); // that failed too, try them all again round-robin for(int m = 0; m < 3; m++) { for(int n = 0; n < SETTINGS.wifi.num; n++) { if(connectTo(n) == WIFI_STATUS::CONNECTED) return WIFI_STATUS::CONNECTED; } } ESP_LOGE(TAG, "Cannot connect, need provisioning"); return WIFI_STATUS::NOT_PROVISIONED; } /// @brief Scans all visible ssid's and picks known ssid with highest rssi that is not ignored. /// @return wifi index in Settings int Wifi::scan() { ESP_LOGI(TAG, "Scanning wifi networks..."); int num_networks = WiFi.scanNetworks(false, false, false, 500); ESP_LOGI(TAG, "found %d networks", num_networks); int rssi = -500; int selected_index = -1; // crossref scanned and saved networks for(int n = 0; n < num_networks; n++) { for(int p = 0; p < SETTINGS.wifi.num; p++) { if(strcmp(SETTINGS.wifi.entry[p].ssid, WiFi.SSID(n).c_str()) == 0) { // found saved network ESP_LOGI(TAG, "Found provisioned network '%s' with RSSI %d dBm", SETTINGS.wifi.entry[p].ssid, WiFi.RSSI(n)); if(WiFi.RSSI(n) > rssi && m_ignored[p] == 0) { rssi = WiFi.RSSI(n); selected_index = p; } } } } if(selected_index >= 0) ESP_LOGI(TAG, "Chosen network '%s'", SETTINGS.wifi.entry[selected_index].ssid); else ESP_LOGI(TAG, "No suitable networks found at this time"); return selected_index; }