Skip to content
Open
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: 3 additions & 1 deletion LoggerFirmware/include/Configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ class Config {
CONFIG_STATION_DELAY_S, /* String: delay (seconds) before web-server attempts to re-joint a client network */
CONFIG_STATION_RETRIES_S,/* String: retries (int) before web-server reverts to "safe-mode". */
CONFIG_STATION_TIMEOUT_S,/* String: delay (seconds) before declaring a WiFi connection attempt failed */
CONFIG_STATION_SCAN_INTERVAL_S,/* String: delay (seconds) between background hotspot scans in AP fallback mode */
CONFIG_WS_STATUS_S, /* String: status of the configuration web server */
CONFIG_WS_BOOTSTATUS_S, /* String: status of the configuration web server at boot time */
CONFIG_DEFAULTS_S, /* String: JSON-format for default "lab reset" parameters */
Expand All @@ -99,7 +100,8 @@ class Config {
CONFIG_UPLOAD_INTERVAL_S,/* String: interval (seconds) between upload attempts */
CONFIG_UPLOAD_DURATION_S,/* String: duration (seconds) for each upload event */
CONFIG_UPLOAD_CERT_S, /* String: certificate to pass to upload server for authentication */
CONFIG_MDNS_NAME_S /* String: recognition name for mDNS responder (hostname: name.local) */
CONFIG_MDNS_NAME_S, /* String: recognition name for mDNS responder (hostname: name.local) */
CONFIG_REQUIRE_PMF_S /* String: Require PMF for WPA3 connections (true/false) */
};

/// \brief Extract a configuration string for the specified parameter
Expand Down
10 changes: 8 additions & 2 deletions LoggerFirmware/src/Configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ const String lookup[] = {
"StationDelay", ///< Set the timeout between attempts of the webserver joining a client network
"StationRetries", ///< Set number of join attempts before the webserver reverts to safe mode
"StationTimeout", ///< Set the timeout for any connect attempt
"StationScanInterval", ///< Set the background scan interval (seconds) in AP fallback mode
"WSStatus", ///< The current status of the configuration webserver
"WSBootStatus", ///< The status of the webserver on boot
"LabDefaults", ///< A JSON string for lab-default configuration
Expand All @@ -86,7 +87,8 @@ const String lookup[] = {
"UploadInterval", ///< Interval (seconds) between upload attempts
"UploadDuration", ///< Time (seconds) for upload activity before diverting back to other efforts
"UploadCert", ///< Certificate to pass to the upload server for TLS
"mDNSName"
"mDNSName",
"RequirePMF" ///< Require PMF for WPA3 (string)
};

/// Default constructor. This sets up for a dummy parameter store, which is configured
Expand Down Expand Up @@ -227,13 +229,14 @@ DynamicJsonDocument ConfigJSON::ExtractConfig(bool secure)
params["enable"]["upload"] = upload_online;

// String configurations for the various parameters in configuration
String wifi_station_delay, wifi_station_retries, wifi_station_timeout, wifi_ip_address, wifi_mode;
String wifi_station_delay, wifi_station_retries, wifi_station_timeout, wifi_station_scan_interval, wifi_ip_address, wifi_mode;
String wifi_ap_ssid, wifi_ap_password, wifi_station_ssid, wifi_station_password, wifi_station_mdns_name;
String moduleid, shipname, baudrate_port1, baudrate_port2, udp_bridge_port;

LoggerConfig.GetConfigString(Config::CONFIG_STATION_DELAY_S, wifi_station_delay);
LoggerConfig.GetConfigString(Config::CONFIG_STATION_RETRIES_S, wifi_station_retries);
LoggerConfig.GetConfigString(Config::CONFIG_STATION_TIMEOUT_S, wifi_station_timeout);
LoggerConfig.GetConfigString(Config::CONFIG_STATION_SCAN_INTERVAL_S, wifi_station_scan_interval);
LoggerConfig.GetConfigString(Config::CONFIG_MODULEID_S, moduleid);
LoggerConfig.GetConfigString(Config::CONFIG_SHIPNAME_S, shipname);
LoggerConfig.GetConfigString(Config::CONFIG_AP_SSID_S, wifi_ap_ssid);
Expand All @@ -251,6 +254,7 @@ DynamicJsonDocument ConfigJSON::ExtractConfig(bool secure)
params["wifi"]["station"]["delay"] = wifi_station_delay.toInt();
params["wifi"]["station"]["retries"] = wifi_station_retries.toInt();
params["wifi"]["station"]["timeout"] = wifi_station_timeout.toInt();
params["wifi"]["station"]["scaninterval"] = wifi_station_scan_interval.toInt();
params["wifi"]["station"]["mdns"] = wifi_station_mdns_name;
params["wifi"]["ssids"]["ap"] = wifi_ap_ssid;
params["wifi"]["ssids"]["station"] = wifi_station_ssid;
Expand Down Expand Up @@ -329,6 +333,8 @@ bool ConfigJSON::SetConfig(String const& json_string)
LoggerConfig.SetConfigString(Config::CONFIG_STATION_RETRIES_S, params["wifi"]["station"]["retries"]);
if (params["wifi"]["station"].containsKey("timeout"))
LoggerConfig.SetConfigString(Config::CONFIG_STATION_TIMEOUT_S, params["wifi"]["station"]["timeout"]);
if (params["wifi"]["station"].containsKey("scaninterval"))
LoggerConfig.SetConfigString(Config::CONFIG_STATION_SCAN_INTERVAL_S, params["wifi"]["station"]["scaninterval"]);
if (params["wifi"]["station"].containsKey("mdns"))
LoggerConfig.SetConfigString(Config::CONFIG_MDNS_NAME_S, params["wifi"]["station"]["mdns"]);
}
Expand Down
113 changes: 105 additions & 8 deletions LoggerFirmware/src/WiFiAdapter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include <WiFiAP.h>
#include <ESPmDNS.h>
#include <WebServer.h>
#include <esp_wifi.h>
#include <WifiClient.h>
#include <LittleFS.h>
#include <ESP32-targz.h>
Expand Down Expand Up @@ -134,6 +135,8 @@ class ConnectionStateMachine {
Serial.printf("DBG: starting ConnectionStateMachine; boot status is %s\n", boot_status.c_str());
}
m_lastConnectAttempt = m_lastStatusCheck = millis();
m_lastScanTime = 0;
m_scanStarted = false;

m_connectionRetries = maximumReties();
m_retryDelay = retryDelay();
Expand Down Expand Up @@ -177,7 +180,50 @@ class ConnectionStateMachine {
case STOPPED:
break;
case AP_MODE:
// Once in AP mode and established, we stay in the state.
if (WiFiAdapter::GetWirelessMode() == WiFiAdapter::WirelessMode::ADAPTER_STATION) {
if (!m_scanStarted) {
String scan_interval_s;
long scan_interval_ms = 30000;
if (logger::LoggerConfig.GetConfigString(logger::Config::ConfigParam::CONFIG_STATION_SCAN_INTERVAL_S, scan_interval_s)) {
if (scan_interval_s.toInt() > 0) scan_interval_ms = scan_interval_s.toInt() * 1000;
}
if (m_lastScanTime == 0 || (now - m_lastScanTime) > scan_interval_ms) {
if (m_verbose) Serial.printf("DBG: triggering background scan (interval %ld ms)...\n", scan_interval_ms);
WiFi.scanNetworks(true); // true = async non-blocking
m_scanStarted = true;
m_lastScanTime = now;
}
} else {
// Scan is running asynchronously, poll for completion
int16_t n = WiFi.scanComplete();
if (n >= 0) {
String targetSsid;
logger::LoggerConfig.GetConfigString(logger::Config::ConfigParam::CONFIG_STATION_SSID_S, targetSsid);
if (m_verbose) {
Serial.printf("DBG: background scan returned %d results. targetSsid='%s'\n", n, targetSsid.c_str());
}
bool found = false;
for (int i = 0; i < n; ++i) {
if (m_verbose) {
Serial.printf("DBG: - Scan %d: '%s' (RSSI: %d)\n", i, WiFi.SSID(i).c_str(), WiFi.RSSI(i));
}
if (WiFi.SSID(i) == targetSsid) {
found = true;
break;
}
}
WiFi.scanDelete(); // Memory cleanup
m_scanStarted = false;
if (found) {
if (m_verbose) Serial.printf("DBG: found target hotspot %s over the air, dropping AP to reconnect...\n", targetSsid.c_str());
if (attemptStationJoin()) m_currentState = STATION_CONNECTED;
else m_currentState = STATION_CONNECTING;
}
} else if (n == WIFI_SCAN_FAILED) {
m_scanStarted = false; // reset and try again later
}
}
}
break;
case STATION_CONNECTING:
// We're waiting for the connection to complete, so check status
Expand Down Expand Up @@ -229,15 +275,17 @@ class ConnectionStateMachine {
// We're out of retries for a station connection, so we have to assume
// that the network isn't there, or there's a problem with the password
// etc. -- so we revert to AP mode.
WiFiAdapter::SetWirelessMode(WiFiAdapter::WirelessMode::ADAPTER_SOFTAP);
logger::LoggerConfig.SetConfigString(logger::Config::ConfigParam::CONFIG_WS_STATUS_S, "AP-Enabled,Station-Join-Failed");
if (m_verbose)
Serial.print("DBG: set status to Station-Join-Failed, rebooting to AP safe mode.\n");
ESP.restart();
logger::LoggerConfig.SetConfigString(logger::Config::ConfigParam::CONFIG_WS_STATUS_S, "AP-Fallback,Station-Join-Failed");
WiFi.disconnect(); // Stop background AutoReconnect spam so scans can run
WiFi.mode(WIFI_AP_STA); // Ensure both AP and Station interfaces are up for scanning
apSetup();
m_currentState = AP_MODE;
m_lastScanTime = now; // Delay first scan by interval after dropping to AP
break;
case STATION_CONNECTED:
// The system (finally?) connected, so we update status, and then go into
// connection checking mode.
m_connectionRetries = maximumReties(); // Reset retry count for future dropouts
logger::LoggerConfig.SetConfigString(logger::Config::ConfigParam::CONFIG_WS_STATUS_S, "Station-Enabled,Connected");
m_currentState = CONNECTION_CHECK;
if (m_verbose) {
Expand Down Expand Up @@ -277,6 +325,8 @@ class ConnectionStateMachine {
bool m_verbose; // Flag for debug information to happen
int m_lastConnectAttempt; // Time (ms) for last connection attempt
int m_lastStatusCheck; // Time (ms) for last connection status attempt
int m_lastScanTime; // Time (ms) for background scan triggers
bool m_scanStarted; // Whether an async scan is running
int m_connectionRetries; // Count of remaining connection attempts
int m_retryDelay; // Delay (ms) before attempt to retry to connect
int m_statusDelay; // Delay (ms) before checking connection status again
Expand All @@ -291,6 +341,13 @@ class ConnectionStateMachine {
// logger first boots, the WIFi comes up with known SSID and password.
if (ssid.length() == 0) ssid = "wibl-config";
if (ssid.length() == 0) password = "wibl-config-password";

String logger_name;
logger::LoggerConfig.GetConfigString(logger::Config::CONFIG_MDNS_NAME_S, logger_name);
if (logger_name.length() > 0) {
WiFi.softAPsetHostname(logger_name.c_str());
}

WiFi.softAP(ssid.c_str(), password.c_str());
WiFi.setSleep(false);
IPAddress server_address = WiFi.softAPIP();
Expand Down Expand Up @@ -322,8 +379,48 @@ class ConnectionStateMachine {
Serial.print("ERR: attempting to join a WiFi network as a station without a specified SSID\n");
return false;
}
wl_status_t status = WiFi.begin(ssid.c_str(), password.c_str());

// Configure WPA3/PMF fallback & parameters for modern hotspots
WiFi.mode(WIFI_STA);

String logger_name;
logger::LoggerConfig.GetConfigString(logger::Config::CONFIG_MDNS_NAME_S, logger_name);
if (logger_name.length() > 0) {
WiFi.setHostname(logger_name.c_str());
}

wifi_config_t conf;
esp_wifi_get_config(WIFI_IF_STA, &conf);

bool require_pmf = false;
String require_pmf_str;
if (logger::LoggerConfig.GetConfigString(logger::Config::ConfigParam::CONFIG_REQUIRE_PMF_S, require_pmf_str)) {
require_pmf = require_pmf_str.equalsIgnoreCase("true") || require_pmf_str == "1";
}

if (m_verbose) {
Serial.printf("DBG: WPA3 PMF configured as %s\n", require_pmf ? "REQUIRED" : "CAPABLE-ONLY");
}

WiFi.disconnect(true);
delay(100);
WiFi.setSleep(false);

// Blank and build the sta configuration struct manually so we can set WPA3 options
memset(&conf, 0, sizeof(conf));
memcpy(conf.sta.ssid, ssid.c_str(), ssid.length());
memcpy(conf.sta.password, password.c_str(), password.length());

conf.sta.pmf_cfg.capable = true;
conf.sta.pmf_cfg.required = require_pmf;
#ifdef WPA3_SAE_PWE_BOTH
conf.sta.sae_pwe_h2e = WPA3_SAE_PWE_BOTH;
#endif
esp_wifi_set_config(WIFI_IF_STA, &conf);
esp_wifi_set_bandwidth(WIFI_IF_STA, WIFI_BW_HT20);

wl_status_t status = WiFi.begin(); // DO NOT pass ssid/password here, it overwrites the PMF config we just set!

m_lastConnectAttempt = millis();
if (m_verbose) {
Serial.printf("DBG: started network join on %s:%s at %d with immediate status %d\n", ssid.c_str(), password.c_str(), m_lastConnectAttempt, (int)status);
Expand Down Expand Up @@ -469,7 +566,7 @@ class ESP32WiFiAdapter : public WiFiAdapter {
m_server->serveStatic("/logs", m_storage->Controller(), "/logs/");
m_server->serveStatic("/", LittleFS, "/website/"); // Note trailing '/' since this is a directory being served.
}
//m_state.Verbose(true);
m_state.Verbose(m_verbose);
m_state.Start();
m_server->begin();
return true;
Expand Down
3 changes: 3 additions & 0 deletions LoggerFirmware/website/actions/configure.htm
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ <h1 class="title">Wireless Inexpensive Bathymetry Logger</h1>
<label for="join-timeout">Join Timeout (s)</label>
<input type="number" name="jointimeout" id="join-timeout"/>
<br>
<label for="scan-interval">Fallback AP Scan Interval (s)</label>
<input type="number" name="scaninterval" id="scan-interval"/>
<br>
</fieldset>
<fieldset class="internal">
<legend>Identification</legend>
Expand Down
3 changes: 3 additions & 0 deletions LoggerFirmware/website/js/configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ function createJSONConfig() {
const stationDelay = document.getElementById("retry-delay").value;
const stationRetries = document.getElementById("retry-count").value;
const stationTimeout = document.getElementById("join-timeout").value;
const stationScanInterval = document.getElementById("scan-interval").value;
const mdnsName = document.getElementById("mdns-name").value;
const apSSID = document.getElementById("ap-ssid").value;
const stationSSID = document.getElementById("station-ssid").value;
Expand Down Expand Up @@ -77,6 +78,7 @@ function createJSONConfig() {
"delay": ${stationDelay},
"retries": ${stationRetries},
"timeout": ${stationTimeout},
"scaninterval": ${stationScanInterval},
"mdns": "${mdnsName}"
},
"ssids": {
Expand Down Expand Up @@ -127,6 +129,7 @@ function parseConfigJSON(config) {
document.getElementById("retry-delay").value = config.wifi.station.delay;
document.getElementById("retry-count").value = config.wifi.station.retries;
document.getElementById("join-timeout").value = config.wifi.station.timeout;
document.getElementById("scan-interval").value = config.wifi.station.scaninterval;
document.getElementById("mdns-name").value = config.wifi.station.mdns;
document.getElementById("ap-ssid").value = config.wifi.ssids.ap;
document.getElementById("station-ssid").value = config.wifi.ssids.station;
Expand Down