knxdisplay/main/Wifi.cpp
2026-01-19 21:23:22 +01:00

441 lines
13 KiB
C++

#include "Wifi.hpp"
#include "sdkconfig.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "nvs_flash.h"
#include "nvs.h"
#include <cstring>
#include <algorithm>
static const char* TAG = "Wifi";
static const char* disconnectReasonToString(uint16_t reason) {
switch (reason) {
case WIFI_REASON_UNSPECIFIED:
return "UNSPECIFIED";
case WIFI_REASON_AUTH_EXPIRE:
return "AUTH_EXPIRE";
case WIFI_REASON_AUTH_LEAVE:
return "AUTH_LEAVE";
case WIFI_REASON_ASSOC_EXPIRE:
return "ASSOC_EXPIRE";
case WIFI_REASON_NOT_AUTHED:
return "NOT_AUTHED";
case WIFI_REASON_NOT_ASSOCED:
return "NOT_ASSOCED";
case WIFI_REASON_ASSOC_LEAVE:
return "ASSOC_LEAVE";
case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT:
return "4WAY_HANDSHAKE_TIMEOUT";
case WIFI_REASON_GROUP_KEY_UPDATE_TIMEOUT:
return "GROUP_KEY_UPDATE_TIMEOUT";
case WIFI_REASON_BEACON_TIMEOUT:
return "BEACON_TIMEOUT";
case WIFI_REASON_NO_AP_FOUND:
return "NO_AP_FOUND";
case WIFI_REASON_AUTH_FAIL:
return "AUTH_FAIL";
case WIFI_REASON_ASSOC_FAIL:
return "ASSOC_FAIL";
case WIFI_REASON_HANDSHAKE_TIMEOUT:
return "HANDSHAKE_TIMEOUT";
case WIFI_REASON_CONNECTION_FAIL:
return "CONNECTION_FAIL";
default:
return "UNKNOWN";
}
}
static bool isWpa3AuthMode(wifi_auth_mode_t authmode) {
switch (authmode) {
case WIFI_AUTH_WPA3_PSK:
case WIFI_AUTH_WPA3_EXT_PSK:
case WIFI_AUTH_WPA3_EXT_PSK_MIXED_MODE:
return true;
default:
return false;
}
}
Wifi& Wifi::instance() {
static Wifi instance;
return instance;
}
void Wifi::eventHandler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data) {
Wifi& wifi = Wifi::instance();
if (event_base == WIFI_EVENT) {
switch (event_id) {
case WIFI_EVENT_STA_START:
ESP_LOGI(TAG, "WiFi station started");
break;
case WIFI_EVENT_STA_DISCONNECTED:
if (event_data) {
wifi_event_sta_disconnected_t* event =
(wifi_event_sta_disconnected_t*)event_data;
ESP_LOGI(TAG, "WiFi disconnected (reason=%u/%s/%s)",
event->reason, disconnectReasonToString(event->reason), event->ssid);
} else {
ESP_LOGI(TAG, "WiFi disconnected (reason=unknown)");
}
wifi.connected_ = false;
wifi.currentSSID_ = "";
if (wifi.statusCallback_) {
wifi.statusCallback_(false);
}
xEventGroupSetBits(wifi.wifiEventGroup_, WIFI_FAIL_BIT);
break;
case WIFI_EVENT_SCAN_DONE:
ESP_LOGI(TAG, "WiFi scan done");
xEventGroupSetBits(wifi.wifiEventGroup_, WIFI_SCAN_DONE_BIT);
break;
default:
break;
}
} else if (event_base == IP_EVENT) {
if (event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*)event_data;
ESP_LOGI(TAG, "Got IP: " IPSTR, IP2STR(&event->ip_info.ip));
wifi.connected_ = true;
if (wifi.statusCallback_) {
wifi.statusCallback_(true);
}
xEventGroupSetBits(wifi.wifiEventGroup_, WIFI_CONNECTED_BIT);
}
}
}
void Wifi::init(bool autoConnect) {
if (initialized_) {
return;
}
ESP_LOGI(TAG, "Initializing WiFi");
wifiEventGroup_ = xEventGroupCreate();
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
if (!autoConnect) {
ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
ESP_LOGI(TAG, "WiFi storage set to RAM (auto-connect disabled)");
}
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&eventHandler,
nullptr,
nullptr));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&eventHandler,
nullptr,
nullptr));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_start());
int8_t tx_power = 40; // 0.25 dBm units -> 5 dBm.
esp_err_t tx_err = esp_wifi_set_max_tx_power(tx_power);
if (tx_err != ESP_OK) {
ESP_LOGW(TAG, "Failed to set max TX power to %d: %s", tx_power, esp_err_to_name(tx_err));
} else {
ESP_LOGI(TAG, "Set max TX power to %d (0.25 dBm units)", tx_power);
}
initialized_ = true;
if (autoConnect) {
auto savedNetworks = getSavedNetworks();
if (!savedNetworks.empty()) {
std::string password;
if (getSavedPassword(savedNetworks[0].c_str(), password)) {
ESP_LOGI(TAG, "Auto-connecting to saved network: %s", savedNetworks[0].c_str());
connect(savedNetworks[0].c_str(), password.c_str());
}
}
}
ESP_LOGI(TAG, "WiFi initialized");
}
void Wifi::scan(std::function<void(std::vector<wifi_ap_record_t>&)> callback) {
scanCallback_ = callback;
xEventGroupClearBits(wifiEventGroup_, WIFI_SCAN_DONE_BIT);
wifi_scan_config_t scan_config = {};
scan_config.ssid = nullptr;
scan_config.bssid = nullptr;
scan_config.channel = 0;
scan_config.show_hidden = false;
scan_config.scan_type = WIFI_SCAN_TYPE_ACTIVE;
scan_config.scan_time.active.min = 100;
scan_config.scan_time.active.max = 300;
ESP_ERROR_CHECK(esp_wifi_scan_start(&scan_config, false));
xTaskCreate([](void* arg) {
Wifi& wifi = Wifi::instance();
xEventGroupWaitBits(wifi.wifiEventGroup_, WIFI_SCAN_DONE_BIT,
pdTRUE, pdFALSE, pdMS_TO_TICKS(10000));
uint16_t ap_count = 0;
esp_wifi_scan_get_ap_num(&ap_count);
std::vector<wifi_ap_record_t> ap_records(ap_count);
if (ap_count > 0) {
esp_wifi_scan_get_ap_records(&ap_count, ap_records.data());
}
ESP_LOGI(TAG, "Found %d access points", ap_count);
if (wifi.scanCallback_) {
wifi.scanCallback_(ap_records);
}
vTaskDelete(nullptr);
}, "wifi_scan", 4096, nullptr, 5, nullptr);
}
void Wifi::connect(const char* ssid, const char* password, wifi_auth_mode_t authmode) {
ESP_LOGI(TAG, "Connecting to %s with password: '%s' (authmode=%d)", ssid, password, authmode);
xEventGroupClearBits(wifiEventGroup_, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT);
wifi_config_t wifi_config = wifi_config_t();
wifi_sta_config_t sta_config = wifi_sta_config_t();
wifi_config.sta = sta_config;
strcpy((char *)wifi_config.sta.ssid, (char *)ssid);
strcpy((char *)wifi_config.sta.password, (char *)password);
wifi_config.sta.pmf_cfg.capable = true;
wifi_config.sta.pmf_cfg.required = false;
currentSSID_ = ssid;
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
esp_err_t err = esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to set WiFi config: %s", esp_err_to_name(err));
return;
}
err = esp_wifi_connect();
if (err == ESP_ERR_WIFI_CONN) {
ESP_LOGW(TAG, "WiFi connect failed: ESP_ERR_WIFI_CONN");
return;
}
if (err != ESP_OK) {
ESP_LOGE(TAG, "WiFi connect failed: %s", esp_err_to_name(err));
return;
}
}
void Wifi::disconnect() {
ESP_LOGI(TAG, "Disconnecting");
esp_wifi_disconnect();
connected_ = false;
currentSSID_ = "";
}
bool Wifi::isConnected() {
return connected_;
}
std::string Wifi::getCurrentSSID() {
return currentSSID_;
}
std::string Wifi::getIPAddress() {
esp_netif_t* netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
if (!netif) {
return "";
}
esp_netif_ip_info_t ip_info;
if (esp_netif_get_ip_info(netif, &ip_info) != ESP_OK) {
return "";
}
char ip_str[16];
snprintf(ip_str, sizeof(ip_str), IPSTR, IP2STR(&ip_info.ip));
return std::string(ip_str);
}
int8_t Wifi::getRSSI() {
wifi_ap_record_t ap_info;
if (esp_wifi_sta_get_ap_info(&ap_info) == ESP_OK) {
return ap_info.rssi;
}
return 0;
}
void Wifi::setStatusCallback(std::function<void(bool connected)> callback) {
statusCallback_ = callback;
}
void Wifi::saveNetwork(const char* ssid, const char* password) {
nvs_handle_t handle;
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Error opening NVS: %s", esp_err_to_name(err));
return;
}
char key[32];
snprintf(key, sizeof(key), "pw_%s", ssid);
size_t key_len = strlen(key);
if (key_len > 15) {
key[15] = '\0';
}
err = nvs_set_str(handle, key, password);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Error saving password: %s", esp_err_to_name(err));
}
auto networks = getSavedNetworks();
bool found = false;
for (const auto& net : networks) {
if (net == ssid) {
found = true;
break;
}
}
if (!found) {
networks.insert(networks.begin(), ssid);
if (networks.size() > MAX_SAVED_NETWORKS) {
networks.pop_back();
}
std::string networkList;
for (size_t i = 0; i < networks.size(); i++) {
if (i > 0) networkList += ";";
networkList += networks[i];
}
nvs_set_str(handle, "networks", networkList.c_str());
}
nvs_commit(handle);
nvs_close(handle);
ESP_LOGI(TAG, "Saved network: %s", ssid);
}
void Wifi::removeNetwork(const char* ssid) {
nvs_handle_t handle;
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Error opening NVS: %s", esp_err_to_name(err));
return;
}
char key[32];
snprintf(key, sizeof(key), "pw_%s", ssid);
size_t key_len = strlen(key);
if (key_len > 15) {
key[15] = '\0';
}
nvs_erase_key(handle, key);
auto networks = getSavedNetworks();
std::string ssidStr(ssid);
networks.erase(std::remove(networks.begin(), networks.end(), ssidStr), networks.end());
std::string networkList;
for (size_t i = 0; i < networks.size(); i++) {
if (i > 0) networkList += ";";
networkList += networks[i];
}
nvs_set_str(handle, "networks", networkList.c_str());
nvs_commit(handle);
nvs_close(handle);
ESP_LOGI(TAG, "Removed network: %s", ssid);
}
std::vector<std::string> Wifi::getSavedNetworks() {
std::vector<std::string> networks;
nvs_handle_t handle;
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &handle);
if (err != ESP_OK) {
return networks;
}
size_t required_size = 0;
err = nvs_get_str(handle, "networks", nullptr, &required_size);
if (err == ESP_OK && required_size > 1) {
char* buf = new char[required_size];
nvs_get_str(handle, "networks", buf, &required_size);
std::string networkList(buf);
delete[] buf;
if (!networkList.empty()) {
size_t start = 0;
size_t end = networkList.find(';');
while (end != std::string::npos) {
std::string net = networkList.substr(start, end - start);
if (!net.empty()) {
networks.push_back(net);
}
start = end + 1;
end = networkList.find(';', start);
}
if (start < networkList.size()) {
std::string net = networkList.substr(start);
if (!net.empty()) {
networks.push_back(net);
}
}
}
}
nvs_close(handle);
return networks;
}
bool Wifi::getSavedPassword(const char* ssid, std::string& password) {
nvs_handle_t handle;
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &handle);
if (err != ESP_OK) {
return false;
}
char key[32];
snprintf(key, sizeof(key), "pw_%s", ssid);
size_t key_len = strlen(key);
if (key_len > 15) {
key[15] = '\0';
}
size_t required_size = 0;
err = nvs_get_str(handle, key, nullptr, &required_size);
if (err == ESP_OK && required_size > 0) {
password.resize(required_size);
nvs_get_str(handle, key, &password[0], &required_size);
nvs_close(handle);
return true;
}
nvs_close(handle);
return false;
}