A Wi-Fi manager for the NodeMCU Lua firmware on ESP8266. Ship a device with no network configured; it raises its own access point, serves a one-page form, stores what the user types, and joins that network on every boot afterwards.
Simpler than tzapu/WiFiManager - five files, ~198 lines of Lua, no dependencies - and it does the same job.
On boot the firmware runs init.lua, which arms the factory-reset button and then asks one question:
does credentials.lua exist on flash?
flowchart LR
B(["boot"]) --> Q{"credentials.lua<br/>on SPIFFS?"}
Q -- "no" --> AP["<b>AP mode</b><br/>SSID ESP_<chipid><br/>form on 192.168.4.1"]
AP -->|"user submits SSID + password"| W["write credentials.lua"]
W --> ST
Q -- "yes" --> ST["<b>Station mode</b><br/>join the stored network"]
ST --> ON(["online"])
ON -.->|"reset button on D7<br/>deletes credentials.lua"| B
Full detail: docs/ARCHITECTURE.md.
-
Flash a NodeMCU 2.x Lua firmware including the
file,gpio,net,node,tmrandwifimodules. See docs/INSTALL.md - the 2.x requirement is not optional. -
Upload all five
.luafiles to the SPIFFS root, withinit.lualast. Do not uploadcredentials.lua; it is generated on the device. -
Reset the board. It advertises an open-to-you access point:
SSID ESP_<chipid>, e.g.ESP_1594043Passphrase passw0rdConfig URL http://192.168.4.1 -
Join it, open the URL, enter your network's SSID and password, press Save.
-
The browser will show an error - that is expected. The device drops its access point before it can reply. Watch the serial console at 115200 baud instead;
Config done, IP is 192.168.2.28means it worked. -
Every later boot joins that network automatically.
To start over, press the reset button on D7, or over serial:
file.remove("credentials.lua") node.restart()The console is the only progress indicator this project has. A full first-boot-and-provision transcript:
Every message is catalogued in docs/TROUBLESHOOTING.md.
| File | Role |
|---|---|
init.lua |
Boot entry. Sets 160 MHz, loads the reset button, then the wifi manager. Your own code goes at the bottom. |
reset_conf.lua |
Factory-reset button on D7/GPIO13 and status LED on D4/GPIO2. Loaded in both modes. |
wifimanager.lua |
The mode decision: station if credentials.lua exists, AP if not. |
station_mode.lua |
Joins the stored network and polls for a DHCP lease. |
ap_mode.lua |
SoftAP, HTTP server, form, credential parser, persistence. |
credentials.lua |
Generated on the device. Two plain data lines: SSID, then passphrase. Its existence is the "provisioned" flag. |
Nothing external is required. The reset button and LED are optional:
| Purpose | IO index | GPIO | Dev-kit label | Wiring |
|---|---|---|---|---|
| Factory reset | 7 | GPIO13 | D7 | Momentary switch to GND; internal pull-up is enabled in code |
| Status LED | 4 | GPIO2 | D4 | Onboard LED, active-low |
The button fires on release, not press - reset_conf.lua:36 registers a rising edge on a pin
that idles high. Details and cautions in docs/HARDWARE.md.
Everything is a literal in the source; there is no config file.
| Change | Where |
|---|---|
| AP SSID prefix | ap_mode.lua:10 |
| AP passphrase | ap_mode.lua:11 - must be 8-64 characters or AP mode aborts |
| Page title and text | ap_mode.lua:123-124 |
| Footer branding | ap_mode.lua:133 |
| Reset button pin | reset_conf.lua:2 |
| LED pin | reset_conf.lua:4 |
| Connection poll interval and retry cap | ap_mode.lua:94-99, station_mode.lua:13-20 |
| CPU frequency | init.lua:2 |
Full reference with valid ranges and failure modes: docs/CONFIGURATION.md.
It is a compact, readable provisioning bootstrap - and it still has real, documented sharp edges. The three that matter most now:
- Wrong credentials take the device offline for ~60 s. Both join paths retry for 12 attempts, then wipe the stored credentials and reboot into AP mode. The browser cannot reach the device during the retry window, and a device in the field will drop off the network rather than retry indefinitely.
- Credentials are handled in the clear - no TLS, the passphrase is echoed to the serial console
and shown in a
type="text"field, and the provisioning AP passphrase is a hard-codedpassw0rd. - Anyone on the provisioning AP can set credentials with a plain unauthenticated
GET.
The four highest-severity findings from the original review - no URL-decoding, credentials stored as executable Lua, permanent stranding, and a request race that corrupted saved credentials - are fixed and regression-tested; the remaining twenty catalogue entries stay documented with fix sketches in docs/KNOWN-ISSUES.md.
| Document | Contents |
|---|---|
| docs/INSTALL.md | Firmware requirements, flashing, uploading, factory reset |
| docs/ARCHITECTURE.md | Boot sequence, state machine, persistence model, globals, timers |
| docs/MODULES.md | Line-by-line file reference and verified firmware API index |
| docs/PROVISIONING-PROTOCOL.md | The HTTP contract, the parser, and its measured behaviour |
| docs/CONFIGURATION.md | Every tunable, its valid range, and what breaks |
| docs/HARDWARE.md | Pin map, button and LED wiring, power notes |
| docs/TROUBLESHOOTING.md | Symptom index, serial-message reference, live inspection |
| docs/KNOWN-ISSUES.md | Reviewed defects and limitations |
NodeMCU 2.x only. Two APIs used here were removed from later firmware: the numeric-id static
timers (tmr.alarm(2, ...)) and the type argument to net.createServer. The reference build is
NodeMCU 2.2.0.0, Lua 5.1.4, SDK 2.2.1. See
docs/INSTALL.md.

