This is a headless version of the WiFi configuration manager for ESP32 programs written in the Arduino framework. It provides JSON endpoints for configuration without a web UI. The library allows you to configure WiFi settings and custom parameters through HTTP endpoints.
The configuration is stored in files in the flash filesystem of the ESP. Debug output is written to Serial.
Only automatic IP address assignment (DHCP) is supported.
#include <SPIFFS.h>
#include <HeadlessWiFiSettings.h>
void setup() {
Serial.begin(115200);
SPIFFS.begin(true); // On first run, will format after failing to mount
HeadlessWiFiSettings.connect();
}
void loop() {
...
}void setup() {
Serial.begin(115200);
SPIFFS.begin(true); // On first run, will format after failing to mount
// Note that these examples call functions that you probably don't have.
HeadlessWiFiSettings.onSuccess = []() { green(); };
HeadlessWiFiSettings.onFailure = []() { red(); };
HeadlessWiFiSettings.onWaitLoop = []() { blue(); return 30; }; // delay 30 ms
HeadlessWiFiSettings.onPortalWaitLoop = []() { blink(); };
String host = HeadlessWiFiSettings.string("server_host", "default.example.org");
int port = HeadlessWiFiSettings.integer("server_port", 0, 65535, 443);
HeadlessWiFiSettings.connect(true, 30);
}HeadlessWiFiSettings can receive WiFi credentials over the Improv Wi-Fi serial protocol. Initialise the serial handler and call the loop handler from your main loop:
void setup() {
Serial.begin(115200);
SPIFFS.begin(true);
HeadlessWiFiSettings.beginSerialImprov("HeadlessWiFiSettings", "1.0");
HeadlessWiFiSettings.connect();
}
void loop() {
HeadlessWiFiSettings.serialImprovLoop();
}When the device first starts without any stored WiFi credentials, it will automatically enter portal mode. In portal mode:
- The ESP32 creates an access point with the SSID specified in
HeadlessWiFiSettings.hostname(default:esp32-XXXXXXwhere XXXXXX is a unique device ID) - A captive portal is started on port 80
- DNS server redirects all requests to the portal's IP address
Connect to the ESP32's access point and use the endpoints below to configure WiFi:
# Scan for available networks
curl http://192.168.4.1/wifi/scan
# Get current WiFi settings
curl http://192.168.4.1/wifi/main
# Configure WiFi credentials
curl -X POST http://192.168.4.1/wifi \
-d "wifi-ssid=YourNetworkName" \
-d "wifi-password=YourPassword"You can organize parameters into different endpoints using markEndpoint():
// Default "main" endpoint
String host = HeadlessWiFiSettings.string("server_host", "example.org");
int port = HeadlessWiFiSettings.integer("server_port", 443);
// Switch to "mqtt" endpoint
HeadlessWiFiSettings.markEndpoint("mqtt");
String mqttHost = HeadlessWiFiSettings.string("mqtt_host", "mqtt.example.org");
int mqttPort = HeadlessWiFiSettings.integer("mqtt_port", 1883);
// Legacy "extras" endpoint (for backward compatibility)
HeadlessWiFiSettings.markExtra();
String otherParam = HeadlessWiFiSettings.string("other_param", "value");All endpoints are served over HTTP on port 80. The API returns JSON responses and accepts form-encoded POST data.
Scans for available WiFi networks and returns their SSIDs and signal strengths.
Response:
{
"networks": {
"NetworkName1": -45,
"NetworkName2": -67,
"NetworkName3": -82
}
}Signal strength values are in dBm (negative numbers, closer to 0 is stronger). Duplicate SSIDs are merged with the strongest signal retained.
Returns the current values and defaults for the main parameter endpoint.
Response:
{
"values": {
"wifi-ssid": "CurrentNetwork",
"wifi-password": "***###***",
"server_host": "example.org",
"server_port": 443
},
"defaults": {
"server_host": "default.example.org",
"server_port": 443
}
}Note: Password fields always return the masked value ***###*** for security.
Returns values and defaults for a specific endpoint (e.g., /wifi/mqtt, /wifi/extras).
Response format: Same as /wifi/main
Updates configuration parameters on the main endpoint.
Request: Send parameters as form-encoded data:
POST /wifi
Content-Type: application/x-www-form-urlencoded
wifi-ssid=MyNetwork&wifi-password=MyPassword&server_host=api.example.com&server_port=8080
Response:
200 OK- Configuration saved successfully500 Internal Server Error- Error writing to flash filesystem404 Not Found- Endpoint doesn't exist
After a successful save, the onConfigSaved callback is triggered.
Updates configuration parameters for a specific endpoint.
Request format: Same as POST /wifi/main
Returns available options for dropdown parameters.
Example: GET /wifi/options/log_level
Response:
["debug", "info", "warning", "error"]Error Response:
404 Not Found- Parameter not found or not a dropdown type
Here's a comprehensive example demonstrating most features:
#include <SPIFFS.h>
#include <HeadlessWiFiSettings.h>
#include <ESPAsyncWebServer.h>
void setup() {
Serial.begin(115200);
SPIFFS.begin(true);
// Configure hostname
HeadlessWiFiSettings.hostname = "my-device-"; // Will append device ID
// Set up callbacks
HeadlessWiFiSettings.onConnect = []() {
Serial.println("Connecting to WiFi...");
};
HeadlessWiFiSettings.onSuccess = []() {
Serial.println("WiFi connected!");
Serial.println(WiFi.localIP());
};
HeadlessWiFiSettings.onFailure = []() {
Serial.println("WiFi connection failed");
};
HeadlessWiFiSettings.onConfigSaved = []() {
Serial.println("Configuration saved to flash");
};
// Define main configuration parameters
String serverHost = HeadlessWiFiSettings.string("server_host", "api.example.com", "Server Host");
int serverPort = HeadlessWiFiSettings.integer("server_port", 1, 65535, 443, "Server Port");
bool useTLS = HeadlessWiFiSettings.checkbox("use_tls", true, "Use TLS");
// Define MQTT parameters in separate endpoint
HeadlessWiFiSettings.markEndpoint("mqtt");
String mqttBroker = HeadlessWiFiSettings.string("mqtt_broker", "mqtt.local", "MQTT Broker");
int mqttPort = HeadlessWiFiSettings.integer("mqtt_port", 1883, "MQTT Port");
String mqttUser = HeadlessWiFiSettings.string("mqtt_user", "", "MQTT Username");
String mqttPass = HeadlessWiFiSettings.pstring("mqtt_pass", "", "MQTT Password");
// Define advanced parameters
HeadlessWiFiSettings.markExtra();
std::vector<String> logLevels = {"debug", "info", "warning", "error"};
int logLevel = HeadlessWiFiSettings.dropdown("log_level", logLevels, 1, "Log Level");
float updateInterval = HeadlessWiFiSettings.floating("update_interval", 0.1, 60.0, 5.0, "Update Interval (s)");
// Add custom HTTP endpoints
HeadlessWiFiSettings.onHttpSetup = [](AsyncWebServer* server) {
server->on("/status", HTTP_GET, [](AsyncWebServerRequest* request) {
request->send(200, "application/json", "{\"status\":\"ok\",\"uptime\":" + String(millis()) + "}");
});
server->on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
request->send(200, "text/html",
"<h1>My Device</h1>"
"<p>Configure via <a href='/wifi/main'>/wifi/main</a></p>"
"<p>MQTT settings at <a href='/wifi/mqtt'>/wifi/mqtt</a></p>"
);
});
};
// Connect to WiFi (30 second timeout, show portal on failure)
HeadlessWiFiSettings.connect(true, 30);
// Now use the configured values
Serial.printf("Server: %s:%d (TLS: %s)\n",
serverHost.c_str(), serverPort, useTLS ? "yes" : "no");
Serial.printf("MQTT: %s:%d\n", mqttBroker.c_str(), mqttPort);
Serial.printf("Log level: %s\n", logLevels[logLevel].c_str());
}
void loop() {
// Your application code here
delay(100);
}Once the device is running, you can interact with it using curl or any HTTP client:
# Check status
curl http://my-device-123456.local/status
# Get main configuration
curl http://my-device-123456.local/wifi/main
# Update server settings
curl -X POST http://my-device-123456.local/wifi \
-d "server_host=newserver.com" \
-d "server_port=8443" \
-d "use_tls=1"
# Get MQTT configuration
curl http://my-device-123456.local/wifi/mqtt
# Update MQTT credentials
curl -X POST http://my-device-123456.local/wifi/mqtt \
-d "mqtt_user=myuser" \
-d "mqtt_pass=mypassword"
# Get dropdown options
curl http://my-device-123456.local/wifi/options/log_level
# Scan for WiFi networks
curl http://my-device-123456.local/wifi/scanAutomated installation:
Getting the source for manual installation:
git clone https://github.com/ESPresense/HeadlessWiFiSettings- .zip and .tar.gz files
This library uses a singleton instance (object), HeadlessWiFiSettings, and is not
designed to be inherited from (subclassed), or to have multiple instances.
bool connect(bool portal = true, int wait_seconds = 60);If no WiFi network is configured yet, starts the configuration portal.
In other cases, it will attempt to connect to the network in station (WiFi
client) mode, and wait until either a connection is established, or
wait_seconds has elapsed. Returns true if connection succeeded.
By default, a failed connection (no connection established within the timeout)
will cause the configuration portal to be started. Given portal = false, it
will instead return false.
To wait forever until WiFi is connected, use wait_seconds = -1. In this case,
the value of portal is ignored.
void portal();Disconnects any active WiFi and turns the ESP into an access point that serves the configuration endpoints.
This function never ends. A restart is required to resume normal operation.
String string(String name, String init = "", String label = name);
String string(String name, unsigned int max_length, String init = "", String label = name);
String string(String name, unsigned int min_length, unsigned int max_length, String init = "", String label = name);Configures a custom string parameter and returns the current value. When no value is configured, the init value is returned.
String pstring(String name, String init = "", String label = name);Like string(), but for password fields. Values are masked in JSON responses as ***###***.
long integer(String name, long init = 0, String label = name);
long integer(String name, long min, long max, long init = 0, String label = name);Configures a custom integer parameter with optional min/max constraints.
float floating(String name, float init = 0, String label = name);
float floating(String name, long min, long max, float init = 0, String label = name);Configures a custom floating-point parameter with optional min/max constraints.
bool checkbox(String name, bool init = false, String label = name);Configures a boolean checkbox parameter.
long dropdown(String name, std::vector<String> options, long init = 0, String label = name);Configures a dropdown parameter with predefined options. Returns the index of the selected option. The available options can be retrieved via /wifi/options/{name}.
Note: All parameter configuration functions should be called before calling .connect() or .portal().
The name is used as the filename in SPIFFS and as the parameter name in JSON endpoints.
void markEndpoint(String name);Switches to a named endpoint. All subsequent parameter definitions will be grouped under this endpoint and accessible via /wifi/{name}.
// Parameters added to "main" endpoint
String host = HeadlessWiFiSettings.string("host", "example.org");
// Switch to custom endpoint
HeadlessWiFiSettings.markEndpoint("mqtt");
String mqttHost = HeadlessWiFiSettings.string("mqtt_host", "broker.local");void markExtra();Convenience function that switches to the "extras" endpoint. Equivalent to markEndpoint("extras").
Note: because of the way this library is designed, any assignment to the member variables should be done before calling any of the functions.
StringName to use as the hostname and SSID for the access point. By default, this is set to "esp32-" or "esp8266-", depending on the platform.
If it ends in a - character, a unique 6 digit device identifier is added automatically.
StringThis variable is used to protect the configuration portal's softAP. When no password is explicitly assigned before the first custom configuration parameter is defined, a password will be automatically generated.
boolBy setting this to true, before any custom configuration parameter is defined,
secure mode will be forced, instead of the default behavior.
Callbacks can be assigned to customize behavior at various stages. All callbacks are optional.
std::function<void(AsyncWebServer*)> onHttpSetup;Called during HTTP server setup, before the server starts. Allows you to add custom routes or modify server configuration.
HeadlessWiFiSettings.onHttpSetup = [](AsyncWebServer* server) {
server->on("/custom", HTTP_GET, [](AsyncWebServerRequest* request) {
request->send(200, "text/plain", "Custom endpoint");
});
};std::function<void(void)> onConnect;Called when attempting to connect to WiFi, before the connection starts.
std::function<void(void)> onSuccess;Called when WiFi connection is successful.
std::function<void(void)> onFailure;Called when WiFi connection fails after the timeout period.
std::function<int(void)> onWaitLoop;Called repeatedly while waiting for WiFi connection. Return the delay in milliseconds until the next call (default: 100ms).
HeadlessWiFiSettings.onWaitLoop = []() {
// Blink LED or update display
return 50; // Call again in 50ms
};std::function<void(void)> onPortal;Called when the configuration portal starts.
std::function<void(void)> onPortalView;Called when someone views the portal page (currently unused in headless mode).
std::function<int(void)> onPortalWaitLoop;Called repeatedly while the portal is running. Return the delay in milliseconds until the next call.
std::function<void(void)> onConfigSaved;Called after configuration parameters are successfully saved to flash storage.
std::function<void(String&)> onUserAgent;Called with the User-Agent string from HTTP requests (currently unused in headless mode).
This was forked from https://github.com/Juerd/ESP-WiFiSettings when it was converted to use AsyncWebServer instead of WebServer. This version removes the web UI in favor of JSON endpoints.