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