8 |
9 |
10 | * [WiFi configuration manager for the ESP32 and ESP8266 platforms in the Arduino framework](#wifi-configuration-manager-for-the-esp32-and-esp8266-platforms-in-the-arduino-framework)
11 | * [Description](#description)
12 | * [Examples](#examples)
13 | * [Minimal usage](#minimal-usage)
14 | * [Callbacks and custom variables](#callbacks-and-custom-variables)
15 | * [Other examples](#other-examples)
16 | * [Note for ESP8266 users](#note-for-esp8266-users)
17 | * [Installing](#installing)
18 | * [Reference](#reference)
19 | * [Functions](#functions)
20 | * [WiFiSettings.connect([...])](#wifisettingsconnect)
21 | * [WiFiSettings.portal()](#wifisettingsportal)
22 | * [WiFiSettings.integer(...)](#wifisettingsinteger)
23 | * [WiFiSettings.string(...)](#wifisettingsstring)
24 | * [WiFiSettings.checkbox(...)](#wifisettingscheckbox)
25 | * [WiFiSettings.html(...)](#wifisettingshtml)
26 | * [WiFiSettings.heading(...)](#wifisettingsheading)
27 | * [WiFiSettings.warning(...)](#wifisettingswarning)
28 | * [WiFiSettings.info(...)](#wifisettingsinfo)
29 | * [Variables](#variables)
30 | * [WiFiSettings.hostname](#wifisettingshostname)
31 | * [WiFiSettings.password](#wifisettingspassword)
32 | * [WiFiSettings.ssid](#wifisettingsssid)
33 | * [WiFiSettings.secure](#wifisettingssecure)
34 | * [WiFiSettings.language](#wifisettingslanguage)
35 | * [WiFiSettings.on*](#wifisettingson)
36 | * [History](#history)
37 | * [A note about Hyrum's Law](#a-note-about-hyrums-law)
38 |
39 |
40 |
41 |
42 |
43 | ## Description
44 |
45 | This is a very simple, and somewhat naive, WiFi configuration manager for
46 | ESP32 and ESP8266 programs written in the Arduino framework. It will allow you
47 | to configure your WiFi network name (SSID) and password via a captive portal:
48 | the ESP becomes an access point with a web based configuration page.
49 |
50 | It was written for ease of use, not for extended functionality. For example,
51 | restarting the microcontroller is the only way to leave the configuration
52 | portal. A button to restart is provided in the web interface.
53 |
54 | The library generates a random password to protect the portal with, but
55 | it's only secured if you choose to do so by checking a checkbox. Of course,
56 | the user can configure pick their own password.
57 |
58 | The configuration is stored in files in the flash filesystem of the ESP. The
59 | files are dumped in the root directory of the filesystem. Debug output
60 | (including the password to the configuration portal) is written to `Serial`.
61 |
62 | Only automatic IP address assignment (DHCP) is supported.
63 |
64 | ## Examples
65 |
66 | ### Minimal usage
67 |
68 | ```C++
69 | #include
70 | #include
71 |
72 | void setup() {
73 | Serial.begin(115200);
74 | SPIFFS.begin(true); // On first run, will format after failing to mount
75 |
76 | WiFiSettings.connect();
77 | }
78 |
79 | void loop() {
80 | ...
81 | }
82 | ```
83 |
84 | ### Callbacks and custom variables
85 |
86 | ```C++
87 | void setup() {
88 | Serial.begin(115200);
89 | SPIFFS.begin(true); // On first run, will format after failing to mount
90 |
91 | // Note that these examples call functions that you probably don't have.
92 | WiFiSettings.onSuccess = []() { green(); };
93 | WiFiSettings.onFailure = []() { red(); };
94 | WiFiSettings.onWaitLoop = []() { blue(); return 30; }; // delay 30 ms
95 | WiFiSettings.onPortalWaitLoop = []() { blink(); };
96 |
97 | String host = WiFiSettings.string( "server_host", "default.example.org");
98 | int port = WiFiSettings.integer("server_port", 0, 65535, 443);
99 |
100 | WiFiSettings.connect(true, 30);
101 | }
102 | ```
103 |
104 | ### Other examples
105 |
106 | * The [ArduinoOTA example](examples/ArduinoOTA/ArduinoOTA.ino) shows how to
107 | enable over-the-air uploads in the WiFiSettings configuration portal. If you
108 | use the password from WiFiSettings as your OTA password, you no longer have
109 | to hard code it!
110 |
111 | ### Note for ESP8266 users
112 |
113 | The examples are written for ESP32. To use them with the older ESP8266 chip,
114 | note that in the ESP8266 world, SPIFFS is deprecated and replaced by LittleFS.
115 |
116 | WifiSettings uses SPIFFS on ESP32, and LittleFS on ESP8266.
117 |
118 | Simply change both occurrences of `SPIFFS` to `LittleFS`, and remove `true` in
119 | the call to `LittleFS.begin();`. LittleFS will format the filesystem by
120 | default.
121 |
122 | ## Installing
123 |
124 | Automated installation:
125 | * [Instructions for Arduino Library Manager](https://www.arduino.cc/en/guide/libraries)
126 | * [Instructions for PlatformIO Library Manager](https://platformio.org/lib/show/7251/esp32-WiFiSettings/installation)
127 |
128 | Getting the source for manual installation:
129 | * `git clone https://github.com/Juerd/ESP-WiFiSettings`
130 | * [.zip and .tar.gz files](https://github.com/Juerd/ESP-WiFiSettings/releases)
131 |
132 | ## Reference
133 |
134 | This library uses a singleton instance (object), `WiFiSettings`, and is not
135 | designed to be inherited from (subclassed), or to have multiple instances.
136 |
137 | ### Functions
138 |
139 | #### WiFiSettings.connect([...])
140 |
141 | ```C++
142 | bool connect(bool portal = true, int wait_seconds = 30);
143 | ```
144 |
145 | If no WiFi network is configured yet, starts the configuration portal.
146 | In other cases, it will attempt to connect to the network in station (WiFi
147 | client) mode, and wait until either a connection is established, or
148 | `wait_seconds` has elapsed. Returns `true` if connection succeeded.
149 |
150 | By default, a failed connection (no connection established within the timeout)
151 | will cause the configuration portal to be started. Given `portal = false`, it
152 | will instead return `false`.
153 |
154 | To wait forever until WiFi is connected, use `wait_seconds = -1`. In this case,
155 | the value of `portal` is ignored.
156 |
157 | Calls the following callbacks:
158 |
159 | * WiFiSettings.onConnect
160 | * WiFiSettings.onWaitLoop -> int (milliseconds to wait)
161 | * WiFiSettings.onSuccess
162 | * WiFiSettings.onFailure
163 |
164 | #### WiFiSettings.portal()
165 |
166 | ```C++
167 | void portal();
168 | ```
169 |
170 | Disconnects any active WiFi and turns the ESP into a captive portal with a
171 | DNS server that works on every hostname.
172 |
173 | Normally, this function is called by `.connect()`. To allow reconfiguration
174 | after the initial configuration, you could call `.portal()` manually, for
175 | example when a button is pressed during startup.
176 |
177 | This function never ends. A restart is required to resume normal operation.
178 |
179 | Calls the following callbacks:
180 |
181 | * WiFiSettings.onPortal
182 | * WiFiSettings.onPortalWaitLoop
183 | * WiFiSettings.onPortalView
184 | * WiFiSettings.onUserAgent(String& ua)
185 | * WiFiSettings.onConfigSaved
186 | * WiFiSettings.onRestart
187 |
188 | #### WiFiSettings.integer(...)
189 | #### WiFiSettings.string(...)
190 | #### WiFiSettings.checkbox(...)
191 |
192 | ```C++
193 | int integer(String name, [long min, long max,] int init = 0, String label = name);
194 | String string(String name, [[unsigned int min_length,] unsigned int max_length,] String init = "", String label = name);
195 | bool checkbox(String name, bool init = false, String label = name);
196 | ```
197 |
198 | Configures a custom configurable option and returns the current value. When no
199 | value (or an empty string) is configured, the value given as `init` is returned.
200 |
201 | These functions should be called *before* calling `.connect()` or `.portal()`.
202 |
203 | The `name` is used as the filename in the SPIFFS, and as an HTML form element
204 | name, and must be valid in both of those contexts. Any given `name` should only
205 | be used once!
206 |
207 | It is strongly suggested to include the name of a project in the `name` of the
208 | configuration option, if it is specific to that project. For example, an MQTT
209 | topic is probably specific to the application, while the server hostname
210 | is likely to be shared among several projects. This helps when the ESP is
211 | later reused for different applications.
212 |
213 | Optionally, `label` can be specified as a descriptive text to use on the
214 | configuration portal.
215 |
216 | Some restrictions for the values can be given. Note that these limitations are
217 | implemented on the client side, and may not be respected by browsers. For
218 | integers, a range can be specified by supplying both `min` and `max`. For
219 | strings, a maximum length can be specified as `max_length`. A minimum string
220 | length can be set with `min_length`, effectively making the field mandatory:
221 | it can no longer be left empty to get the `init` value.
222 |
223 | #### WiFiSettings.html(...)
224 | #### WiFiSettings.heading(...)
225 | #### WiFiSettings.warning(...)
226 | #### WiFiSettings.info(...)
227 |
228 | ```C++
229 | void html(String tag, String contents, bool escape = true);
230 | void heading(String contents, bool escape = true);
231 | void warning(String contents, bool escape = true);
232 | void info(String contents, bool escape = true);
233 | ```
234 |
235 | Mix in custom text or HTML fragments, such as headings, warning texts, or info
236 | texts.
237 |
238 | Custom HTML can be specified with the `html` function, which takes a tag (e.g.
239 | `"p"`) or a tag with attributes (e.g. `"p align=right"`) as the first argument.
240 | Only tags that take a closing tag should be used. The other functions are
241 | provided for convenience.
242 |
243 | The contents are safely escaped by default, but raw HTML can be added by
244 | providing `false` as the last argument, in which case the contents are added to
245 | the page without any verification or modification. Consider the security
246 | implications of using unescaped data from external sources.
247 |
248 | ### Variables
249 |
250 | Note: because of the way this library is designed, any assignment to the
251 | member variables should be done *before* calling any of the functions.
252 |
253 | #### WiFiSettings.hostname
254 |
255 | ```C++
256 | String
257 | ```
258 |
259 | Name to use as the hostname and SSID for the access point. By default, this is
260 | set to "esp32-" or "esp8266-", depending on the platform.
261 |
262 | If it ends in a `-` character, a unique 6 digit device identifier
263 | (specifically, the hexadecimal representation of the device interface specific
264 | part of the ESP's MAC address, in reverse byte order) is added automatically.
265 | This is highly recommended.
266 |
267 | Use only ASCII digits and letters. ASCII hyphens (`-`) can only be used in
268 | between other characters (i.e. not two in a row, and not as the first
269 | character). Most characters, including underscores (`_`) and spaces, are not
270 | valid in hostnames.
271 |
272 | This variable may be read any time after any other function is called to obtain
273 | for example a unique device id for MQTT's client-id.
274 |
275 | #### WiFiSettings.password
276 |
277 | ```C++
278 | String
279 | ```
280 |
281 | This variable is used to protect the configuration portal's softAP. When no
282 | password is explicitly assigned before the first custom configuration parameter
283 | is defined (with `.string`, `.integer`, or `.checkbox`), a password will be
284 | automatically generated and can be configured by the user.
285 |
286 | It's strongly recommended to leave this variable untouched, and use the
287 | built-in password generation feature, and letting the user configure their own
288 | password, instead of "hard coding" a password.
289 |
290 | The password has no effect unless the portal is secured; see `.secure`.
291 |
292 | #### WiFiSettings.ssid
293 |
294 | ```C++
295 | String
296 | ```
297 |
298 | This variable is used to expose the SSID. This variable can be used
299 | to, for example, have a callback that rescans and restarts the device if the
300 | prefered WiFi appears.
301 |
302 | #### WiFiSettings.secure
303 |
304 | ```C++
305 | bool
306 | ```
307 |
308 | By setting this to `true`, before any custom configuration parameter is defined
309 | with `.string`, `.integer`, or `.checkbox`, secure mode will be forced, instead
310 | of the default behavior, which is to initially use an insecure softAP and to
311 | let the user decide whether to secure it.
312 |
313 | When `.secure` is left in the default state, `false`, the user setting will be
314 | used.
315 |
316 | When forcing secure mode, it is still recommended to leave `.password` unset so
317 | that a password is automatically generated, if you have a way to communicate it
318 | to the user, for example with an LCD display, or
319 | `Serial.println(WiFiSettings.password);`. Having hard coded password literals
320 | in source code is generally considered a bad idea, because that makes it harder
321 | to share the code with others.
322 |
323 | #### WiFiSettings.language
324 |
325 | ```C++
326 | String
327 | ```
328 |
329 | The language to be used in the WiFiSettings portal. Currently supported are
330 | `en` and `nl`. Once the user has picked a language in the portal, the user
331 | setting overrides any value previously assigned. This variable is updated to
332 | reflect the currently selected language.
333 |
334 | By default, all available languages are available. To conserve flash storage
335 | space, it is possible to select only specific languages, by specifying build
336 | flags such as `LANGUAGE_EN`. If only a single language is defined, this
337 | language will be used regardless of any configuration, and no language
338 | drop-down is presented to the user. Note: build flags are not available in the
339 | Arduino IDE, but can be specified in Arduino board files. In PlatformIO, build
340 | flags can be specified in the `[env]` section, e.g. `build_flags =
341 | -DLANGUAGE_EN`.
342 |
343 | *If you wish to contribute a translation, please refer to
344 | `WiFiSettings_strings.h`. (Note: due to storage constraints on microcontroller
345 | flash filesystems, only widely used natural languages will be included.)*
346 |
347 | #### WiFiSettings.on*
348 |
349 | The callback functions are mentioned in the documentation for the respective
350 | functions that call them.
351 |
352 | ## History
353 |
354 | Note that this library was briefly named WiFiConfig, but was renamed to
355 | WiFiSettings because there was already another library called
356 | [WiFiConfig](https://github.com/snakeye/WifiConfig).
357 |
358 | From version 3.0.0, based on a contribution by Reinier van der Leer, this
359 | library also supports the older ESP8266, and the repository was renamed from
360 | esp32-WiFiSettings to ESP-WiFiSettings.
361 |
362 | ## A note about Hyrum's Law
363 |
364 | It is said that *all observable behaviors of your system will be depended on by
365 | somebody*, and you are of course free to explore the source and use any
366 | unintended feature you may find to your advantage. Bear in mind, however, that
367 | depending on any behavior that is not documented here, is more likely to cause
368 | breakage when you install a newer version of this library. The author feels no
369 | obligation to keep backwards compatibility with undocumented features :)
370 |
--------------------------------------------------------------------------------
/WiFiSettings.cpp:
--------------------------------------------------------------------------------
1 | #include "WiFiSettings.h"
2 | #ifdef ESP32
3 | #define ESPFS SPIFFS
4 | #define ESPMAC (Sprintf("%06" PRIx64, ESP.getEfuseMac() >> 24))
5 | #include
6 | #include
7 | #include
8 | #include
9 | #elif ESP8266
10 | #define ESPFS LittleFS
11 | #define ESPMAC (Sprintf("%06" PRIx32, ESP.getChipId()))
12 | #include
13 | #include
14 | #include
15 | #define WebServer ESP8266WebServer
16 | #define esp_task_wdt_reset wdt_reset
17 | #define wifi_auth_mode_t uint8_t // wl_enc_type
18 | #define WIFI_AUTH_OPEN ENC_TYPE_NONE
19 | constexpr auto WIFI_AUTH_WPA2_ENTERPRISE = -1337; // not available on ESP8266
20 | #define setHostname hostname
21 | #define INADDR_NONE IPAddress(0,0,0,0)
22 | #else
23 | #error "This library only supports ESP32 and ESP8266"
24 | #endif
25 | #include
26 | #include
27 | #include
28 | #include
29 |
30 | WiFiSettingsLanguage::Texts _WSL_T;
31 |
32 | #define Sprintf(f, ...) ({ char* s; asprintf(&s, f, __VA_ARGS__); String r = s; free(s); r; })
33 |
34 | namespace { // Helpers
35 | String slurp(const String& fn) {
36 | File f = ESPFS.open(fn, "r");
37 | String r = f.readString();
38 | f.close();
39 | return r;
40 | }
41 |
42 | bool spurt(const String& fn, const String& content) {
43 | File f = ESPFS.open(fn, "w");
44 | if (!f) return false;
45 | auto w = f.print(content);
46 | f.close();
47 | return w == content.length();
48 | }
49 |
50 | String pwgen() {
51 | const char* passchars = "ABCEFGHJKLMNPRSTUXYZabcdefhkmnorstvxz23456789-#@?!";
52 | String password = "";
53 | for (int i = 0; i < 16; i++) {
54 | // Note: no seed needed for ESP8266 and ESP32 hardware RNG
55 | password.concat( passchars[random(strlen(passchars))] );
56 | }
57 | return password;
58 | }
59 |
60 | String html_entities(const String& raw) {
61 | String r;
62 | for (unsigned int i = 0; i < raw.length(); i++) {
63 | char c = raw.charAt(i);
64 | if (c < '!' || c == '"' || c == '&' || c == '\'' || c == '<' || c == '>' || c == 0x7f) {
65 | // ascii control characters, html syntax characters, and space
66 | r += Sprintf("%d;", c);
67 | } else {
68 | r += c;
69 | }
70 | }
71 | return r;
72 | }
73 |
74 | struct WiFiSettingsParameter {
75 | String name;
76 | String label;
77 | String value;
78 | String init;
79 | long min = LONG_MIN;
80 | long max = LONG_MAX;
81 |
82 | String filename() { String fn = "/"; fn += name; return fn; }
83 | bool store() { return (name && name.length())? spurt(filename(), value): true; }
84 | void fill() { if (name && name.length()) value = slurp(filename()); }
85 | virtual void set(const String&) = 0;
86 | virtual String html() = 0;
87 | };
88 |
89 | struct WiFiSettingsString : WiFiSettingsParameter {
90 | virtual void set(const String& v) { value = v; }
91 | String html() {
92 | String h = F("