SensESP 3.3.0
Universal Signal K sensor toolkit ESP32
Loading...
Searching...
No Matches
wifi_provisioner.cpp
Go to the documentation of this file.
1#include "sensesp.h"
2
3#include "wifi_provisioner.h"
4
5#include <esp_wifi.h>
6
7#include "sensesp_app.h"
8
9namespace sensesp {
10
11// Wifi config portal timeout (seconds). The smaller the value, the faster
12// the device will attempt to reconnect. If set too small, it might
13// become impossible to actually configure the Wifi settings in the captive
14// portal.
15#ifndef WIFI_CONFIG_PORTAL_TIMEOUT
16#define WIFI_CONFIG_PORTAL_TIMEOUT 180
17#endif
18
19// Network configuration logic:
20// 1. Use hard-coded hostname and WiFi credentials by default
21// 2. If the hostname or credentials have been changed in WiFiManager or
22// the web UI, use the updated values.
23// 3. If the hard-coded hostname is changed, use that instead of the saved one.
24// (But keep using the saved WiFi credentials!)
25
26WiFiProvisioner::WiFiProvisioner(const String& config_path,
27 const String& client_ssid,
28 const String& client_password,
29 const String& ap_ssid,
30 const String& ap_password)
31 : FileSystemSaveable{config_path}, Resettable(0) {
32 bool config_loaded = load();
33
34 if (!config_loaded) {
35 if (ap_ssid != "" && ap_password != "") {
36 this->ap_settings_.enabled_ = true;
37 this->ap_settings_.ssid_ = ap_ssid;
38 this->ap_settings_.password_ = ap_password;
39 } else {
40 this->ap_settings_.enabled_ = false;
41 }
42 }
43
44 if (!config_loaded && client_ssid != "" && client_password != "") {
45 ClientSSIDConfig preset_client_config = {client_ssid, client_password,
46 true};
47 client_settings_.push_back(preset_client_config);
48 client_enabled_ = true;
49 }
50
51 // Fill in the rest of the client settings array with empty configs
52 int num_fill = kMaxNumClientConfigs - client_settings_.size();
53 for (int i = 0; i < num_fill; i++) {
55 }
56
57 ESP_LOGD(__FILENAME__, "Enabling WiFi provisioner");
58
59 // Hate to do this, but Raspberry Pi AP setup is going to be much more
60 // complicated with enforced WPA2. BAD Raspberry Pi! BAD!
61 WiFi.setMinSecurity(WIFI_AUTH_WPA_PSK);
62
63 // Try setting hostname already here.
64 String hostname = SensESPBaseApp::get_hostname();
65 WiFi.setHostname(hostname.c_str());
66
67 // Start WiFi with a bogus SSID to initialize the network stack but
68 // don't connect to any network.
69 WiFi.begin("0", "0", 0, nullptr, false);
70
71 // If both saved AP settings and saved client settings
72 // are available, start in STA+AP mode.
73
74 if (this->ap_settings_.enabled_ && this->client_enabled_ == true) {
75 WiFi.mode(WIFI_AP_STA);
78 }
79
80 // If saved AP settings are available, use them.
81
82 else if (this->ap_settings_.enabled_) {
83 WiFi.mode(WIFI_AP);
85 }
86
87 // If saved client settings are available, use them.
88
89 else if (this->client_enabled_) {
90 WiFi.mode(WIFI_STA);
92 }
93
94 if (this->ap_settings_.enabled_ &&
95 this->ap_settings_.captive_portal_enabled_) {
96 dns_server_ = std::unique_ptr<DNSServer>(new DNSServer());
97
98 dns_server_->setErrorReplyCode(DNSReplyCode::NoError);
99 dns_server_->start(53, "*", WiFi.softAPIP());
100
101 event_loop()->onRepeat(1, [this]() { dns_server_->processNextRequest(); });
102 }
103}
104
106 if (dns_server_) {
107 dns_server_->stop();
108 }
109
110 // Stop WiFi
111 WiFi.disconnect(true);
112}
113
115 String hostname = SensESPBaseApp::get_hostname();
116 WiFi.setHostname(hostname.c_str());
117
118 ESP_LOGI(__FILENAME__, "Starting access point %s",
119 ap_settings_.ssid_.c_str());
120
121 bool result =
122 WiFi.softAP(ap_settings_.ssid_.c_str(), ap_settings_.password_.c_str(),
123 ap_settings_.channel_, ap_settings_.hidden_);
124
125 if (!result) {
126 ESP_LOGE(__FILENAME__, "Failed to start access point.");
127 return;
128 }
129}
130
132 String hostname = SensESPBaseApp::get_hostname();
133 WiFi.setHostname(hostname.c_str());
134
135 // set up WiFi in regular STA (client) mode
136 auto reconnect_cb = [this]() {
137 static uint32_t attempt_num = 0;
138 static uint32_t current_config_idx = 0;
139
140 int num_configs = client_settings_.size();
141
142 if (WiFi.status() == WL_CONNECTED) {
143 attempt_num = 0;
144 current_config_idx = 0;
145 return;
146 }
147
148 // First check if any of the client settings are defined
149 if (num_configs == 0) {
150 ESP_LOGW(__FILENAME__,
151 "No client settings defined. Leaving WiFi client disconnected.");
152 return;
153 }
154
155 uint32_t prev_config_idx = current_config_idx;
156
157 ClientSSIDConfig config;
158
159 // Get next valid client config
160 for (current_config_idx = current_config_idx;
161 current_config_idx < prev_config_idx + num_configs;
162 current_config_idx++) {
163 config = client_settings_[current_config_idx % num_configs];
164 if (config.ssid_ != "" && config.password_ != "") {
165 break;
166 }
167 }
168
169 ESP_LOGD(__FILENAME__, "Current client config index: %d",
170 current_config_idx);
171 ESP_LOGD(__FILENAME__, "Attempt number: %d", attempt_num);
172 ESP_LOGD(__FILENAME__, "Config SSID: %s", config.ssid_.c_str());
173
174 // If no valid client config found, leave WiFi client disconnected
175 if (config.ssid_ == "" || config.password_ == "") {
176 ESP_LOGW(
177 __FILENAME__,
178 "No valid client settings found. Leaving WiFi client disconnected.");
179 return;
180 }
181
182 ESP_LOGI(__FILENAME__,
183 "Connecting to wifi SSID %s (connection attempt #%d).",
184 config.ssid_.c_str(), attempt_num);
185
186 if (!config.use_dhcp_) {
187 ESP_LOGI(__FILENAME__, "Using static IP address: %s",
188 config.ip_.toString().c_str());
189 // Arduino-ESP32 signature:
190 // WiFi.config(local_ip, gateway, subnet, dns1, dns2).
191 // The pre-refactor code passed (ip, dns, gateway, netmask) which
192 // silently set gateway=dns, subnet=gateway and dns1=netmask — a
193 // long-standing bug that broke every saved static-IP config.
194 WiFi.config(config.ip_, config.gateway_, config.netmask_,
195 config.dns_server_);
196 }
197 WiFi.begin(config.ssid_.c_str(), config.password_.c_str());
198 attempt_num++;
199 current_config_idx++; // Move to the next config for the next attempt
200 };
201
202 // Perform an initial connection without a delay.
203 reconnect_cb();
204
205 // Launch a separate onRepeat event to (re-)establish WiFi connection.
206 // Connecting is attempted only every 20 s to allow the previous connection
207 // attempt to complete even if the network is slow.
208 event_loop()->onRepeat(20000, reconnect_cb);
209}
210
211bool WiFiProvisioner::to_json(JsonObject& root) {
212 JsonObject apSettingsJson = root["apSettings"].to<JsonObject>();
213 ap_settings_.as_json(apSettingsJson);
214
215 JsonObject clientSettingsJson = root["clientSettings"].to<JsonObject>();
216 clientSettingsJson["enabled"] = client_enabled_;
217 JsonArray clientConfigsJson = clientSettingsJson["settings"].to<JsonArray>();
218 int num_serialized = 0;
219 for (auto& config : client_settings_) {
220 if (num_serialized++ >= kMaxNumClientConfigs) {
221 break;
222 }
223 JsonObject clientConfigJson = clientConfigsJson.add<JsonObject>();
224 config.as_json(clientConfigJson);
225 }
226 return true;
227}
228
229bool WiFiProvisioner::from_json(const JsonObject& config) {
230 if (config["hostname"].is<String>()) {
231 // deal with the legacy Json format
232 String hostname = config["hostname"].as<String>();
233 SensESPBaseApp::get()->get_hostname_observable()->set(hostname);
234
235 if (config["ssid"].is<String>()) {
236 String ssid = config["ssid"].as<String>();
237 String password = config["password"].as<String>();
238
239 if (config["ap_mode"].is<String>()) {
240 if (config["ap_mode"].as<String>() == "Access Point" ||
241 config["ap_mode"].as<String>() == "Hotspot") {
242 ap_settings_ = {true, ssid, password};
243 } else {
244 ClientSSIDConfig client_settings = {ssid, password};
245 client_settings_.clear();
246 client_settings_.push_back(client_settings);
247 client_enabled_ = true;
248 }
249 }
250 }
251 } else {
252 // Either an empty config or a new-style config
253 if (config["apSettings"].is<JsonVariant>()) {
254 ap_settings_ = AccessPointSettings::from_json(config["apSettings"]);
255 } else {
257 }
258 if (config["clientSettings"].is<JsonVariant>()) {
259 const JsonObject& client_settings_json = config["clientSettings"];
260 client_enabled_ = client_settings_json["enabled"] | false;
261 client_settings_.clear();
262 const JsonArray& client_settings_json_array =
263 client_settings_json["settings"];
264 for (const JsonObject& cfg_json : client_settings_json_array) {
266 }
267 if (client_settings_.size() == 0) {
268 client_enabled_ = false;
269 }
270 }
271 }
272 // Fill in the rest of the client settings array with empty configs
273 while (client_settings_.size() < kMaxNumClientConfigs) {
275 }
276
277 return true;
278}
279
281 ESP_LOGI(__FILENAME__, "Resetting WiFi SSID settings");
282
283 clear();
284 WiFi.disconnect(true);
285 // On ESP32, disconnect does not erase previous credentials. Let's connect
286 // to a bogus network instead
287 WiFi.begin("0", "0", 0, nullptr, false);
288}
289
291 // Scan fails if WiFi is connecting. Disconnect to allow scanning.
292 if (WiFi.status() != WL_CONNECTED) {
293 ESP_LOGD(__FILENAME__,
294 "WiFi is not connected. Disconnecting to allow scanning.");
295 WiFi.disconnect();
296 }
297 ESP_LOGI(__FILENAME__, "Starting WiFi network scan");
298 int result = WiFi.scanNetworks(true);
299 if (result == WIFI_SCAN_FAILED) {
300 ESP_LOGE(__FILENAME__, "WiFi scan failed to start");
301 }
302}
303
305 std::vector<WiFiNetworkInfo>& ssid_list) {
306 int num_networks = WiFi.scanComplete();
307 if (num_networks == WIFI_SCAN_RUNNING) {
308 return WIFI_SCAN_RUNNING;
309 }
310 if (num_networks == WIFI_SCAN_FAILED) {
311 return WIFI_SCAN_FAILED;
312 }
313 ssid_list.clear();
314 for (int i = 0; i < num_networks; i++) {
315 WiFiNetworkInfo info(WiFi.SSID(i), WiFi.RSSI(i), WiFi.encryptionType(i),
316 WiFi.BSSID(i), WiFi.channel(i));
317 ssid_list.push_back(info);
318 }
319
320 return num_networks;
321}
322
323} // namespace sensesp
Storage object for WiFi access point settings.
static AccessPointSettings from_json(const JsonObject &json)
Storage object for WiFi client settings.
static ClientSSIDConfig from_json(const JsonObject &json)
virtual bool clear() override
Delete the data from a persistent storage.
Definition saveable.cpp:71
virtual bool load() override
Load and populate the object from a persistent storage.
Definition saveable.cpp:8
FileSystemSaveable(const String &config_path)
Definition saveable.h:63
Resettable(int priority=0)
static String get_hostname()
Get the current hostname.
static const std::shared_ptr< SensESPBaseApp > & get()
Get the singleton instance of the SensESPBaseApp.
WiFi network info storage class returned by scan results.
bool from_json(const JsonObject &config) override
AccessPointSettings ap_settings_
std::vector< ClientSSIDConfig > client_settings_
int16_t get_wifi_scan_results(std::vector< WiFiNetworkInfo > &ssid_list)
WiFiProvisioner(const String &config_path, const String &client_ssid="", const String &client_password="", const String &ap_ssid="", const String &ap_password="")
bool to_json(JsonObject &doc) override
std::unique_ptr< DNSServer > dns_server_
std::shared_ptr< reactesp::EventLoop > event_loop()
Definition sensesp.cpp:9
constexpr int kMaxNumClientConfigs