Daring Designs uses cookies to enhance your experience, analyze site traffic, and improve our services. By clicking dismiss, you agree to our use of cookies.

Skip to main content
Daring Designs
Contact Search

Arduino Captive WiFi Portal with QR Code

Learn how to create a captive WiFi connection portal that's user friendly and features a QR code for your Arduino project. We're using an Heltec E290 esp32 with an integrated e-ink display but you can implement something similar for your device & display.

Make a Captive WiFi Portal Like This

To get started open up your Arduino IDE or if you like VScode you can use the community maintained version of the deprecated Arduino VScode extension like I do! It allows you to use all your other fancy extensions and Copilot which was definitely used to write some of this code. This tutorial will step through the functions used and details about them.

Start Here

Begin with just the boilerplate setup() and loop() functions. If you didn't know, the setup() function runs once when the device starts up, and theloop() function runs continually after the setup function completes. That's the basics!

sketch.ino

The Setup() Function

Within the setup() function shown in the code below were going to do 3 things basically:

  1. First we initialize the e-ink display settings. We're telling it to set the rotation of the device into a horizontal format, clear the screen and then use "fastmode" which allows for partial refreshes of the display. It's all explained here in the library's documentation for the display.

  2. Next we check for saved WiFi credentials in SPIFFS with the loadWiFiCredentials()function. If there are saved credentials the script will try to connect to wifi.

  3. If the there's no WiFi then startCaptivePortal() is called which turns on access point mode, the DNS/web server, and generates a QR Code on the display.

sketch.ino

Load Wifi Credentials Function

Now, let's create the loadWiFiCredentials() function we just used in the setup() function. Using SPIFFS, it will look for the presence of a file named wifi.txt which will contain the SSID and password.

sketch.ino

Start Captive Portal Function

Ok, now we can create the other function we called in setup(), the startCaptivePortal() function. This function starts the WiFi access point, configures the DNS server to redirect all domains to the devices IP, setups routes for the "captive" portal, and generates a QR code based on the WiFI URI standard.

sketch.ino

Handle Root Function

The startCaptivePortal() function includes a handleRoot() function called on lines 11 and 13. This function contains the (ugly string concatenated) HTML for the captive portal WiFi network selection view.

sketch.ino

Handle Setup Function

Not to be confused with the former setup() function, the handleSetup() function is fired after a user clicks the "Connect" button in the captive portal's root view.

It first attempts to connect to the new WiFi network and then will save the credentials using SPIFFS if a connection succeeds. If the connection fails the user is prompted to try again and directed back to the WiFi selection root view.

sketch.ino

Save WiFi Credentials Function

Used by the handleSetup() function, the saveWiFiCredentials() function will save your WiFi credentials to the file system. It saves it in the wifi.txt with the first line being the SSID and the second the password.

sketch.ino

Display Provisioning Screen Function

This displayProvisioningScreen() function is called in the startCaptivePortal() function and magically prints a QR code to the display using the ricmoo/QRCode library and a bit of hackery.

sketch.ino

And Finally The Loop Function

The loop() function here runs the dnsServer and webServer for the captive portal if there is no WiFi connection.

sketch.ino

Imports and Globals

The imports and globals go at the very top of your sketch file. These are used to import the libraries, declare global variables, and constants.

Oh yeah and don't forget that you will also need to include the third party libraries we used. You can search for the two libraries here and install each:

  • "QRcode" by Richard Moore

  • "heltec-eink-modules" by Todd Herbert

sketch.ino

Bonus Points, Reset Button

Ok so you got this solution working but you want to reset the WiFi credentials huh? You could just use the "Erase All Flash Before Sketch Upload" option in your IDE's board configuration, but that's not very user friendly. It's better to have a physical way to reset it!

For this Heltec E290 board It's got a built in button at GPIO 21 so that's what i'm going to use. There are a few ways to detect a button press, you can use an interrupt, or just poll for it in the loop() function, and you can also add a debounce if you need. But we don't need a debounce and we can use the simple polling method because this button can be pressed multiple times and will not have a negative effect.

First let's add the resetWiFiCredentials() function that will actually do the removal of the credentials.

sketch.ino

Next modify your loop() function to have this super simple pinMode() init and digitalRead() conditional that will fire the resetWifiCredentials() function. Lines 10-14.

sketch.ino

Boom that should do it! Now you can press that button and a message will pop up notifying users of a WiFi reset.

Put it all Together

Ok when you put it all together you should get something like this sketch.ino file. TL;DR You can also just cut to the chase and copy this!

Once you get this uploaded to your device you should see the QR code view shown in the image above. If not check your console output to see if it ran into any errors compiling.

Scan the QR code with your phone and IOS or Android should open up the captive portal screen automatically!