Skip to content

Commit

Permalink
Merge pull request #579 from SignalK/http_server_2
Browse files Browse the repository at this point in the history
WiFi connection logic improvements
  • Loading branch information
mairas authored Mar 9, 2022
2 parents f1d5f6b + d017ba4 commit b4de838
Show file tree
Hide file tree
Showing 9 changed files with 191 additions and 98 deletions.
3 changes: 2 additions & 1 deletion library.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@
},
{
"owner": "alanswx",
"name": "ESPAsyncWiFiManager"
"name": "ESPAsyncWiFiManager",
"version": "^0.31"
},
{
"owner": "bblanchon",
Expand Down
10 changes: 5 additions & 5 deletions src/sensesp/controllers/system_status_controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@

namespace sensesp {

void SystemStatusController::set_input(WifiState new_value,
void SystemStatusController::set_input(WiFiState new_value,
uint8_t input_channel) {
// FIXME: If pointers to member functions would be held in an array,
// this would be a simple array dereferencing
switch (new_value) {
case WifiState::kWifiNoAP:
case WiFiState::kWifiNoAP:
this->update_state(SystemStatus::kWifiNoAP);
break;
case WifiState::kWifiDisconnected:
case WiFiState::kWifiDisconnected:
this->update_state(SystemStatus::kWifiDisconnected);
break;
case WifiState::kWifiConnectedToAP:
case WiFiState::kWifiConnectedToAP:
this->update_state(SystemStatus::kWSDisconnected);
break;
case WifiState::kWifiManagerActivated:
case WiFiState::kWifiManagerActivated:
this->update_state(SystemStatus::kWifiManagerActivated);
break;
}
Expand Down
6 changes: 3 additions & 3 deletions src/sensesp/controllers/system_status_controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ enum class SystemStatus {
* set_wifi_* and set_ws_* methods to take the relevant action when such
* an event occurs.
*/
class SystemStatusController : public ValueConsumer<WifiState>,
class SystemStatusController : public ValueConsumer<WiFiState>,
public ValueConsumer<WSConnectionState>,
public ValueProducer<SystemStatus> {
public:
SystemStatusController() {}

/// ValueConsumer interface for ValueConsumer<WifiState> (Networking object
/// ValueConsumer interface for ValueConsumer<WiFiState> (Networking object
/// state updates)
virtual void set_input(WifiState new_value,
virtual void set_input(WiFiState new_value,
uint8_t input_channel = 0) override;
/// ValueConsumer interface for ValueConsumer<WSConnectionState>
/// (WSClient object state updates)
Expand Down
18 changes: 18 additions & 0 deletions src/sensesp/net/http_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,24 @@ HTTPServer::HTTPServer() : Startable(50) {
server->on("/info", HTTP_GET, std::bind(&HTTPServer::handle_info, this, _1));
}



void HTTPServer::start() {
// only start the server if WiFi is connected
if (WiFi.status() == WL_CONNECTED) {
server->begin();
debugI("HTTP server started");
} else {
debugW("HTTP server not started, WiFi not connected");
}
WiFi.onEvent([this](WiFiEvent_t event, WiFiEventInfo_t info) {
if (event == SYSTEM_EVENT_STA_GOT_IP) {
server->begin();
debugI("HTTP server started");
}
});
}

void HTTPServer::handle_not_found(AsyncWebServerRequest* request) {
debugD("NOT_FOUND: ");
if (request->method() == HTTP_GET) {
Expand Down
2 changes: 1 addition & 1 deletion src/sensesp/net/http_server.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class HTTPServer : public Startable {
public:
HTTPServer();
~HTTPServer() { delete server; }
virtual void start() override { server->begin(); }
virtual void start() override;
void handle_not_found(AsyncWebServerRequest* request);
void handle_config(AsyncWebServerRequest* request);
void handle_device_reset(AsyncWebServerRequest* request);
Expand Down
202 changes: 125 additions & 77 deletions src/sensesp/net/networking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,40 +14,59 @@ namespace sensesp {
#define WIFI_CONFIG_PORTAL_TIMEOUT 180
#endif

bool should_save_config = false;

void save_config_callback() { should_save_config = true; }
// Network configuration logic:
// 1. Use hard-coded hostname and WiFi credentials by default
// 2. If the hostname or credentials have been changed in WiFiManager or
// the web UI, use the updated values.
// 3. If the hard-coded hostname is changed, use that instead of the saved one.
// (But keep using the saved WiFi credentials!)

Networking::Networking(String config_path, String ssid, String password,
String hostname, const char* wifi_manager_password)
: Configurable{config_path},
wifi_manager_password_{wifi_manager_password},
Startable(80),
Resettable(0) {
this->output = WifiState::kWifiNoAP;
this->output = WiFiState::kWifiNoAP;

preset_ssid = ssid;
preset_password = password;
preset_hostname = hostname;
default_hostname = hostname;

load_configuration();

if (!ssid.isEmpty()) {
debugI("Using hard-coded SSID %s and password", ssid.c_str());
this->ap_ssid = ssid;
this->ap_password = password;
} else {
load_configuration();
if (default_hostname != preset_hostname) {
// if the preset hostname has changed, use it instead of the loaded one
SensESPBaseApp::get()->get_hostname_observable()->set(preset_hostname);
default_hostname = preset_hostname;
}

server = new AsyncWebServer(80);
dns = new DNSServer();
wifi_manager = new AsyncWiFiManager(server, dns);
}

void Networking::start() {
debugD("Enabling Networking object");

// If we have preset or saved WiFi config, always use it. Otherwise,
// start WiFiManager. WiFiManager always starts the configuration portal
// instead of trying to connect.

if (ap_ssid != "" && ap_password != "") {
debugI("Using SSID %s", ap_ssid.c_str());
setup_saved_ssid();
} else if (ap_ssid == "" && WiFi.status() != WL_CONNECTED &&
wifi_manager_enabled_) {
debugI("Starting WiFiManager");
setup_wifi_manager();
}
if (ap_ssid == "" && WiFi.status() != WL_CONNECTED) {
// otherwise, fall through and WiFi will remain disconnected
}

void Networking::activate_wifi_manager() {
debugD("Activating WiFiManager");
if (WiFi.status() != WL_CONNECTED) {
setup_wifi_manager();
}
}
Expand All @@ -63,132 +82,161 @@ void Networking::setup_wifi_callbacks() {
WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED);
}

/**
* @brief Start WiFi using preset SSID and password.
*/
void Networking::setup_saved_ssid() {
this->emit(WifiState::kWifiDisconnected);
this->emit(WiFiState::kWifiDisconnected);
setup_wifi_callbacks();

const char* hostname = SensESPBaseApp::get_hostname().c_str();

WiFi.setHostname(hostname);

WiFi.begin(ap_ssid.c_str(), ap_password.c_str());

debugI("Connecting to wifi %s.", ap_ssid.c_str());
String hostname = SensESPBaseApp::get_hostname();
WiFi.setHostname(hostname.c_str());

auto reconnect_cb = [this]() {
if (WiFi.status() != WL_CONNECTED) {
debugI("Connecting to wifi SSID %s.", ap_ssid.c_str());
WiFi.begin(ap_ssid.c_str(), ap_password.c_str());
}
};

// Perform an initial connection without a delay.
reconnect_cb();

// Launch a separate onRepeat reaction to (re-)establish WiFi connection.
// Connecting is attempted only every 20 s to allow the previous connection
// attempt to complete even if the network is slow.
ReactESP::app->onRepeat(20000, reconnect_cb);
}

/**
* This method gets called when WiFi is connected to the AP and has
* received an IP address.
*/
void Networking::wifi_station_connected() {
debugI("Connected to wifi, SSID: %s (signal: %d)", WiFi.SSID().c_str(),
WiFi.RSSI());
debugI("IP address of Device: %s", WiFi.localIP().toString().c_str());
debugI("Default route: %s", WiFi.gatewayIP().toString().c_str());
debugI("DNS server: %s", WiFi.dnsIP().toString().c_str());
this->emit(WifiState::kWifiConnectedToAP);
this->emit(WiFiState::kWifiConnectedToAP);
}

/**
* This method gets called when WiFi is disconnected from the AP.
*/
void Networking::wifi_station_disconnected() {
debugI("Disconnected from wifi.");
this->emit(WifiState::kWifiDisconnected);
this->emit(WiFiState::kWifiDisconnected);
}

/**
* @brief Start WiFi using WiFi Manager.
*
* If the setup process has been completed before, this method will start
* the WiFi connection using the saved SSID and password. Otherwise, it will
* start the WiFi Manager.
*/
void Networking::setup_wifi_manager() {
should_save_config = false;
wifi_manager = new AsyncWiFiManager(server, dns);

String hostname = SensESPBaseApp::get_hostname();

setup_wifi_callbacks();

// set config save notify callback
wifi_manager->setSaveConfigCallback(save_config_callback);
wifi_manager->setBreakAfterConfig(true);

wifi_manager->setConfigPortalTimeout(WIFI_CONFIG_PORTAL_TIMEOUT);

#ifdef SERIAL_DEBUG_DISABLED
wifi_manager->setDebugOutput(false);
#endif
AsyncWiFiManagerParameter custom_hostname(
"hostname", "Set ESP Device custom hostname", hostname.c_str(), 20);
"hostname", "Set ESP32 device custom hostname", hostname.c_str(), 20);
wifi_manager->addParameter(&custom_hostname);
wifi_manager->setTryConnectDuringConfigPortal(false);

// Create a unique SSID for configuring each SensESP Device
String config_ssid = SensESPBaseApp::get_hostname();
config_ssid = "Configure " + config_ssid;
const char* pconfig_ssid = config_ssid.c_str();

this->emit(WifiState::kWifiManagerActivated);
this->emit(WiFiState::kWifiManagerActivated);

WiFi.setHostname(SensESPBaseApp::get_hostname().c_str());

if (!wifi_manager->autoConnect(pconfig_ssid, wifi_manager_password_)) {
debugE("Failed to connect to wifi and config timed out. Restarting...");

this->emit(WifiState::kWifiDisconnected);

ESP.restart();
wifi_manager->startConfigPortal(pconfig_ssid, wifi_manager_password_);

// WiFiManager attempts to connect to the new SSID, but that doesn't seem to
// work reliably. Instead, we'll just attempt to connect manually.

bool connected = false;
this->ap_ssid = wifi_manager->getConfiguredSTASSID();
this->ap_password = wifi_manager->getConfiguredSTAPassword();

// attempt to connect with the new SSID and password
if (this->ap_ssid != "" && this->ap_password != "") {
debugD("Attempting to connect to acquired SSID %s and password",
this->ap_ssid.c_str());
WiFi.begin(this->ap_ssid.c_str(), this->ap_password.c_str());
for (int i = 0; i < 20; i++) {
if (WiFi.status() == WL_CONNECTED) {
connected = true;
break;
}
delay(1000);
}
}

debugI("Connected to wifi,");
debugI("IP address of Device: %s", WiFi.localIP().toString().c_str());
this->emit(WifiState::kWifiConnectedToAP);
// Only save the new configuration if we were able to connect to the new SSID.

if (should_save_config) {
if (connected) {
String new_hostname = custom_hostname.getValue();
debugI("Got new custom hostname: %s", new_hostname.c_str());
SensESPBaseApp::get()->get_hostname_observable()->set(new_hostname);
this->ap_ssid = WiFi.SSID();
debugI("Got new SSID and password: %s", ap_ssid.c_str());
this->ap_password = WiFi.psk();
save_configuration();
debugW("Restarting in 500ms");
ReactESP::app->onDelay(500, []() { ESP.restart(); });
}
}

static const char SCHEMA_PREFIX[] PROGMEM = R"({
"type": "object",
"properties": {
)";

String get_property_row(String key, String title, bool readonly) {
String readonly_title = "";
String readonly_property = "";

if (readonly) {
readonly_title = " (readonly)";
readonly_property = ",\"readOnly\":true";
}

return "\"" + key + "\":{\"title\":\"" + title + readonly_title + "\"," +
"\"type\":\"string\"" + readonly_property + "}";
debugW("Restarting...");
ESP.restart();
}

String Networking::get_config_schema() {
String schema;
// If hostname is not set by SensESPAppBuilder::set_hostname() in main.cpp,
// then preset_hostname will be "SensESP", and should not be read-only in the
// Config UI. If preset_hostname is not "SensESP", then it was set in
// main.cpp, so it should be read-only.
bool hostname_preset = preset_hostname != "SensESP";
return String(FPSTR(SCHEMA_PREFIX)) +
get_property_row("hostname", "ESP device hostname", hostname_preset) +
"}}";
static const char kSchema[] = R"###({
"type": "object",
"properties": {
"ssid": { "title": "WiFi SSID", "type": "string" },
"password": { "title": "WiFi password", "type": "string", "format": "password" },
"hostname": { "title": "Device hostname", "type": "string" }
}
})###";

return String(kSchema);
}

// FIXME: hostname should be saved in SensESPApp

void Networking::get_configuration(JsonObject& root) {
// root["hostname"] = SensESPBaseApp::get_hostname();
String hostname = SensESPBaseApp::get_hostname();
root["hostname"] = hostname;
root["default_hostname"] = default_hostname;
root["ssid"] = ap_ssid;
root["password"] = ap_password;
}

bool Networking::set_configuration(const JsonObject& config) {
debugD("%s\n", __func__);
// if (!config.containsKey("hostname")) {
// return false;
//}
//
// if (preset_hostname == "SensESP") {
// SensESPBaseApp::get()->get_hostname_observable()->set(
// config["hostname"].as<String>());
//}
if (!config.containsKey("hostname")) {
return false;
}

SensESPBaseApp::get()->get_hostname_observable()->set(
config["hostname"].as<String>());

if (config.containsKey("default_hostname")) {
default_hostname = config["default_hostname"].as<String>();
}
ap_ssid = config["ssid"].as<String>();
ap_password = config["password"].as<String>();

return true;
}
Expand Down
Loading

0 comments on commit b4de838

Please sign in to comment.