Note: Full documentation has been moved to the
docs/folder.This project implements WiFi provisioning for ESP32-S3 using a secure, bonded BLE connection. The device receives WiFi credentials via JSON over BLE, connects to the network, and saves the credentials to NVS upon successful connection. Real-time status updates are sent to the provisioning app via BLE notifications.
# Build the project- ✅ **JSON-based Credentials**: Receives WiFi credentials in JSON format: `{"ssid":"network","password":"pass"}`
idf.py build- ✅ **Real-time Notifications**: Sends provisioning status updates to mobile app via BLE notifications
- ✅ **NVS Storage**: Saves WiFi credentials to Non-Volatile Storage after successful connection
# Flash to ESP32-S3- ✅ **Auto-reconnect**: Automatically connects to stored WiFi on subsequent boots
idf.py -p COM3 flash monitor- ✅ **Comprehensive Error Handling**: Detailed error codes for authentication failures, timeouts, etc.
```- ✅ **State Machine**: Tracks provisioning progress through well-defined states
- ✅ **Power Efficient**: Disables BLE after successful provisioning
## Project Structure
## Architecture
esp32_S3/### Components
├── main/ # Application source code
│ ├── main.c # Main application entry point1. BLE Provisioning (ble_provisioning.c/h)
│ ├── ble_provisioning.c # BLE GATT server implementation - GATT server with custom service
│ ├── wifi_manager.c # WiFi connection and NVS storage - WiFi credentials characteristic (write)
│ └── provisioning_state.c # State machine - Provisioning status characteristic (notify)
├── docs/ # Documentation - Secure bonding implementation
│ ├── README.md # Complete project documentation
│ ├── KOTLIN_INTEGRATION.md # Mobile app integration guide2. WiFi Manager (wifi_manager.c/h)
│ └── UUID_MAPPING.md # BLE UUID reference - WiFi connection handling
├── config/ # Configuration files - Event-driven architecture
│ ├── sdkconfig.defaults # Default ESP-IDF configuration - NVS credential storage
│ └── partitions.csv # Flash partition table - Connection retry logic
├── test/ # Test files and examples
│ ├── test_credentials.json3. Provisioning State Machine (provisioning_state.c/h)
│ └── test_credentials_examples.txt - State tracking and transitions
└── build/ # Build artifacts (generated) - Status code definitions
## Features4. **Main Application** (`main.c`)
- System initialization
- ✅ Secure BLE Communication with bonding/pairing - Component orchestration
- ✅ JSON-based WiFi credentials - Stored credential handling
- ✅ Real-time status notifications
- ✅ NVS credential storage### BLE Service Structure
- ✅ Auto-reconnect on boot
- ✅ Comprehensive error handling**Service UUID**: `00467768-6228-2272-4663-277478268000`
## Documentation| Characteristic | UUID | Properties | Description |
|----------------|------|------------|-------------|
- **[Complete Guide](docs/README.md)** - Full project documentation, architecture, and usage| State | `00467768-6228-2272-4663-277478268001` | Read | Current provisioning state |
- **[Kotlin Integration](docs/KOTLIN_INTEGRATION.md)** - Android app development guide| WiFi Credentials | `00467768-6228-2272-4663-277478268002` | Write | Receives JSON with SSID and password |
- **[UUID Mapping](docs/UUID_MAPPING.md)** - BLE service and characteristic reference| Status | `00467768-6228-2272-4663-277478268003` | Read, Notify | Sends status updates to app |
## Device Information### Provisioning States
- **Device Name**: `ESP32_WiFi_Prov`| State | Description |
- **Service UUID**: `00467768-6228-2272-4663-277478268000`|-------|-------------|
- **Security**: LE Secure Connections with MITM protection| `IDLE` | Waiting for BLE connection |
| `BLE_CONNECTED` | Client connected, waiting for credentials |
## License| `CREDENTIALS_RECEIVED` | Valid credentials received |
| `WIFI_CONNECTING` | Attempting WiFi connection |
This project is provided as-is for educational and commercial use.| `WIFI_CONNECTED` | Connected to WiFi, saving credentials |
| `PROVISIONED` | Successfully provisioned |
---| `WIFI_FAILED` | WiFi connection failed |
| `ERROR` | Error occurred during provisioning |
**Version**: 1.0.0
**Date**: November 2025### Status Codes
- `SUCCESS`: Operation successful
- `ERROR_INVALID_JSON`: Malformed JSON data
- `ERROR_MISSING_SSID`: SSID field missing
- `ERROR_MISSING_PASSWORD`: Password field missing
- `ERROR_WIFI_TIMEOUT`: Connection timeout
- `ERROR_WIFI_AUTH_FAILED`: Wrong password
- `ERROR_WIFI_NO_AP_FOUND`: SSID not found
- `ERROR_STORAGE_FAILED`: NVS write failed
## Prerequisites
- ESP-IDF v5.0 or later
- ESP32-S3 development board
- USB cable for flashing and monitoring
## Building and Flashing
### 1. Set up ESP-IDF environment
```powershell
# If not already set up, install ESP-IDF and configure the environment
# Follow: https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/get-started/
# Activate ESP-IDF environment
. $env:IDF_PATH\export.ps1
cd c:\Code\esp32_S3
idf.py set-target esp32s3
idf.py menuconfigRecommended menuconfig settings:
- Component config → Bluetooth → Enable Bluetooth
- Component config → Bluetooth → Bluedroid Enable
- Component config → Bluetooth → Enable BLE only
- Component config → Bluetooth → Enable secure connections
idf.py buildidf.py -p COM3 flash monitorReplace COM3 with your actual serial port.
- Device starts and initializes BLE provisioning
- BLE advertising begins with device name:
ESP32_WiFi_Prov - Connect to the device from your provisioning app
- Device will request bonding/pairing (Just Works method)
- Send WiFi credentials via JSON to the WiFi Credentials characteristic:
{
"ssid": "YourNetworkName",
"password": "YourPassword"
}- Monitor status updates via notifications on the Provisioning Status characteristic
- Upon successful connection, credentials are saved to NVS
- BLE provisioning service stops automatically
- Device reads stored credentials from NVS
- Automatically connects to the stored WiFi network
- BLE provisioning is NOT started (power saving)
- If connection fails, BLE provisioning starts as fallback
The app receives JSON notifications with the following structure:
{
"state": "WIFI_CONNECTED",
"status": "SUCCESS",
"message": "192.168.1.100",
"timestamp": 12345
}- Scan for devices advertising the provisioning service
- Connect to the ESP32 device
- Pair/Bond - Accept the pairing request
- Discover services and characteristics
- Subscribe to notifications on the Status characteristic (
00467768-6228-2272-4663-277478268003) - Write JSON credentials to the WiFi Credentials characteristic (
00467768-6228-2272-4663-277478268002) - Monitor status notifications until
PROVISIONEDorERRORstate
// UUIDs matching your app
val WIFI_SERVICE_UUID = UUID.fromString("00467768-6228-2272-4663-277478268000")
val STATE_CHAR_UUID = UUID.fromString("00467768-6228-2272-4663-277478268001")
val WIFICREDS_CHAR_UUID = UUID.fromString("00467768-6228-2272-4663-277478268002")
val STATUS_CHAR_UUID = UUID.fromString("00467768-6228-2272-4663-277478268003")
// 1. Connect and bond
device.connect()
device.createBond()
// 2. Discover services
val service = device.getService(WIFI_SERVICE_UUID)
val wifiCredChar = service.getCharacteristic(WIFICREDS_CHAR_UUID)
val statusChar = service.getCharacteristic(STATUS_CHAR_UUID)
// 3. Subscribe to notifications
statusChar.setNotificationEnabled(true)
statusChar.onNotificationReceived { data ->
val status = JSONObject(String(data))
Log.d("Provision", "Status: ${status.getString("state")} - ${status.getString("message")}")
when (status.getString("state")) {
"PROVISIONED" -> {
Log.i("Provision", "Success! Device IP: ${status.getString("message")}")
device.disconnect()
}
"ERROR", "WIFI_FAILED" -> {
Log.e("Provision", "Error: ${status.getString("status")} - ${status.getString("message")}")
}
}
}
// 4. Send credentials
val credentials = JSONObject().apply {
put("ssid", "MyNetwork")
put("password", "MyPassword123")
}
wifiCredChar.write(credentials.toString().toByteArray())- BLE Bonding: Enabled with MITM protection
- Pairing Method: Just Works (no PIN required)
- Encryption: All BLE communication encrypted after pairing
- NVS Encryption: Enabled in sdkconfig (optional)
- ✅ 128-bit UUIDs: Already implemented - matches your Kotlin app UUIDs
- Passkey Entry: Change
ESP_IO_CAP_NONEtoESP_IO_CAP_OUTand display a PIN - Certificate Pinning: Validate mobile app certificate before accepting credentials
- Rate Limiting: Add delays between failed connection attempts
- Secure Storage: Enable NVS encryption with unique keys per device
- OTA Updates: Implement secure firmware updates
- Timeout: Add timeout for provisioning mode
To reset the device and clear stored WiFi credentials:
// Call this function from your code
wifi_manager_clear_credentials();Or erase NVS flash completely:
idf.py -p COM3 erase-flash- Check BLE is enabled in menuconfig
- Verify
idf.py set-target esp32s3was run - Check logs for initialization errors
- Ensure mobile app supports BLE bonding
- Check device has enough memory
- Try erasing flash and reflashing
- Verify SSID and password are correct
- Check WiFi network is 2.4GHz (ESP32 doesn't support 5GHz)
- Ensure WPA2-PSK authentication
- Check distance to access point
- Ensure characteristic notifications are subscribed
- Verify bonding completed successfully
- Check MTU size (default 517 bytes)
The project uses ESP-IDF logging with the following tags:
MAIN: Main applicationBLE_PROV: BLE provisioningWIFI_MGR: WiFi managerPROV_STATE: State machine
Set log level in menuconfig or via code:
esp_log_level_set("BLE_PROV", ESP_LOG_DEBUG);- BLE Advertising: ~50-80mA
- BLE Connected: ~60-100mA
- WiFi Connected: ~80-150mA
- Deep Sleep (not implemented): ~10μA
After provisioning, BLE is disabled to save power.
- Add support for WPA3 networks
- Implement AP mode fallback provisioning
- Add device reset button (GPIO long press)
- Support multiple WiFi credential storage
- Add web-based provisioning interface
- Implement cloud provisioning
- Add MQTT connectivity check
- Battery level monitoring and reporting
This project is provided as-is for educational and commercial use.
Project: ESP32-S3 WiFi BLE Provisioning
Version: 1.0.0
Date: November 2025