Skip to main content

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:

MethodBest for
SmartConfigVwire mobile app workflow; credentials sent wirelessly from phone
AP ModeWorks 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.

FunctionDescription
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.

PropertyValue
ScopeOrganization-level
ExpiryConfigurable (1 day – never)
Max usesConfigurable (unlimited by default)
RevocationInstant 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:

  1. Flash all devices with identical factory firmware (no token).
  2. Each device auto-provisions on first WiFi connection using the shared Provisioning Key.
  3. The VWire Server auto-assigns them to your organization's default project.
  4. Rename and assign devices in the dashboard after deployment.