Device Provisioning
Provisioning is the process of getting WiFi credentials and a device token onto a device without hard-coding them in the firmware. This is essential for products shipped to end users who supply their own WiFi network.
The Vwire library provides two built-in provisioning methods:
| Method | Best for |
|---|---|
| SmartConfig | Vwire mobile app workflow; credentials sent wirelessly from phone |
| AP Mode | Works on all networks including 5 GHz; device creates a captive-portal hotspot |
Both methods save credentials to NVS flash and reuse them on subsequent boots. Requires #include <VwireProvisioning.h>.
Method 1 — SmartConfig (via Vwire app)
The device listens for an encrypted broadcast from the Vwire Android app. The user selects the device in the app, enters the WiFi password, and the device receives everything it needs.
SmartConfig only works with 2.4 GHz WiFi networks.
#include <Vwire.h>
#include <VwireProvisioning.h>
#define SMARTCONFIG_TIMEOUT 120000 // 2 minutes
bool provisioned = false;
VWIRE_CONNECTED() {
Serial.println("Connected to Vwire!");
digitalWrite(LED_BUILTIN, HIGH);
}
void onProvisioningState(VwireProvisioningState state) {
if (state == VWIRE_PROV_SUCCESS) {
provisioned = true;
Serial.println("Provisioning successful!");
} else if (state == VWIRE_PROV_TIMEOUT || state == VWIRE_PROV_FAILED) {
Serial.println("Provisioning failed — retrying...");
VwireProvision.startSmartConfig(SMARTCONFIG_TIMEOUT);
}
}
void setup() {
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
VwireProvision.onStateChange(onProvisioningState);
if (VwireProvision.hasCredentials()) {
// Reuse stored credentials from previous provisioning
Vwire.config(VwireProvision.getAuthToken());
Vwire.begin(VwireProvision.getSSID(), VwireProvision.getPassword());
} else {
// First boot — start SmartConfig
Serial.println("Open Vwire app → Settings → Smart Config");
VwireProvision.startSmartConfig(SMARTCONFIG_TIMEOUT);
}
}
void loop() {
if (VwireProvision.isProvisioning()) {
VwireProvision.run();
return;
}
// After successful provisioning, connect to Vwire
if (provisioned && !Vwire.connected()) {
Vwire.config(VwireProvision.getAuthToken());
Vwire.begin(); // WiFi already connected from provisioning
provisioned = false;
}
Vwire.run();
// Your normal code here
}
Method 2 — AP Mode (captive portal)
The device starts as a WiFi hotspot named Vwire-XXXX (last 4 chars of MAC). The user connects to it, a browser portal opens automatically, and the user fills in their WiFi credentials and device token.
Works with both 2.4 GHz and 5 GHz networks. Ideal when the phone and router are on 5 GHz.
#include <Vwire.h>
#include <VwireProvisioning.h>
#define AP_TIMEOUT 300000 // 5 minutes
bool provisioned = false;
VWIRE_CONNECTED() {
Serial.println("Connected to Vwire!");
Vwire.syncVirtual(V0);
}
void onProvisioningState(VwireProvisioningState state) {
if (state == VWIRE_PROV_AP_ACTIVE) {
Serial.printf("Connect to: %s\n", VwireProvision.getAPSSID());
Serial.printf("Then open: http://%s\n", VwireProvision.getAPIP().c_str());
} else if (state == VWIRE_PROV_SUCCESS) {
provisioned = true;
}
}
void setup() {
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
VwireProvision.onStateChange(onProvisioningState);
if (VwireProvision.hasCredentials()) {
Vwire.config(VwireProvision.getAuthToken());
Vwire.begin(VwireProvision.getSSID(), VwireProvision.getPassword());
} else {
// Start AP mode captive portal
VwireProvision.startAPMode(AP_TIMEOUT);
}
}
void loop() {
if (VwireProvision.isProvisioning()) {
VwireProvision.run();
return;
}
if (provisioned && !Vwire.connected()) {
Vwire.config(VwireProvision.getAuthToken());
Vwire.begin();
provisioned = false;
}
Vwire.run();
}
Credential storage
Both methods save credentials to NVS flash (non-volatile storage). They persist across power cycles and firmware updates.
| Function | Description |
|---|---|
VwireProvision.hasCredentials() | Check if credentials exist in NVS |
VwireProvision.getAuthToken() | Read stored auth token |
VwireProvision.getSSID() | Read stored WiFi SSID |
VwireProvision.getPassword() | Read stored WiFi password |
VwireProvision.clearCredentials() | Erase stored credentials (force re-provisioning) |
Re-provisioning
To allow factory reset / re-provisioning via a physical button, detect a long press and clear credentials:
#define BUTTON_PIN 0 // GPIO0 = Boot/Flash button on most ESP boards
#define HOLD_TIME 5000 // Hold 5 seconds to reset
unsigned long buttonStart = 0;
void checkResetButton() {
if (digitalRead(BUTTON_PIN) == LOW) {
if (buttonStart == 0) buttonStart = millis();
else if (millis() - buttonStart >= HOLD_TIME) {
Serial.println("Clearing credentials — restarting...");
VwireProvision.clearCredentials();
ESP.restart();
}
} else {
buttonStart = 0;
}
}
Call checkResetButton() inside loop().
Provisioning flow
Factory firmware (no token) → Device boots
→ Publishes to provisioning topic with hardware ID
→ VWire Server creates device + token
→ Server publishes token securely back to device
→ Device stores token in NVS flash
→ Device reconnects with permanent token
Step 1: Provision API request
The simplest approach is to call the VWire Provisioning API from the device over HTTPS:
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <Preferences.h>
Preferences prefs;
String deviceToken;
bool provision() {
String hardwareId = WiFi.macAddress(); // e.g. "A4:CF:12:34:56:78"
hardwareId.replace(":", ""); // "A4CF12345678"
HTTPClient http;
http.begin("https://app.vwire.io/api/v1/provisioning");
http.addHeader("Content-Type", "application/json");
http.addHeader("X-Provision-Key", PROVISION_KEY); // shared factory key
StaticJsonDocument<256> body;
body["hardwareId"] = hardwareId;
body["productType"] = "SensorNode-v2";
body["firmwareVersion"] = FIRMWARE_VERSION;
String bodyStr;
serializeJson(body, bodyStr);
int code = http.POST(bodyStr);
if (code != 200) { http.end(); return false; }
StaticJsonDocument<256> resp;
deserializeJson(resp, http.getString());
http.end();
deviceToken = resp["token"].as<String>();
prefs.begin("vwire", false);
prefs.putString("token", deviceToken);
prefs.end();
return true;
}
void setup() {
prefs.begin("vwire", true);
deviceToken = prefs.getString("token", "");
prefs.end();
if (deviceToken.isEmpty()) {
if (!provision()) {
Serial.println("Provisioning failed — restarting");
ESP.restart();
}
}
Vwire.begin(SSID, PASSWORD);
}
Provisioning key
The X-Provision-Key header carries a short-lived Provisioning Key scoped to your organization.
Generate one in Dashboard → Settings → Provisioning Keys.
| Property | Value |
|---|---|
| Scope | Organization-level |
| Expiry | Configurable (1 day – never) |
| Max uses | Configurable (unlimited by default) |
| Revocation | Instant from dashboard |
NVS token persistence
Once provisioned, the device stores the token in ESP32 NVS (Non-Volatile Storage):
// Save
Preferences prefs;
prefs.begin("vwire", false);
prefs.putString("token", token);
prefs.end();
// Load
prefs.begin("vwire", true);
String token = prefs.getString("token", "");
prefs.end();
NVS survives firmware updates but is erased by esptool.py erase_flash.
Mass provisioning (factory flow)
For manufacturing lines:
- Flash all devices with identical factory firmware (no token).
- Each device auto-provisions on first WiFi connection using the shared Provisioning Key.
- The VWire Server auto-assigns them to your organization's default project.
- Rename and assign devices in the dashboard after deployment.