521 lines
15 KiB
C++
521 lines
15 KiB
C++
/// © MiroZ 2024
|
|
#include "Wifi.h"
|
|
#include "errors.h"
|
|
#include "Settings.h"
|
|
|
|
#include <WiFi.h>
|
|
#include <functional>
|
|
#include "utilities.h"
|
|
#include "app_config.h"
|
|
|
|
#include <esp_wifi.h>
|
|
#include "esp_phy_init.h"
|
|
|
|
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);
|
|
// Add WiFi configuration here
|
|
esp_wifi_set_country_code("US", true); // Or your country code
|
|
//WiFi.setSleep(false); // Disable power saving if BT is enabled, do not do this!
|
|
|
|
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, rssi: %d", WiFi.disconnectReasonName(reason), WiFi.RSSI());
|
|
ESP_LOGI(TAG, "Local IP: %s", WiFi.localIP().toString().c_str());
|
|
}
|
|
if(event == ARDUINO_EVENT_WIFI_STA_CONNECTED)
|
|
{
|
|
ESP_LOGI(TAG, "Connected - RSSI: %d, Channel: %d", WiFi.RSSI(), WiFi.channel());
|
|
}
|
|
}
|
|
|
|
void Wifi::task()
|
|
{
|
|
while(true)
|
|
{
|
|
// Check if we're in provisioning mode and handle timeout
|
|
if(m_wifi_status == WIFI_STATUS::PROVISIONING)
|
|
{
|
|
if(isProvisioningTimedOut())
|
|
{
|
|
ESP_LOGW(TAG, "Provisioning timeout reached, stopping AP mode");
|
|
stopProvisioning();
|
|
m_wifi_status = WIFI_STATUS::NOT_CONNECTED;
|
|
m_provision_timeout = true;
|
|
}
|
|
delay(1000); // Check every second during provisioning
|
|
continue;
|
|
}
|
|
|
|
// Check pause flag FIRST (but not during provisioning)
|
|
while(m_pause && m_wifi_status != WIFI_STATUS::PROVISIONING)
|
|
{
|
|
delay(100);
|
|
}
|
|
|
|
if(!WiFi.isConnected())
|
|
{
|
|
m_wifi_status = WIFI_STATUS::PENDING;
|
|
m_wifi_status = startConnecting();
|
|
|
|
delay(500);
|
|
}
|
|
delay(15000);
|
|
}
|
|
}
|
|
|
|
void Wifi::startProvisioning()
|
|
{
|
|
m_wifi_status = WIFI_STATUS::PROVISIONING;
|
|
m_provision_start_time = millis();
|
|
m_provision_timeout = false;
|
|
ESP_LOGI(TAG, "Starting provisioning mode for %lu minutes", PROVISION_TIMEOUT_MS / 60000);
|
|
}
|
|
|
|
void Wifi::stopProvisioning()
|
|
{
|
|
m_wifi_status = WIFI_STATUS::NOT_CONNECTED;
|
|
m_provision_start_time = 0;
|
|
WiFi.mode(WIFI_STA); // Stop AP mode
|
|
}
|
|
|
|
bool Wifi::isProvisioningTimedOut()
|
|
{
|
|
if(m_provision_start_time == 0) return false;
|
|
return (millis() - m_provision_start_time) > PROVISION_TIMEOUT_MS;
|
|
}
|
|
|
|
void Wifi::resetProvisioningTimer()
|
|
{
|
|
m_provision_start_time = millis();
|
|
m_provision_timeout = false;
|
|
}
|
|
|
|
void Wifi::pause(bool pause)
|
|
{
|
|
m_pause = pause;
|
|
}
|
|
|
|
|
|
bool Wifi::shouldRecalibrateRadio(int failed_ssid_index)
|
|
{
|
|
// Check cooldown period
|
|
unsigned long current_time = millis();
|
|
bool cooldown_ok = (m_last_recalibration_time == 0) ||
|
|
(current_time - m_last_recalibration_time >= RECALIBRATION_COOLDOWN_MS);
|
|
|
|
if(!cooldown_ok)
|
|
{
|
|
unsigned long time_remaining = RECALIBRATION_COOLDOWN_MS - (current_time - m_last_recalibration_time);
|
|
ESP_LOGD(TAG, "Recalibration skipped - cooldown period active (%lu ms remaining)", time_remaining);
|
|
return false;
|
|
}
|
|
|
|
// Check if we have credentials for this index
|
|
if(failed_ssid_index < 0 || failed_ssid_index >= SETTINGS.wifi.num)
|
|
{
|
|
ESP_LOGD(TAG, "Recalibration skipped - no valid credentials");
|
|
return false;
|
|
}
|
|
|
|
// Check if the SSID is visible (network is up)
|
|
const char* target_ssid = SETTINGS.wifi.entry[failed_ssid_index].ssid;
|
|
if(!isSSIDVisible(target_ssid))
|
|
{
|
|
ESP_LOGD(TAG, "Recalibration skipped - SSID '%s' not visible", target_ssid);
|
|
return false;
|
|
}
|
|
|
|
ESP_LOGW(TAG, "Radio recalibration needed - connection failed but SSID '%s' is visible", target_ssid);
|
|
ESP_LOGI(TAG, "Current time: %lu, Last recalibration: %lu", current_time, m_last_recalibration_time);
|
|
return true;
|
|
}
|
|
|
|
bool Wifi::isSSIDVisible(const char* ssid)
|
|
{
|
|
ESP_LOGI(TAG, "Scanning for SSID '%s' visibility...", ssid);
|
|
|
|
int num_networks = WiFi.scanNetworks(false, false, false, 500);
|
|
ESP_LOGI(TAG, "Found %d networks during visibility check", num_networks);
|
|
|
|
for(int n = 0; n < num_networks; n++)
|
|
{
|
|
if(strcmp(WiFi.SSID(n).c_str(), ssid) == 0)
|
|
{
|
|
ESP_LOGI(TAG, "SSID '%s' found with RSSI %d dBm", ssid, WiFi.RSSI(n));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
ESP_LOGI(TAG, "SSID '%s' not found in scan results", ssid);
|
|
return false;
|
|
}
|
|
|
|
void Wifi::performRadioRecalibration()
|
|
{
|
|
ESP_LOGW(TAG, "Performing WiFi radio recalibration...");
|
|
|
|
// Disconnect and stop WiFi
|
|
WiFi.disconnect();
|
|
WiFi.mode(WIFI_OFF);
|
|
delay(1000);
|
|
|
|
// Erase calibration data to force fresh calibration
|
|
esp_phy_erase_cal_data_in_nvs();
|
|
|
|
// Record recalibration time
|
|
m_last_recalibration_time = millis();
|
|
|
|
ESP_LOGW(TAG, "Radio recalibration completed - WiFi will reinitialize on next connection attempt");
|
|
|
|
// Brief delay to let the radio settle
|
|
delay(2000);
|
|
}
|
|
|
|
|
|
/// @brief Connect to provisioned wifi
|
|
/// @param index index of wifi in settings
|
|
/// @return WIFI_STATUS::CONNECTED/NOT_CONENCTED connected
|
|
// Modified connectTo method with smart recalibration
|
|
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);
|
|
|
|
// Check if we should recalibrate the radio
|
|
if(shouldRecalibrateRadio(index))
|
|
{
|
|
performRadioRecalibration();
|
|
|
|
// Try connecting one more time after recalibration
|
|
ESP_LOGI(TAG, "Retrying connection after recalibration...");
|
|
delay(3000); // Give radio time to reinitialize
|
|
|
|
WiFi.mode(WIFI_STA);
|
|
WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS);
|
|
WiFi.begin(SETTINGS.wifi.entry[index].ssid, SETTINGS.wifi.entry[index].pwd);
|
|
|
|
status = WiFi.waitForConnectResult(10000);
|
|
if(status == WL_CONNECTED)
|
|
{
|
|
ESP_LOGI(TAG, "Connected after recalibration!");
|
|
return WIFI_STATUS::CONNECTED;
|
|
}
|
|
else
|
|
{
|
|
ESP_LOGW(TAG, "Still failed after recalibration, status: %d", status);
|
|
WiFi.disconnect();
|
|
}
|
|
}
|
|
|
|
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 Connect to default fallback wifi network
|
|
/// @return WIFI_STATUS::CONNECTED/NOT_CONNECTED
|
|
// Also modify startConnecting to handle recalibration for fallback network
|
|
Wifi::WIFI_STATUS Wifi::connectToDefault()
|
|
{
|
|
const char* default_ssid = "CBX_IoT";
|
|
const char* default_password = "69696969";
|
|
|
|
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 default network %s...", default_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(default_ssid, default_password);
|
|
int status = WiFi.waitForConnectResult(10000);
|
|
|
|
if(status == WL_CONNECTED)
|
|
{
|
|
ESP_LOGI(TAG, "Connected to default network");
|
|
return WIFI_STATUS::CONNECTED;
|
|
}
|
|
|
|
delay(1000);
|
|
WiFi.disconnect();
|
|
ESP_LOGW(TAG, "Failed to connect to default network, status: %d", status);
|
|
|
|
return WIFI_STATUS::NOT_CONNECTED;
|
|
}
|
|
|
|
/// @brief Connects to wifi. Returns immediately if already connected.
|
|
/// @return WH_OK | WH_ERR_WIFI_NOT_PROVISIONED
|
|
Wifi::WIFI_STATUS Wifi::startConnecting()
|
|
{
|
|
if(WiFi.isConnected())
|
|
return WIFI_STATUS::CONNECTED;
|
|
|
|
// Try provisioned networks first if any exist
|
|
if(SETTINGS.wifi.num > 0)
|
|
{
|
|
// 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_LOGW(TAG, "Failed to connect to provisioned network");
|
|
}
|
|
else
|
|
{
|
|
// 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)
|
|
{
|
|
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_LOGW(TAG, "Failed to connect to any provisioned networks");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ESP_LOGW(TAG, "No networks provisioned");
|
|
}
|
|
|
|
// Fallback: Try default WiFi network CBX_IoT
|
|
ESP_LOGI(TAG, "Attempting fallback connection to default network CBX_IoT...");
|
|
|
|
WIFI_STATUS fallback_status = connectToDefault();
|
|
if(fallback_status == WIFI_STATUS::CONNECTED)
|
|
{
|
|
ESP_LOGI(TAG, "Successfully connected to default network");
|
|
return WIFI_STATUS::CONNECTED;
|
|
}
|
|
|
|
// Check if we should recalibrate for the fallback network
|
|
ESP_LOGW(TAG, "Fallback connection failed, checking if recalibration needed...");
|
|
|
|
const char* default_ssid = "CBX_IoT";
|
|
if(isSSIDVisible(default_ssid))
|
|
{
|
|
unsigned long current_time = millis();
|
|
|
|
// Allow recalibration if:
|
|
// 1. Never recalibrated before (m_last_recalibration_time == 0)
|
|
// 2. Or enough time has passed since last recalibration
|
|
bool cooldown_ok = (m_last_recalibration_time == 0) ||
|
|
(current_time - m_last_recalibration_time >= RECALIBRATION_COOLDOWN_MS);
|
|
|
|
if(cooldown_ok)
|
|
{
|
|
ESP_LOGW(TAG, "Fallback network '%s' visible but connection failed - performing recalibration", default_ssid);
|
|
ESP_LOGI(TAG, "Current time: %lu, Last recalibration: %lu", current_time, m_last_recalibration_time);
|
|
|
|
performRadioRecalibration();
|
|
|
|
// Retry fallback connection after recalibration
|
|
ESP_LOGI(TAG, "Retrying fallback connection after recalibration...");
|
|
fallback_status = connectToDefault();
|
|
if(fallback_status == WIFI_STATUS::CONNECTED)
|
|
{
|
|
ESP_LOGI(TAG, "Successfully connected to fallback after recalibration!");
|
|
return WIFI_STATUS::CONNECTED;
|
|
}
|
|
else
|
|
{
|
|
ESP_LOGW(TAG, "Fallback still failed after recalibration");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
unsigned long time_remaining = RECALIBRATION_COOLDOWN_MS - (current_time - m_last_recalibration_time);
|
|
ESP_LOGD(TAG, "Recalibration skipped - cooldown period active (%lu ms remaining)", time_remaining);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ESP_LOGI(TAG, "Fallback network not visible - network may be down");
|
|
}
|
|
|
|
ESP_LOGE(TAG, "Cannot connect to any network including default fallback");
|
|
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;
|
|
} |