Skip to content

neuling/PolyWiFi

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

35 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PolyWiFi Logo

PolyWiFi

ESP32 WiFi manager library for Arduino. Handles multi-credential storage, automatic reconnection, captive portal setup, and intelligent roaming between access points.

Features

  • Multi-credential storage — Save up to 10 WiFi networks in NVS, auto-connect to the strongest available
  • Fast connect — Caches BSSID + channel for ~200ms reconnect instead of ~3s scan-based
  • Captive portal — Web-based setup UI with custom parameters
  • Roaming — Automatic BSSID switching (mesh/multi-AP) and cross-SSID handover based on signal strength
  • WiFi on/off controlenable() / disable() to toggle WiFi, with pre-switch callback for graceful cleanup
  • Non-blocking — All operations use millis()-based timers, safe for real-time loop() tasks
  • Resilient state machine — Timeout guards on every state, exponential backoff, no dead ends

Quick Start

#include <PolyWiFi.h>

PolyWiFi wifi;

void setup() {
  Serial.begin(115200);
  wifi.setAPName("MyDevice-Setup");
  wifi.begin();
}

void loop() {
  wifi.loop();
}

On first boot (no saved credentials), a captive portal opens automatically. Connect to the "MyDevice-Setup" WiFi, open 192.168.4.1, and configure your network.

Installation

PlatformIO

Add to platformio.ini:

[env:esp32]
platform = espressif32
framework = arduino
board = esp32dev
lib_deps =
  bblanchon/ArduinoJson@^7.4.2

Configuration

All settings have sensible defaults. Call setters before begin().

Basic Settings

wifi.setAPName("MyDevice-Setup");       // Portal AP name (default: "PolyWiFi-Setup")
wifi.setAPPassword("secret");           // Portal AP password (default: open)
wifi.setConnectTimeout(15000);          // Per-credential connect timeout in ms (default: 15000)
wifi.setPortalTimeout(300000);          // Auto-close portal after ms (default: 300000 = 5min)
wifi.setMaxCredentials(10);             // Max stored networks (default: 10)
wifi.setMinSignalQuality(10);           // Min signal quality 0-100 (default: 10, maps to RSSI)
wifi.setAutoReconnect(true);            // Auto-reconnect on disconnect (default: true)
wifi.setFastConnectTimeout(3000);       // Fast connect attempt timeout in ms (default: 3000)
wifi.setPortalTitle("My Device");       // Title shown in portal UI
wifi.setDoneMessage("Saved! Rebooting...");  // Message after config save

Roaming

Roaming is disabled by default. When enabled, it monitors signal strength and switches to a better access point when the current signal degrades. It handles two cases:

  • BSSID roaming — Same SSID, different AP (e.g. mesh networks, enterprise WiFi)
  • Cross-SSID roaming — Different saved SSID with better signal (e.g. walking from home WiFi into mobile hotspot range)
wifi.setRoaming(true, -75);             // Enable roaming, trigger scan below -75 dBm
wifi.setRoamingHysteresis(10);          // Candidate must be 10 dB better (default: 10)
wifi.setRoamingCooldown(60000, 30000);  // Cooldown: 60s after switch, 30s after no-switch

Roaming uses the ESP32's hardware RSSI threshold event — zero CPU cost while signal is good. Scans only trigger when signal drops below the threshold.

Roaming Profiles

Here are some example configurations for different use cases:

Stationary sensor (battery-powered, never moves):

// No roaming needed — save energy
wifi.setRoaming(false);
wifi.setAutoReconnect(true);

Stationary device with mesh network (plugged in, multiple APs same SSID):

// Conservative BSSID roaming, no rush
wifi.setRoaming(true, -80);             // Only scan when signal is quite bad
wifi.setRoamingHysteresis(15);          // Large hysteresis — don't switch for small gains
wifi.setRoamingCooldown(120000, 120000); // 2 min cooldowns — save energy

Mobile device (robot, vehicle, handheld):

// Aggressive roaming — fast handover is critical
wifi.setRoaming(true, -65);             // Scan early while signal is still usable
wifi.setRoamingHysteresis(6);           // Switch even for moderate improvements
wifi.setRoamingCooldown(30000, 15000);  // Short cooldowns — keep checking
wifi.setFastConnectTimeout(2000);       // Shorter fast-connect attempt

Multi-zone coverage (home + mobile hotspot + office):

// Cross-SSID is the main feature here
wifi.setRoaming(true, -75);             // Default threshold
wifi.setRoamingHysteresis(10);          // Switch when new network is clearly better
wifi.setRoamingCooldown(60000, 30000);  // Moderate cooldowns

Pre-configured Credentials

Seed WiFi credentials in code. Useful for factory provisioning, fallback networks, or headless devices without portal.

wifi.addCredential("HomeWiFi", "password123");
wifi.addCredential("Office", "secret");
wifi.addCredential("iPhone-Hotspot", "12345678");

wifi.begin();  // Connects to strongest known network

Seeded credentials are stored in NVS like portal-added ones. Duplicates are skipped automatically — safe to call on every boot.

To clear all stored credentials (factory reset):

wifi.resetCredentials();

Custom Parameters

Add custom configuration fields to the portal UI. Values are stored in NVS and persist across reboots.

PolyWiFi wifi;

// Create params — constructor auto-registers with PolyWiFi
PolyWiFiParam header(&wifi, PW_HEADER, "cfg_hdr", "Device Configuration");
PolyWiFiParam nickname(&wifi, PW_INPUT, "nickname", "Device Name", "", "My Device");
PolyWiFiParam token(&wifi, PW_PASSWORD, "api_token", "API Token");
PolyWiFiParam role(&wifi, PW_SELECT, "role", "Role", "sensor", "sensor|actuator|gateway");
PolyWiFiParam debug(&wifi, PW_CHECKBOX, "debug", "Debug Mode", "false");
PolyWiFiParam notes(&wifi, PW_TEXTAREA, "notes", "Notes");

void setup() {
  wifi.begin();
}

void loop() {
  wifi.loop();

  // Read parameter values
  String name = wifi.getParam("nickname");
  String selectedRole = wifi.getParam("role");
  bool debugMode = wifi.getParam("debug") == "true";
}

Parameter Types

Type Description Default value Options field
PW_INPUT Text input Any string Placeholder text
PW_PASSWORD Password input Any string Placeholder text
PW_TEXTAREA Multi-line text Any string Placeholder text
PW_CHECKBOX Checkbox "true" or "false"
PW_SELECT Dropdown One of the options Pipe-separated: "opt1|opt2|opt3"
PW_HEADER Section header (visual only, no value)
PW_DIVIDER Visual divider (visual only, no value)

Callbacks

// Called on every state change
wifi.onStateChange([](pw_state_t state) {
  Serial.printf("State: %d\n", state);
});

// Called when credentials are saved from portal
wifi.onCredentialsSaved([]() {
  Serial.println("New WiFi saved!");
});

// Called on portal events (started, stopped, timeout, etc.)
wifi.onPortalActivity([](pw_portal_event_t event) {
  if (event == PW_EVT_PORTAL_TIMEOUT) {
    Serial.println("Portal timed out");
  }
});

// Called before WiFi switches AP or disconnects
wifi.onBeforeSwitch([](pw_switch_reason_t reason, const char* newSSID) -> bool {
  Serial.printf("WiFi switching (reason=%d)\n", reason);
  // Clean up connections before WiFi goes away
  ws.closeAll();
  return true;  // return false to cancel the switch
});

Switch Reasons

Reason Description Cancellable
PW_SWITCH_ROAMING_BSSID Better AP found on same SSID Yes
PW_SWITCH_ROAMING_SSID Better saved network found Yes
PW_SWITCH_RECONNECT Connection lost No (already disconnected)
PW_SWITCH_DISABLE disable() called Yes

WiFi Control

wifi.disable();       // Turns off WiFi radio, state machine paused
wifi.enable();        // Reconnects to best known network
wifi.isEnabled();     // true if WiFi is not disabled

Status & Info

wifi.isConnected();               // true if connected to a network
wifi.isPortalActive();            // true if captive portal is running
wifi.getState();                  // Current state (pw_state_t enum)
wifi.localIP();                   // IP address as String
wifi.ssid();                      // Connected SSID
wifi.rssi();                      // Current signal strength in dBm

State Machine

IDLE ──→ SCANNING ──→ CONNECTING ──→ CONNECTED
  ↑          │              │             │
  │          ↓              ↓             ↓
  │     PORTAL_ACTIVE ← (all failed)  RECONNECTING
  │          │                            │
  │          ↓                            │
  │     PORTAL_SAVING                     │
  │          │                            │
  └──────────┴────────────────────────────┘

  Any state ──→ DISABLED ──→ (enable) ──→ connect flow

States

State Description
PW_IDLE No connection, retries periodically if credentials exist
PW_SCANNING Scanning for saved networks
PW_CONNECTING Attempting connection to a network
PW_CONNECTED Connected, roaming active if enabled
PW_RECONNECTING Lost connection, scanning for networks
PW_PORTAL_STARTING Portal initializing
PW_PORTAL_ACTIVE Captive portal running
PW_PORTAL_SAVING Testing connection from portal submission
PW_DISABLED WiFi off, state machine paused (disable() / enable())

Portal Events

Event Description
PW_EVT_PORTAL_STARTED Portal opened
PW_EVT_PORTAL_STOPPED Portal closed
PW_EVT_PORTAL_TIMEOUT Portal auto-closed after timeout
PW_EVT_SCAN_REQUESTED User triggered scan in portal UI
PW_EVT_CONFIG_SUBMITTED User submitted WiFi config
PW_EVT_CONFIG_SUCCESS Connection test succeeded
PW_EVT_CONFIG_FAILED Connection test failed
PW_EVT_PARAMS_SAVED Custom parameters saved (triggers reboot)
PW_EVT_EXIT_REQUESTED User pressed exit in portal UI

RSSI Reference

RSSI Signal Quality
-30 to -50 dBm Excellent
-50 to -60 dBm Very good
-60 to -70 dBm Good
-70 to -80 dBm Moderate
-80 to -90 dBm Poor
Below -90 dBm Unusable

Manual Portal Control

// Open portal on button press (toggle)
if (buttonPressed) {
  if (wifi.isPortalActive()) {
    wifi.stopPortal();
  } else {
    wifi.startPortal();
  }
}

How It Works

  1. Boot — Loads saved credentials from NVS. If the last-connected network has cached BSSID+channel, attempts fast connect (~200ms). Otherwise scans for the strongest saved network.

  2. Connection failure — Cycles through saved credentials sorted by signal strength. If all fail, opens the captive portal.

  3. Connected — Monitors connection. If roaming is enabled, listens for the ESP32 hardware RSSI-low event. When triggered, scans for better APs (same SSID or other saved SSIDs). Switches if a significantly better option exists.

  4. Disconnect — Immediately scans and reconnects. If reconnection fails within 30s, opens the portal.

  5. Portal — Runs a web server on 192.168.4.1 with DNS captive portal redirect. Users can add/remove networks, configure custom parameters, or exit the portal to reconnect.

Memory Footprint

PolyWiFi adds approximately ~120 KB Flash and ~2 KB RAM on top of the base ESP32 Arduino + WiFi framework.

Flash RAM
Base (Arduino + WiFi) 722 KB 43 KB
With PolyWiFi 844 KB 46 KB
PolyWiFi overhead ~120 KB ~2 KB

Fits comfortably on all ESP32 variants (ESP32, ESP32-S2, S3, C3, C6) with the default 4 MB flash partition layout. Only external dependency is ArduinoJson — the web server uses the built-in WebServer.h.

Development

The committed platformio.ini targets a generic esp32dev board and only compiles the examples/CompileCheck sketch — it exists for CI and quick compilation checks, not for flashing real hardware.

To develop and test on your own board, create a local platformio_dev.ini (git-ignored) tailored to your setup:

[platformio]
src_dir = examples/Basic

[env:myboard]
platform = espressif32@^6.13.0
board = seeed_xiao_esp32s3        ; ← your board
framework = arduino
build_flags = -DARDUINO_USB_CDC_ON_BOOT=1
lib_deps =
    bblanchon/ArduinoJson@^7.4.2
    ; add display libs etc. as needed
monitor_speed = 115200
lib_extra_dirs = .

Build and flash with:

pio run -c platformio_dev.ini -t upload

Monitor serial output:

pio run -c platformio_dev.ini -t monitor

Tip: Keep platformio_dev.ini out of version control — it's specific to your hardware and wiring.

License

MIT

About

ESP32 WiFi manager library for Arduino. Handles multi-credential storage, automatic reconnection, captive portal setup, and intelligent roaming between access points.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors