Pico Portal on the Raspberry Pi Pico W
Pico Portal started as a way for me to demonstrate Evil Portals in a classroom setting. I wanted something small, portable, and easy to power on without dragging a full setup into the room.
The idea was simple. Turn a Pico W into a standalone access point, serve a captive portal style page, and show what happens right on the device. That made it easier to explain because people could connect to it, interact with it, and immediately see the result.
It also gave me a good way to use the templates from my Red Portals repo in a physical demo. Red Portals handles the portal templates. Pico Portal handles the device side.
Why I built it
Captive portals and Evil Portal style attacks are easy to talk about in theory, but that usually does not land the same way as seeing one in person. I wanted something I could bring into a room, let people connect to, and use to walk through what was actually happening.
The Pico W made sense for that. It is cheap, small, has Wi-Fi built in, and has just enough room to run the access point, DNS catch-all behavior, and a simple web server.
The display side helped too. Being able to show status messages, selected templates, and submitted data on the device itself made the demo a lot more useful.
What Pico Portal does
Pico Portal turns the Raspberry Pi Pico W into its own Wi-Fi access point and serves a selected HTML template as the portal page.
When the device starts, it brings up the AP, starts a DNS catch-all
server, and runs a small web server. If a client hits the usual captive
portal detection paths like
/generate_204
,
/hotspot-detect.html
,
/connecttest.txt
, or
/ncsi.txt
, those are handled and redirected back into the local flow. Unknown
routes get pushed back to the configured local domain.
Most of that behavior is controlled through
options.json
. That is where I set the SSID, password, domain, active template, display
type, dark mode, timestamps, and brightness.
The pages themselves are just HTML files in the
templates
folder. That part was intentional. I did not want a heavy frontend setup
for this. I wanted to be able to drop in a template, reboot the device,
and use it.
What happens on the device
One thing I wanted from the start was for the device to show me what it was doing without needing another screen open.
On boot, it shows a splash screen, prints status messages, sets the
display LED color, and keeps the onboard LED blinking as a heartbeat.
Messages are wrapped for the screen, can be scrolled with the buttons, and
are also written to
log.txt
.
That makes it useful during a live demo because I can see connection activity and submitted values right away. It also makes it useful after the fact because the log is still there to review.
How I set it up and use it live
The software setup is straightforward. I install the project dependencies
with
npm install
, open Thonny, connect the Pico W over USB, and copy everything from
src/
to the root of the board. The
src/modules/
folder has to be there, so that is one thing I always double check.
From there, I add my HTML templates to
src/templates/
. That is where the real-time part becomes useful. I can set the default
template in the config, but I can also change the active portal while the
device is already running by opening the on-device menu.
Pressing
B
opens the Select Template menu. Pressing
A
selects the highlighted template. The
X
and
Y
buttons scroll through the log or menu, which makes it easy to switch
pages on the fly during a demo without reconnecting the Pico to a
computer.
That matters a lot in practice. I can power the device on, let people join the network, swap between portal templates while it is live, and show the results directly on the screen as things happen. That is a much better fit for classroom demos than stopping to reflash or copy files every time I want to show a different portal.
Once the files are in place, I unplug the Pico from the computer, connect it to power, and it boots straight into the Pico Portal interface. At that point it is ready to broadcast the SSID, serve the selected page, and show activity in real time.
How the portal flow works
The flow is pretty straightforward.
The device boots and creates its own Wi-Fi network using the SSID from the config. Once a client connects, DNS requests are answered locally so the device can keep traffic pointed back to itself. The web server then serves the selected portal page.
The main route serves the active template. There is also a
/success
route for the bundled success page and a
/login
route that reads
username
,
password
, and an optional
redirect
parameter.
When a request hits
/login
, Pico Portal logs the submitted values and either reloads the page or
redirects to the target that was passed in.
That makes it work well with the templates from Red Portals, since those templates already submit to that same route structure.
How the code is laid out
The main entry point is
src/main.py
.
That file wires together the options, screen service, splash screen, LEDs,
message rendering, menu handling, button handling, and the portal service.
After that it runs the portal and button loop with
uasyncio
.
I split the project into small services because it keeps the code easier to follow and easier to change.
PortalService
handles the access point, DNS catch-all behavior, and web routes.
ScreenService
handles the display setup and backlight behavior for the supported
Pimoroni displays.
MessagesService
handles writing messages to the screen, wrapping text, scrolling,
timestamps, and file logging.
MenuService
scans the
templates
folder and lets me switch the active homepage template from the device.
ButtonService
maps the physical buttons so I can open the menu, select a template, and
scroll messages without reconnecting the Pico to a computer.
Why it works well with Red Portals
This project pairs well with Red Portals because the boundary between the two is simple.
Red Portals gives me compact single-file templates that are easy to move around and easy to drop into a device like this. Pico Portal gives me the AP, DNS, web server, display output, and template switching.
So if I want to demonstrate different portal scenarios, I do not need to
change the device code every time. I can build or copy a template into
src/templates/
, select it from the menu, and use the same hardware.
That made it a good fit for demos and classroom use because I could keep the platform the same and just swap the content depending on what I wanted to show.
Closing thoughts
Pico Portal started as a simple way for me to demonstrate Evil Portals in a classroom setting, but it turned into a solid little Pico W project on its own.
It is small, portable, easy to understand, and easy to reuse. I can load templates onto it, switch between them on the device while it is running, and show what is happening in real time without needing much else.
I sometimes put together complete demo units, but the repo is the best place to start if you want to build your own.
If you want the device side, start with Pico Portal.
If you want templates to use with it, check out Red Portals.
