Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions include/Configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#define WIFI_MAX_SSID_STRLEN 32
#define WIFI_MAX_PASSWORD_STRLEN 64
#define WIFI_MAX_HOSTNAME_STRLEN 31
#define WIFI_BSSID_OCTETS 6

#define SYSLOG_MAX_HOSTNAME_STRLEN 128

Expand Down Expand Up @@ -318,6 +319,7 @@ struct CONFIG_T {
struct {
char Ssid[WIFI_MAX_SSID_STRLEN + 1];
char Password[WIFI_MAX_PASSWORD_STRLEN + 1];
uint8_t Bssid[WIFI_BSSID_OCTETS];
uint8_t Ip[4];
uint8_t Netmask[4];
uint8_t Gateway[4];
Expand Down Expand Up @@ -471,6 +473,8 @@ class ConfigurationClass {
INVERTER_CONFIG_T* getFreeInverterSlot();
INVERTER_CONFIG_T* getInverterConfig(const uint64_t serial);
void deleteInverterById(const uint8_t id);
static String serializeBssid(uint8_t const* bssid);
static void deserializeBssid(String const& bssidStr, uint8_t* bssid);

int8_t getIndexForLogModule(const String& moduleName) const;

Expand Down
1 change: 1 addition & 0 deletions include/defaults.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

#define WIFI_SSID ""
#define WIFI_PASSWORD ""
#define WIFI_BSSID ""
#define WIFI_DHCP true

#define MDNS_ENABLED false
Expand Down
37 changes: 37 additions & 0 deletions src/Configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ bool ConfigurationClass::write()
JsonObject wifi = doc["wifi"].to<JsonObject>();
wifi["ssid"] = config.WiFi.Ssid;
wifi["password"] = config.WiFi.Password;
wifi["bssid"] = serializeBssid(config.WiFi.Bssid);
wifi["ip"] = IPAddress(config.WiFi.Ip).toString();
wifi["netmask"] = IPAddress(config.WiFi.Netmask).toString();
wifi["gateway"] = IPAddress(config.WiFi.Gateway).toString();
Expand Down Expand Up @@ -710,6 +711,7 @@ bool ConfigurationClass::read()
JsonObject wifi = doc["wifi"];
strlcpy(config.WiFi.Ssid, wifi["ssid"] | WIFI_SSID, sizeof(config.WiFi.Ssid));
strlcpy(config.WiFi.Password, wifi["password"] | WIFI_PASSWORD, sizeof(config.WiFi.Password));
deserializeBssid(wifi["bssid"] | WIFI_BSSID, config.WiFi.Bssid);
strlcpy(config.WiFi.Hostname, wifi["hostname"] | APP_HOSTNAME, sizeof(config.WiFi.Hostname));

IPAddress wifi_ip;
Expand Down Expand Up @@ -1257,6 +1259,41 @@ int8_t ConfigurationClass::getIndexForLogModule(const String& moduleName) const
return -1;
}

String ConfigurationClass::serializeBssid(uint8_t const* bssid)
{
if (std::all_of(bssid, bssid + WIFI_BSSID_OCTETS, [](uint8_t b) { return b == 0; })) {
return "";
}

// 2 chars per byte + one separator between bytes + null terminator
char bssidStr[WIFI_BSSID_OCTETS * 2 + WIFI_BSSID_OCTETS - 1 + 1];
snprintf(bssidStr, sizeof(bssidStr), "%02X:%02X:%02X:%02X:%02X:%02X",
bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]);
return bssidStr;
}

void ConfigurationClass::deserializeBssid(String const& bssidStr, uint8_t* bssid)
{
memset(bssid, 0, WIFI_BSSID_OCTETS);

String cleanBssidStr = bssidStr;
cleanBssidStr.replace(":", "");
cleanBssidStr.replace("-", "");

if (cleanBssidStr.length() != 12) { return; }

Comment on lines +1283 to +1284
Copy link

Copilot AI May 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider logging a warning or error when the BSSID string length is invalid. This can help diagnose issues when an incorrect BSSID is provided, instead of silently failing.

Suggested change
if (cleanBssidStr.length() != 12) { return; }
if (cleanBssidStr.length() != 12) {
MessageOutput::logWarning("Invalid BSSID string length: " + String(cleanBssidStr.length()) + ". Expected length is 12.");
return;
}

Copilot uses AI. Check for mistakes.
for (int i = 0; i < WIFI_BSSID_OCTETS; i++) {
char byteStr[3] = {cleanBssidStr[i*2], cleanBssidStr[i*2+1], 0};
char* endPtr;
bssid[i] = strtol(byteStr, &endPtr, 16);
if (endPtr != byteStr + 2) {
// invalid hex value in BSSID string
memset(bssid, 0, WIFI_BSSID_OCTETS);
return;
}
}
}

void ConfigurationClass::loop()
{
std::unique_lock<std::mutex> lock(sWriterMutex);
Expand Down
32 changes: 30 additions & 2 deletions src/NetworkSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,33 @@ void NetworkSettingsClass::applyConfig()
return;
}

const bool newCredentials = strcmp(WiFi.SSID().c_str(), config.Ssid) || strcmp(WiFi.psk().c_str(), config.Password);
uint8_t const* configuredBssid = NULL; // auto
if (std::any_of(std::begin(Configuration.get().WiFi.Bssid),
std::end(Configuration.get().WiFi.Bssid),
[](uint8_t b) { return b != 0; })) {
configuredBssid = Configuration.get().WiFi.Bssid;
}

auto bssidChanged = [configuredBssid]() -> bool {
uint8_t* currentBssid = WiFi.BSSID();
if (configuredBssid == NULL && currentBssid == NULL) {
return false;
}

if (configuredBssid == NULL || currentBssid == NULL) {
return true;
}

for (int i = 0; i < WIFI_BSSID_OCTETS; i++) {
Copy link

Copilot AI May 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider using std::memcmp to compare the BSSID arrays, which may simplify the code and improve clarity.

Copilot uses AI. Check for mistakes.
if (configuredBssid[i] != currentBssid[i]) {
return true;
}
}

return false;
};

const bool newCredentials = strcmp(WiFi.SSID().c_str(), config.Ssid) || strcmp(WiFi.psk().c_str(), config.Password) || bssidChanged();

ESP_LOGI(TAG, "Start configuring WiFi STA using %s credentials",
newCredentials ? "new" : "existing");
Expand All @@ -330,7 +356,9 @@ void NetworkSettingsClass::applyConfig()
if (newCredentials) {
success = WiFi.begin(
config.Ssid,
config.Password) != WL_CONNECT_FAILED;
config.Password,
0, // channel (0 = auto)
configuredBssid) != WL_CONNECT_FAILED;
} else {
success = WiFi.begin() != WL_CONNECT_FAILED;
}
Expand Down
3 changes: 3 additions & 0 deletions src/WebApi_network.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ void WebApiNetworkClass::onNetworkAdminGet(AsyncWebServerRequest* request)
root["dns2"] = IPAddress(config.WiFi.Dns2).toString();
root["ssid"] = config.WiFi.Ssid;
root["password"] = config.WiFi.Password;
root["bssid"] = Configuration.serializeBssid(config.WiFi.Bssid);
root["aptimeout"] = config.WiFi.ApTimeout;
root["mdnsenabled"] = config.Mdns.Enabled;
root["syslogenabled"] = config.Syslog.Enabled;
Expand All @@ -100,6 +101,7 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)

if (!(root["ssid"].is<String>()
&& root["password"].is<String>()
&& root["bssid"].is<String>()
&& root["hostname"].is<String>()
&& root["dhcp"].is<bool>()
&& root["ipaddress"].is<String>()
Expand Down Expand Up @@ -216,6 +218,7 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
config.WiFi.Dns2[3] = dns2[3];
strlcpy(config.WiFi.Ssid, root["ssid"].as<String>().c_str(), sizeof(config.WiFi.Ssid));
strlcpy(config.WiFi.Password, root["password"].as<String>().c_str(), sizeof(config.WiFi.Password));
Configuration.deserializeBssid(root["bssid"].as<String>(), config.WiFi.Bssid);
strlcpy(config.WiFi.Hostname, root["hostname"].as<String>().c_str(), sizeof(config.WiFi.Hostname));
if (root["dhcp"].as<bool>()) {
config.WiFi.Dhcp = true;
Expand Down
2 changes: 2 additions & 0 deletions webapp/src/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,8 @@
"WifiConfiguration": "WLAN-Konfiguration",
"WifiSsid": "WLAN-SSID",
"WifiPassword": "WLAN-Passwort",
"WifiBssid": "WLAN-BSSID",
"WifiBssidHint": "Setze die BSSID des WLAN-Access Points, um mit einem bestimmten Access Point zu verbinden. Leer lassen um jeden Access Point zu erlauben.",
"Hostname": "Hostname",
"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.",
"EnableDhcp": "DHCP aktivieren",
Expand Down
2 changes: 2 additions & 0 deletions webapp/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,8 @@
"WifiConfiguration": "WiFi Configuration",
"WifiSsid": "WiFi SSID",
"WifiPassword": "WiFi Password",
"WifiBssid": "WiFi BSSID",
"WifiBssidHint": "Set the BSSID of the WiFi Access Point to connect to. Leave empty to allow connecting to any Access Point.",
"Hostname": "Hostname",
"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.",
"EnableDhcp": "Enable DHCP",
Expand Down
1 change: 1 addition & 0 deletions webapp/src/types/NetworkConfig.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export interface NetworkConfig {
ssid: string;
password: string;
bssid: string;
hostname: string;
dhcp: boolean;
ipaddress: string;
Expand Down
9 changes: 9 additions & 0 deletions webapp/src/views/NetworkAdminView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@
maxlength="64"
/>

<InputElement
:label="$t('networkadmin.WifiBssid')"
:tooltip="$t('networkadmin.WifiBssidHint')"
v-model="networkConfigList.bssid"
type="text"
minlength="12"
maxlength="17"
/>

<InputElement
:label="$t('networkadmin.Hostname')"
v-model="networkConfigList.hostname"
Expand Down