Skip to main content

WiFi configuration captive portal for Raspberry Pi — button-triggered AP + web UI

Project description

pi-wifi-setup

WiFi configuration captive portal for Raspberry Pi.
On first boot (or button press) the device creates a hotspot — connect from your phone, pick a network, enter the password, done.


How it works

  1. On startup, if no WiFi credentials are saved, the Pi automatically raises a hotspot
  2. hostapd creates a virtual uap0 AP interface alongside the existing wlan0
  3. dnsmasq handles DHCP and redirects all DNS to the captive portal
  4. User connects to the hotspot and is directed to http://192.168.4.1
  5. The web UI lists nearby networks; user selects one and enters the password
  6. Credentials are saved via NetworkManager (nmcli) and persist across reboots
  7. AP shuts down, device resumes normal operation
  8. A physical button can trigger setup mode at any time to change the network

Requirements

System packages:

sudo apt install hostapd dnsmasq network-manager
sudo systemctl enable NetworkManager
sudo systemctl disable dnsmasq

Python: 3.9+
Hardware: Any Raspberry Pi with built-in WiFi:

Model Chip AP mode
Pi Zero W CYW43438
Pi Zero 2 W CYW43438
Pi 3B / 3B+ CYW43438 / CYW43455
Pi 4B CYW43455
Pi 5 CYW43455

Pi Zero W / Zero 2 W note: some units require hostapd to run without the driver=nl80211 line. pi-wifi-setup detects this automatically and retries without the driver directive if the first attempt fails.


Installation

pip install pi-wifi-setup

Button wiring

Connect a momentary push button between GPIO 17 (BCM) and GND.
No resistor needed — the internal pull-up is enabled in software.

3.3V  ──────────────────┐
                        │  (internal pull-up via lgpio)
GPIO 17 (BCM) ──────────┤
                        │
                [BUTTON]
                        │
GND ────────────────────┘

Physical pin reference (Pi 40-pin header):

Signal Physical Pin BCM
GND 9, 14, 20 …
GPIO 17 Pin 11 17

You can use any free GPIO pin — pass it as button_gpio=<pin>.


Quick start

import threading
import time
from pi_wifi_setup import SetupMode

# --- your application ---

_stop_event = threading.Event()

def main_loop():
    while not _stop_event.is_set():
        print("Working...")   # replace with your actual logic
        time.sleep(1)

def on_pause():
    """Called before the setup AP starts — stop your app here."""
    _stop_event.set()

def on_resume():
    """Called after a successful WiFi connection — restart your app here."""
    _stop_event.clear()
    threading.Thread(target=main_loop, daemon=True).start()

# --- WiFi setup ---

setup = SetupMode(
    ap_ssid="MyDevice",
    ap_password="setup1234",  # WPA2 password, or None for open network
    button_gpio=17,           # BCM pin number (physical pin 11)
    auto_on_no_credentials=True,
)

# Blocks until WiFi is configured if no credentials are saved.
# After that runs in the background, waiting for a button press.
setup.start(on_pause=on_pause, on_resume=on_resume)

# WiFi is ready — start your app
main_loop()

If your application does not need to pause during reconfiguration (e.g. it is stateless or handles reconnection itself), on_pause and on_resume can be omitted:

setup = SetupMode(ap_ssid="MyDevice", ap_password="setup1234")
setup.start()  # blocks until WiFi configured, then continues

main_loop()

First boot (no credentials saved)

setup = SetupMode(ap_ssid="MyDevice", ap_password="setup1234")
setup.start()  # blocks until connected, then continues

Already configured — button only

setup = SetupMode(
    ap_ssid="MyDevice",
    ap_password="setup1234",
    auto_on_no_credentials=False,
)
setup.start(on_pause=app.pause, on_resume=app.resume)

Connect programmatically (no UI)

from pi_wifi_setup import WiFiConnector

wifi = WiFiConnector(interface="wlan0")

if not wifi.has_credentials():
    wifi.connect(ssid="HomeNetwork", password="secret123", timeout=30)

status = wifi.get_status()
print(status["ip_address"])

API

SetupMode

Parameter Default Description
button_gpio 17 BCM GPIO pin for the setup button
ap_ssid "VendingSetup" Hotspot SSID
ap_password None WPA2 password, None for open network
long_press_seconds 3.0 Hold duration to trigger setup
wifi_interface "wlan0" Primary WiFi interface
auto_on_no_credentials True Auto-start AP if no network is saved
Method Description
start(on_pause, on_resume) Start listener; blocks if auto AP is triggered
run_blocking() Start AP immediately and block until connected
stop() Stop button listener
has_credentials() True if a WiFi connection is saved in NetworkManager

WiFiConnector

Method Description
has_credentials() Check for saved connections
connect(ssid, password, timeout) Connect and persist credentials
get_status() Return {connected, ssid, ip_address, wpa_state}

Architecture

pi_wifi_setup/
├── setup_mode.py      # Orchestrator
├── ap_manager.py      # hostapd + dnsmasq + iptables
├── web_server.py      # Flask captive portal
├── wifi_connector.py  # nmcli wrapper
├── scanner.py         # WiFi network scanner
├── button_handler.py  # GPIO long-press detector (lgpio)
└── templates/
    └── index.html     # Web UI

License

MIT © 2026 Mikhail Shmarov


Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pi_wifi_setup-1.0.0.tar.gz (16.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pi_wifi_setup-1.0.0-py3-none-any.whl (16.2 kB view details)

Uploaded Python 3

File details

Details for the file pi_wifi_setup-1.0.0.tar.gz.

File metadata

  • Download URL: pi_wifi_setup-1.0.0.tar.gz
  • Upload date:
  • Size: 16.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for pi_wifi_setup-1.0.0.tar.gz
Algorithm Hash digest
SHA256 093878153010672b2c5527393afb0e7c0ca91cc75883f586c6d7549c3fd8d627
MD5 a26e60bf04416c89b66afa6022b6679c
BLAKE2b-256 084d8e7ab3381128f62c66f1cd03aa1099089d71e1b632dfeb9a31606f4b649b

See more details on using hashes here.

File details

Details for the file pi_wifi_setup-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: pi_wifi_setup-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 16.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for pi_wifi_setup-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d5b81d62a1e7e7c029d9492720a7b6facc77f625e191b843c78ab2fa98c83966
MD5 28067cd4372456f2df564ef422aac558
BLAKE2b-256 6e050e192167152f438a5cfda1d17a63f66f02b18553008f907b4dce62ad8495

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page