Skip to content

Commit f0ce840

Browse files
schlimmchenAndreasBoehm
authored andcommitted
Feature: support connecting to a fixed Wi-Fi BSSID
1 parent 3d1dbd2 commit f0ce840

File tree

9 files changed

+89
-2
lines changed

9 files changed

+89
-2
lines changed

include/Configuration.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#define WIFI_MAX_SSID_STRLEN 32
1616
#define WIFI_MAX_PASSWORD_STRLEN 64
1717
#define WIFI_MAX_HOSTNAME_STRLEN 31
18+
#define WIFI_BSSID_OCTETS 6
1819

1920
#define SYSLOG_MAX_HOSTNAME_STRLEN 128
2021

@@ -318,6 +319,7 @@ struct CONFIG_T {
318319
struct {
319320
char Ssid[WIFI_MAX_SSID_STRLEN + 1];
320321
char Password[WIFI_MAX_PASSWORD_STRLEN + 1];
322+
uint8_t Bssid[WIFI_BSSID_OCTETS];
321323
uint8_t Ip[4];
322324
uint8_t Netmask[4];
323325
uint8_t Gateway[4];
@@ -471,6 +473,8 @@ class ConfigurationClass {
471473
INVERTER_CONFIG_T* getFreeInverterSlot();
472474
INVERTER_CONFIG_T* getInverterConfig(const uint64_t serial);
473475
void deleteInverterById(const uint8_t id);
476+
static String serializeBssid(uint8_t const* bssid);
477+
static void deserializeBssid(String const& bssidStr, uint8_t* bssid);
474478

475479
int8_t getIndexForLogModule(const String& moduleName) const;
476480

include/defaults.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
#define WIFI_SSID ""
2020
#define WIFI_PASSWORD ""
21+
#define WIFI_BSSID ""
2122
#define WIFI_DHCP true
2223

2324
#define MDNS_ENABLED false

src/Configuration.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ bool ConfigurationClass::write()
276276
JsonObject wifi = doc["wifi"].to<JsonObject>();
277277
wifi["ssid"] = config.WiFi.Ssid;
278278
wifi["password"] = config.WiFi.Password;
279+
wifi["bssid"] = serializeBssid(config.WiFi.Bssid);
279280
wifi["ip"] = IPAddress(config.WiFi.Ip).toString();
280281
wifi["netmask"] = IPAddress(config.WiFi.Netmask).toString();
281282
wifi["gateway"] = IPAddress(config.WiFi.Gateway).toString();
@@ -710,6 +711,7 @@ bool ConfigurationClass::read()
710711
JsonObject wifi = doc["wifi"];
711712
strlcpy(config.WiFi.Ssid, wifi["ssid"] | WIFI_SSID, sizeof(config.WiFi.Ssid));
712713
strlcpy(config.WiFi.Password, wifi["password"] | WIFI_PASSWORD, sizeof(config.WiFi.Password));
714+
deserializeBssid(wifi["bssid"] | WIFI_BSSID, config.WiFi.Bssid);
713715
strlcpy(config.WiFi.Hostname, wifi["hostname"] | APP_HOSTNAME, sizeof(config.WiFi.Hostname));
714716

715717
IPAddress wifi_ip;
@@ -1257,6 +1259,41 @@ int8_t ConfigurationClass::getIndexForLogModule(const String& moduleName) const
12571259
return -1;
12581260
}
12591261

1262+
String ConfigurationClass::serializeBssid(uint8_t const* bssid)
1263+
{
1264+
if (std::all_of(bssid, bssid + WIFI_BSSID_OCTETS, [](uint8_t b) { return b == 0; })) {
1265+
return "";
1266+
}
1267+
1268+
// 2 chars per byte + one separator between bytes + null terminator
1269+
char bssidStr[WIFI_BSSID_OCTETS * 2 + WIFI_BSSID_OCTETS - 1 + 1];
1270+
snprintf(bssidStr, sizeof(bssidStr), "%02X:%02X:%02X:%02X:%02X:%02X",
1271+
bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]);
1272+
return bssidStr;
1273+
}
1274+
1275+
void ConfigurationClass::deserializeBssid(String const& bssidStr, uint8_t* bssid)
1276+
{
1277+
memset(bssid, 0, WIFI_BSSID_OCTETS);
1278+
1279+
String cleanBssidStr = bssidStr;
1280+
cleanBssidStr.replace(":", "");
1281+
cleanBssidStr.replace("-", "");
1282+
1283+
if (cleanBssidStr.length() != 12) { return; }
1284+
1285+
for (int i = 0; i < WIFI_BSSID_OCTETS; i++) {
1286+
char byteStr[3] = {cleanBssidStr[i*2], cleanBssidStr[i*2+1], 0};
1287+
char* endPtr;
1288+
bssid[i] = strtol(byteStr, &endPtr, 16);
1289+
if (endPtr != byteStr + 2) {
1290+
// invalid hex value in BSSID string
1291+
memset(bssid, 0, WIFI_BSSID_OCTETS);
1292+
return;
1293+
}
1294+
}
1295+
}
1296+
12601297
void ConfigurationClass::loop()
12611298
{
12621299
std::unique_lock<std::mutex> lock(sWriterMutex);

src/NetworkSettings.cpp

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,33 @@ void NetworkSettingsClass::applyConfig()
321321
return;
322322
}
323323

324-
const bool newCredentials = strcmp(WiFi.SSID().c_str(), config.Ssid) || strcmp(WiFi.psk().c_str(), config.Password);
324+
uint8_t const* configuredBssid = NULL; // auto
325+
if (std::any_of(std::begin(Configuration.get().WiFi.Bssid),
326+
std::end(Configuration.get().WiFi.Bssid),
327+
[](uint8_t b) { return b != 0; })) {
328+
configuredBssid = Configuration.get().WiFi.Bssid;
329+
}
330+
331+
auto bssidChanged = [configuredBssid]() -> bool {
332+
uint8_t* currentBssid = WiFi.BSSID();
333+
if (configuredBssid == NULL && currentBssid == NULL) {
334+
return false;
335+
}
336+
337+
if (configuredBssid == NULL || currentBssid == NULL) {
338+
return true;
339+
}
340+
341+
for (int i = 0; i < WIFI_BSSID_OCTETS; i++) {
342+
if (configuredBssid[i] != currentBssid[i]) {
343+
return true;
344+
}
345+
}
346+
347+
return false;
348+
};
349+
350+
const bool newCredentials = strcmp(WiFi.SSID().c_str(), config.Ssid) || strcmp(WiFi.psk().c_str(), config.Password) || bssidChanged();
325351

326352
ESP_LOGI(TAG, "Start configuring WiFi STA using %s credentials",
327353
newCredentials ? "new" : "existing");
@@ -330,7 +356,9 @@ void NetworkSettingsClass::applyConfig()
330356
if (newCredentials) {
331357
success = WiFi.begin(
332358
config.Ssid,
333-
config.Password) != WL_CONNECT_FAILED;
359+
config.Password,
360+
0, // channel (0 = auto)
361+
configuredBssid) != WL_CONNECT_FAILED;
334362
} else {
335363
success = WiFi.begin() != WL_CONNECT_FAILED;
336364
}

src/WebApi_network.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ void WebApiNetworkClass::onNetworkAdminGet(AsyncWebServerRequest* request)
7575
root["dns2"] = IPAddress(config.WiFi.Dns2).toString();
7676
root["ssid"] = config.WiFi.Ssid;
7777
root["password"] = config.WiFi.Password;
78+
root["bssid"] = Configuration.serializeBssid(config.WiFi.Bssid);
7879
root["aptimeout"] = config.WiFi.ApTimeout;
7980
root["mdnsenabled"] = config.Mdns.Enabled;
8081
root["syslogenabled"] = config.Syslog.Enabled;
@@ -100,6 +101,7 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
100101

101102
if (!(root["ssid"].is<String>()
102103
&& root["password"].is<String>()
104+
&& root["bssid"].is<String>()
103105
&& root["hostname"].is<String>()
104106
&& root["dhcp"].is<bool>()
105107
&& root["ipaddress"].is<String>()
@@ -216,6 +218,7 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
216218
config.WiFi.Dns2[3] = dns2[3];
217219
strlcpy(config.WiFi.Ssid, root["ssid"].as<String>().c_str(), sizeof(config.WiFi.Ssid));
218220
strlcpy(config.WiFi.Password, root["password"].as<String>().c_str(), sizeof(config.WiFi.Password));
221+
Configuration.deserializeBssid(root["bssid"].as<String>(), config.WiFi.Bssid);
219222
strlcpy(config.WiFi.Hostname, root["hostname"].as<String>().c_str(), sizeof(config.WiFi.Hostname));
220223
if (root["dhcp"].as<bool>()) {
221224
config.WiFi.Dhcp = true;

webapp/src/locales/de.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,8 @@
544544
"WifiConfiguration": "WLAN-Konfiguration",
545545
"WifiSsid": "WLAN-SSID",
546546
"WifiPassword": "WLAN-Passwort",
547+
"WifiBssid": "WLAN-BSSID",
548+
"WifiBssidHint": "Setze die BSSID des WLAN-Access Points, um mit einem bestimmten Access Point zu verbinden. Leer lassen um jeden Access Point zu erlauben.",
547549
"Hostname": "Hostname",
548550
"HostnameHint": "<b>Hinweis:</b> Der Text <span class=\"font-monospace\">%06X</span> wird durch die letzten 6 Ziffern der ESP-ChipID im Hex-Format ersetzt.",
549551
"EnableDhcp": "DHCP aktivieren",

webapp/src/locales/en.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,8 @@
544544
"WifiConfiguration": "WiFi Configuration",
545545
"WifiSsid": "WiFi SSID",
546546
"WifiPassword": "WiFi Password",
547+
"WifiBssid": "WiFi BSSID",
548+
"WifiBssidHint": "Set the BSSID of the WiFi Access Point to connect to. Leave empty to allow connecting to any Access Point.",
547549
"Hostname": "Hostname",
548550
"HostnameHint": "<b>Hint:</b> The text <span class=\"font-monospace\">%06X</span> will be replaced with the last 6 digits of the ESP ChipID in hex format.",
549551
"EnableDhcp": "Enable DHCP",

webapp/src/types/NetworkConfig.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export interface NetworkConfig {
22
ssid: string;
33
password: string;
4+
bssid: string;
45
hostname: string;
56
dhcp: boolean;
67
ipaddress: string;

webapp/src/views/NetworkAdminView.vue

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@
2525
maxlength="64"
2626
/>
2727

28+
<InputElement
29+
:label="$t('networkadmin.WifiBssid')"
30+
:tooltip="$t('networkadmin.WifiBssidHint')"
31+
v-model="networkConfigList.bssid"
32+
type="text"
33+
minlength="12"
34+
maxlength="17"
35+
/>
36+
2837
<InputElement
2938
:label="$t('networkadmin.Hostname')"
3039
v-model="networkConfigList.hostname"

0 commit comments

Comments
 (0)