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