Add PoC support for BSSID

This commit is contained in:
Lorow
2026-01-10 22:05:26 +01:00
parent c6584af3b5
commit e205509fec
13 changed files with 139 additions and 209 deletions

View File

@@ -588,6 +588,7 @@ CONFIG_CAMERA_WIFI_XCLK_FREQ=16500000
#
CONFIG_WIFI_MDNS_HOSTNAME="openiristracker"
CONFIG_WIFI_SSID=""
CONFIG_WIFI_BSSID=""
CONFIG_WIFI_PASSWORD=""
CONFIG_WIFI_AP_SSID="EyeTrackVR"
CONFIG_WIFI_AP_PASSWORD="12345678"

View File

@@ -13,6 +13,11 @@ void from_json(const nlohmann::json& j, UpdateWifiPayload& payload)
payload.ssid = j.at("ssid").get<std::string>();
}
if (j.contains("bssid"))
{
payload.ssid = j.at("bssid").get<std::string>();
}
if (j.contains("password"))
{
payload.password = j.at("password").get<std::string>();

View File

@@ -10,17 +10,19 @@ struct WifiPayload : BasePayload
{
std::string name;
std::string ssid;
std::string bssid;
std::string password;
uint8_t channel;
uint8_t power;
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(WifiPayload, name, ssid, password, channel, power)
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(WifiPayload, name, ssid, bssid, password, channel, power)
struct UpdateWifiPayload : BasePayload
{
std::string name;
std::optional<std::string> ssid;
std::optional<std::string> bssid;
std::optional<std::string> password;
std::optional<uint8_t> channel;
std::optional<uint8_t> power;

View File

@@ -19,8 +19,14 @@ CommandResult setWiFiCommand(std::shared_ptr<DependencyRegistry> registry, const
return CommandResult::getErrorResult("Invalid payload: missing SSID");
}
auto bssid_len = payload.bssid.length();
if (bssid_len > 0 && bssid_len != 11)
{
return CommandResult::getErrorResult("BSSID is malformed");
}
std::shared_ptr<ProjectConfig> projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config);
projectConfig->setWifiConfig(payload.name, payload.ssid, payload.password, payload.channel, payload.power);
projectConfig->setWifiConfig(payload.name, payload.ssid, payload.bssid, payload.password, payload.channel, payload.power);
return CommandResult::getSuccessResult("Config updated");
}
@@ -53,12 +59,22 @@ CommandResult updateWiFiCommand(std::shared_ptr<DependencyRegistry> registry, co
return CommandResult::getErrorResult("Invalid payload - missing network name");
}
if (payload.bssid.has_value())
{
auto bssid_len = payload.bssid.value().length();
if (bssid_len > 0 && bssid_len != 11)
{
return CommandResult::getErrorResult("BSSID is malformed");
}
}
auto projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config);
auto storedNetworks = projectConfig->getWifiConfigs();
if (const auto networkToUpdate = std::ranges::find_if(storedNetworks, [&](auto& network) { return network.name == payload.name; });
networkToUpdate != storedNetworks.end())
{
projectConfig->setWifiConfig(payload.name, payload.ssid.has_value() ? payload.ssid.value() : networkToUpdate->ssid,
payload.bssid.has_value() ? payload.bssid.value() : networkToUpdate->bssid,
payload.password.has_value() ? payload.password.value() : networkToUpdate->password,
payload.channel.has_value() ? payload.channel.value() : networkToUpdate->channel,
payload.power.has_value() ? payload.power.value() : networkToUpdate->power);

View File

@@ -170,14 +170,23 @@ struct WiFiConfig_t : BaseConfigModel
// default constructor used for loading
WiFiConfig_t(Preferences* pref) : BaseConfigModel(pref) {}
WiFiConfig_t(Preferences* pref, const uint8_t index, std::string name, std::string ssid, std::string password, const uint8_t channel, const uint8_t power)
: BaseConfigModel(pref), index(index), name(std::move(name)), ssid(std::move(ssid)), password(std::move(password)), channel(channel), power(power)
WiFiConfig_t(Preferences* pref, const uint8_t index, std::string name, std::string ssid, std::string bssid, std::string password, const uint8_t channel,
const uint8_t power)
: BaseConfigModel(pref),
index(index),
name(std::move(name)),
ssid(std::move(ssid)),
bssid(std::move(bssid)),
password(std::move(password)),
channel(channel),
power(power)
{
}
uint8_t index;
std::string name;
std::string ssid;
std::string bssid;
std::string password;
uint8_t channel;
uint8_t power;
@@ -190,6 +199,7 @@ struct WiFiConfig_t : BaseConfigModel
auto const iter_str = std::string(Helpers::itoa(index, buffer, 10));
this->name = this->pref->getString(("name" + iter_str).c_str(), "");
this->ssid = this->pref->getString(("ssid" + iter_str).c_str(), "");
this->bssid = this->pref->getString(("bssid" + iter_str).c_str(), "");
this->password = this->pref->getString(("password" + iter_str).c_str(), "");
this->channel = this->pref->getUInt(("channel" + iter_str).c_str());
this->power = this->pref->getUInt(("power" + iter_str).c_str());
@@ -204,6 +214,7 @@ struct WiFiConfig_t : BaseConfigModel
this->pref->putString(("name" + iter_str).c_str(), this->name.c_str());
this->pref->putString(("ssid" + iter_str).c_str(), this->ssid.c_str());
this->pref->putString(("bssid" + iter_str).c_str(), this->bssid.c_str());
this->pref->putString(("password" + iter_str).c_str(), this->password.c_str());
this->pref->putUInt(("channel" + iter_str).c_str(), this->channel);
this->pref->putUInt(("power" + iter_str).c_str(), this->power);
@@ -213,8 +224,8 @@ struct WiFiConfig_t : BaseConfigModel
std::string toRepresentation()
{
return Helpers::format_string("{\"name\": \"%s\", \"ssid\": \"%s\", \"password\": \"%s\", \"channel\": %u, \"power\": %u}", this->name.c_str(),
this->ssid.c_str(), this->password.c_str(), this->channel, this->power);
return Helpers::format_string("{\"name\": \"%s\", \"ssid\": \"%s\", \"bssid\": \"%s\", \"password\": \"%s\", \"channel\": %u, \"power\": %u}",
this->name.c_str(), this->ssid.c_str(), this->bssid.c_str(), this->password.c_str(), this->channel, this->power);
};
};

View File

@@ -127,7 +127,8 @@ void ProjectConfig::setCameraConfig(const uint8_t vflip, const uint8_t framesize
ESP_LOGD(CONFIGURATION_TAG, "Updating Camera config");
}
void ProjectConfig::setWifiConfig(const std::string& networkName, const std::string& ssid, const std::string& password, uint8_t channel, uint8_t power)
void ProjectConfig::setWifiConfig(const std::string& networkName, const std::string& ssid, const std::string& bssid, const std::string& password,
uint8_t channel, uint8_t power)
{
const auto size = this->config.networks.size();
@@ -139,6 +140,7 @@ void ProjectConfig::setWifiConfig(const std::string& networkName, const std::str
it->name = networkName;
it->ssid = ssid;
it->bssid = bssid;
it->password = password;
it->channel = channel;
it->power = power;
@@ -150,7 +152,7 @@ void ProjectConfig::setWifiConfig(const std::string& networkName, const std::str
if (size == 0)
{
ESP_LOGI(CONFIGURATION_TAG, "No networks, We're adding a new network");
this->config.networks.emplace_back(this->pref, static_cast<uint8_t>(0), networkName, ssid, password, channel, power);
this->config.networks.emplace_back(this->pref, static_cast<uint8_t>(0), networkName, ssid, bssid, password, channel, power);
// Save the new network immediately
this->config.networks.back().save();
saveNetworkCount(this->pref, 1);
@@ -162,10 +164,10 @@ void ProjectConfig::setWifiConfig(const std::string& networkName, const std::str
{
ESP_LOGI(CONFIGURATION_TAG, "We're adding a new network");
// we don't have that network yet, we can add it as we still have some
// space we're using emplace_back as push_back will create a copy of it,
// space, we're using emplace_back as push_back will create a copy of it,
// we want to avoid that
uint8_t last_index = getNetworkCount(this->pref);
this->config.networks.emplace_back(this->pref, last_index, networkName, ssid, password, channel, power);
this->config.networks.emplace_back(this->pref, last_index, networkName, ssid, bssid, password, channel, power);
// Save the new network immediately
this->config.networks.back().save();
saveNetworkCount(this->pref, static_cast<int>(this->config.networks.size()));

View File

@@ -37,7 +37,8 @@ class ProjectConfig
void setLEDDUtyCycleConfig(int led_external_pwm_duty_cycle);
void setMDNSConfig(const std::string& hostname);
void setCameraConfig(uint8_t vflip, uint8_t framesize, uint8_t href, uint8_t quality, uint8_t brightness);
void setWifiConfig(const std::string& networkName, const std::string& ssid, const std::string& password, uint8_t channel, uint8_t power);
void setWifiConfig(const std::string& networkName, const std::string& ssid, const std::string& bssid, const std::string& password, uint8_t channel,
uint8_t power);
void deleteWifiConfig(const std::string& networkName);

View File

@@ -6,42 +6,6 @@ static const char* TAG = "WiFiScanner";
WiFiScanner::WiFiScanner() {}
void WiFiScanner::scanResultCallback(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
{
auto* scanner = static_cast<WiFiScanner*>(arg);
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_SCAN_DONE)
{
uint16_t ap_count = 0;
esp_wifi_scan_get_ap_num(&ap_count);
if (ap_count == 0)
{
ESP_LOGI(TAG, "No access points found");
return;
}
wifi_ap_record_t* ap_records = new wifi_ap_record_t[ap_count];
ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&ap_count, ap_records));
scanner->networks.clear();
for (uint16_t i = 0; i < ap_count; i++)
{
WiFiNetwork network;
network.ssid = std::string(reinterpret_cast<char*>(ap_records[i].ssid));
network.channel = ap_records[i].primary;
network.rssi = ap_records[i].rssi;
memcpy(network.mac, ap_records[i].bssid, 6);
network.auth_mode = ap_records[i].authmode;
scanner->networks.push_back(network);
}
delete[] ap_records;
ESP_LOGI(TAG, "Found %d access points", ap_count);
}
}
// todo this is garbage
std::vector<WiFiNetwork> WiFiScanner::scanNetworks(int timeout_ms)
{
std::vector<WiFiNetwork> scan_results;
@@ -61,176 +25,67 @@ std::vector<WiFiNetwork> WiFiScanner::scanNetworks(int timeout_ms)
// Stop any ongoing scan
esp_wifi_scan_stop();
// Try sequential channel scanning as a workaround
bool try_sequential_scan = true; // Enable sequential scan
if (!try_sequential_scan)
{
// Normal all-channel scan
wifi_scan_config_t scan_config = {
.ssid = nullptr,
.bssid = nullptr,
.channel = 0, // 0 means scan all channels
.show_hidden = true,
.scan_type = WIFI_SCAN_TYPE_ACTIVE, // Active scan
.scan_time = {.active =
{
.min = 120, // Min per channel
.max = 300 // Max per channel
},
.passive = 360},
.home_chan_dwell_time = 0, // 0 for default
.channel_bitmap = 0 // 0 for all channels
};
err = esp_wifi_scan_start(&scan_config, false);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to start scan: %s", esp_err_to_name(err));
return scan_results;
}
}
else
{
// Sequential channel scan - scan each channel individually with timeout tracking
std::vector<wifi_ap_record_t> all_records;
int64_t start_time = esp_timer_get_time() / 1000; // Convert to ms
for (uint8_t ch = 1; ch <= 13; ch++)
{
// Check if we've exceeded the timeout
int64_t current_time = esp_timer_get_time() / 1000;
int64_t elapsed = current_time - start_time;
if (elapsed >= timeout_ms)
{
ESP_LOGW(TAG, "Sequential scan timeout after %lld ms at channel %d", elapsed, ch);
break;
}
wifi_scan_config_t scan_config = {.ssid = nullptr,
.bssid = nullptr,
.channel = ch,
.show_hidden = true,
.scan_type = WIFI_SCAN_TYPE_ACTIVE,
.scan_time = {.active = {.min = 100, .max = 200}, .passive = 300},
.home_chan_dwell_time = 0,
.channel_bitmap = 0};
err = esp_wifi_scan_start(&scan_config, true); // Blocking scan
if (err == ESP_OK)
{
uint16_t ch_count = 0;
esp_wifi_scan_get_ap_num(&ch_count);
if (ch_count > 0)
{
wifi_ap_record_t* ch_records = new wifi_ap_record_t[ch_count];
if (esp_wifi_scan_get_ap_records(&ch_count, ch_records) == ESP_OK)
{
for (uint16_t i = 0; i < ch_count; i++)
{
all_records.push_back(ch_records[i]);
}
}
delete[] ch_records;
}
}
vTaskDelay(pdMS_TO_TICKS(50));
}
// Process all collected records
for (const auto& record : all_records)
{
WiFiNetwork network;
network.ssid = std::string(reinterpret_cast<const char*>(record.ssid));
network.channel = record.primary;
network.rssi = record.rssi;
memcpy(network.mac, record.bssid, 6);
network.auth_mode = record.authmode;
scan_results.push_back(network);
}
int64_t total_time = (esp_timer_get_time() / 1000) - start_time;
ESP_LOGI(TAG, "Sequential scan completed in %lld ms, found %d APs", total_time, scan_results.size());
// Skip the normal result processing
return scan_results;
}
// Wait for scan completion with timeout
// Sequential channel scan - scan each channel individually with timeout tracking
std::vector<wifi_ap_record_t> all_records;
int64_t start_time = esp_timer_get_time() / 1000; // Convert to ms
int64_t elapsed_ms = 0;
bool scan_done = false;
while (elapsed_ms < timeout_ms)
for (uint8_t ch = 1; ch <= 13; ch++)
{
// Check if scan is actually complete by trying to get AP count
// When scan is done, this will return ESP_OK with a valid count
uint16_t temp_count = 0;
esp_err_t count_err = esp_wifi_scan_get_ap_num(&temp_count);
// Check if we've exceeded the timeout
int64_t current_time = esp_timer_get_time() / 1000;
int64_t elapsed = current_time - start_time;
// If we can successfully get the AP count, the scan is likely complete
// However, we should still wait for the scan to fully finish
if (count_err == ESP_OK && temp_count > 0)
if (elapsed >= timeout_ms)
{
// Give it a bit more time to ensure all channels are scanned
vTaskDelay(pdMS_TO_TICKS(500));
scan_done = true;
ESP_LOGW(TAG, "Sequential scan timeout after %lld ms at channel %d", elapsed, ch);
break;
}
vTaskDelay(pdMS_TO_TICKS(200));
elapsed_ms = (esp_timer_get_time() / 1000) - start_time;
wifi_scan_config_t scan_config = {.ssid = nullptr,
.bssid = nullptr,
.channel = ch,
.show_hidden = true,
.scan_type = WIFI_SCAN_TYPE_ACTIVE,
.scan_time = {.active = {.min = 100, .max = 200}, .passive = 300},
.home_chan_dwell_time = 0,
.channel_bitmap = 0};
err = esp_wifi_scan_start(&scan_config, true); // Blocking scan
if (err == ESP_OK)
{
uint16_t ch_count = 0;
esp_wifi_scan_get_ap_num(&ch_count);
if (ch_count > 0)
{
wifi_ap_record_t* ch_records = new wifi_ap_record_t[ch_count];
if (esp_wifi_scan_get_ap_records(&ch_count, ch_records) == ESP_OK)
{
for (uint16_t i = 0; i < ch_count; i++)
{
all_records.push_back(ch_records[i]);
}
}
delete[] ch_records;
}
}
vTaskDelay(pdMS_TO_TICKS(50));
}
if (!scan_done && elapsed_ms >= timeout_ms)
{
ESP_LOGE(TAG, "Scan timeout after %lld ms", elapsed_ms);
esp_wifi_scan_stop();
return scan_results;
}
// Get scan results
uint16_t ap_count = 0;
esp_wifi_scan_get_ap_num(&ap_count);
if (ap_count == 0)
{
ESP_LOGI(TAG, "No access points found");
return scan_results;
}
wifi_ap_record_t* ap_records = new wifi_ap_record_t[ap_count];
err = esp_wifi_scan_get_ap_records(&ap_count, ap_records);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to get scan records: %s", esp_err_to_name(err));
delete[] ap_records;
return scan_results;
}
// Build the results vector and track channels found
bool channels_found[15] = {false}; // Track channels 0-14
for (uint16_t i = 0; i < ap_count; i++)
// Process all collected records
for (const auto& record : all_records)
{
WiFiNetwork network;
network.ssid = std::string(reinterpret_cast<char*>(ap_records[i].ssid));
network.channel = ap_records[i].primary;
network.rssi = ap_records[i].rssi;
memcpy(network.mac, ap_records[i].bssid, 6);
network.auth_mode = ap_records[i].authmode;
if (network.channel <= 14)
{
channels_found[network.channel] = true;
}
network.ssid = std::string(reinterpret_cast<const char*>(record.ssid));
network.channel = record.primary;
network.rssi = record.rssi;
memcpy(network.mac, record.bssid, 6);
network.auth_mode = record.authmode;
scan_results.push_back(network);
}
delete[] ap_records;
ESP_LOGI(TAG, "Found %d access points", ap_count);
int64_t total_time = (esp_timer_get_time() / 1000) - start_time;
ESP_LOGI(TAG, "Sequential scan completed in %lld ms, found %d APs", total_time, scan_results.size());
// Skip the normal result processing
return scan_results;
}

View File

@@ -21,7 +21,6 @@ class WiFiScanner
public:
WiFiScanner();
std::vector<WiFiNetwork> scanNetworks(int timeout_ms = 15000);
static void scanResultCallback(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data);
private:
std::vector<WiFiNetwork> networks;

View File

@@ -1,4 +1,8 @@
#include "wifiManager.hpp"
#include <charconv>
#include <cstdint>
#include <ranges>
#include <string_view>
static auto WIFI_MANAGER_TAG = "[WIFI_MANAGER]";
@@ -47,7 +51,25 @@ WiFiManager::WiFiManager(std::shared_ptr<ProjectConfig> deviceConfig, QueueHandl
{
}
void WiFiManager::SetCredentials(const char* ssid, const char* password)
std::vector<uint8_t> WiFiManager::ParseBSSID(std::string_view bssid_string)
{
return bssid_string
// We format the bssid/mac address as XX:XX:XX:XX:XX:XX
| std::views::split(':')
// Once we have that, we can convert each sub range into a proper uint8_t value
| std::views::transform(
[](auto&& subrange) -> uint8_t
{
auto view = std::string_view(subrange);
uint8_t result{};
std::from_chars(view.begin(), view.end(), result, 16);
return result;
})
// and now group them into the vector we need
| std::ranges::to<std::vector<uint8_t>>();
}
void WiFiManager::SetCredentials(const char* ssid, const std::vector<uint8_t> bssid, const char* password, bool use_bssid)
{
// Clear the config first
memset(&_wifi_cfg, 0, sizeof(_wifi_cfg));
@@ -62,6 +84,13 @@ void WiFiManager::SetCredentials(const char* ssid, const char* password)
memcpy(_wifi_cfg.sta.password, password, pass_len);
_wifi_cfg.sta.password[pass_len] = '\0';
// if we can use bssid, just copy it. Parser makes sure we do not exceed 6 elements so we should be safe here
// if we fail to parse, the vec will be empty, so use_bssid won't be set
if (use_bssid)
{
std::copy(bssid.begin(), bssid.end(), _wifi_cfg.sta.bssid);
}
// Set other required fields
// Use open auth if no password, otherwise allow any WPA variant
if (strlen(password) == 0)
@@ -81,8 +110,8 @@ void WiFiManager::SetCredentials(const char* ssid, const char* password)
// OPTIMIZATION: Use fast scan instead of all channel scan for quicker connection
_wifi_cfg.sta.scan_method = WIFI_FAST_SCAN;
_wifi_cfg.sta.bssid_set = 0; // Don't use specific BSSID
_wifi_cfg.sta.channel = 0; // Auto channel detection
_wifi_cfg.sta.bssid_set = use_bssid; // Don't use specific BSSID
_wifi_cfg.sta.channel = 0; // Auto channel detection
// Additional settings that might help with compatibility
_wifi_cfg.sta.listen_interval = 0; // Default listen interval
@@ -101,7 +130,8 @@ void WiFiManager::SetCredentials(const char* ssid, const char* password)
void WiFiManager::ConnectWithHardcodedCredentials()
{
SystemEvent event = {EventSource::WIFI, WiFiState_e::WiFiState_ReadyToConnect};
this->SetCredentials(CONFIG_WIFI_SSID, CONFIG_WIFI_PASSWORD);
const auto bssid = this->ParseBSSID(std::string_view(CONFIG_WIFI_BSSID));
this->SetCredentials(CONFIG_WIFI_SSID, bssid, CONFIG_WIFI_PASSWORD, bssid.size());
wifi_mode_t mode;
if (esp_wifi_get_mode(&mode) == ESP_OK)
@@ -170,7 +200,8 @@ void WiFiManager::ConnectWithStoredCredentials()
// Reset retry counter for each network attempt
s_retry_num = 0;
xEventGroupClearBits(s_wifi_event_group, WIFI_FAIL_BIT | WIFI_CONNECTED_BIT);
this->SetCredentials(network.ssid.c_str(), network.password.c_str());
auto bssid = this->ParseBSSID(std::string_view(network.bssid));
this->SetCredentials(network.ssid.c_str(), bssid, network.password.c_str(), bssid.size());
// Update config without stopping WiFi again
ESP_LOGI(WIFI_MANAGER_TAG, "Attempting to connect to SSID: '%s'", network.ssid.c_str());

View File

@@ -7,6 +7,7 @@
#include <algorithm>
#include <cstring>
#include <string>
#include <vector>
#include "WiFiScanner.hpp"
#include "esp_event.h"
@@ -40,10 +41,11 @@ class WiFiManager
int8_t power;
void SetCredentials(const char* ssid, const char* password);
void SetCredentials(const char* ssid, const std::vector<uint8_t> bssid, const char* password, bool use_bssid);
void ConnectWithHardcodedCredentials();
void ConnectWithStoredCredentials();
void SetupAccessPoint();
std::vector<uint8_t> ParseBSSID(std::string_view bssid_string);
public:
WiFiManager(std::shared_ptr<ProjectConfig> deviceConfig, QueueHandle_t eventQueue, StateManager* stateManager);

View File

@@ -93,6 +93,10 @@ menu "OpenIris: WiFi Configuration"
string "WiFi network name (SSID)"
default ""
config WIFI_BSSID
string "WiFi network MAC Address (BSSID), completely optional, used in special cases"
default ""
config WIFI_PASSWORD
string "WiFi password"
default ""

View File

@@ -590,6 +590,7 @@ CONFIG_CAMERA_WIFI_XCLK_FREQ=16500000
# OpenIris: WiFi Configuration
#
CONFIG_WIFI_SSID=""
CONFIG_WIFI_BSSID=""
CONFIG_WIFI_PASSWORD=""
CONFIG_WIFI_AP_SSID="EyeTrackVR"
CONFIG_WIFI_AP_PASSWORD="12345678"