I was able to fix the dynamic provisioning. Here is my full code for anyone that may be interested. After 2 minutes if the ESP32 cannot find a network it will erase the wifi credentials and enter back into provisioning mode.
#define BLYNK_TEMPLATE_ID "TMPL2IJw3-ytG"
#define BLYNK_TEMPLATE_NAME "Quickstart Template"
// Always include WiFi.h first!
#include <WiFi.h>
#include <Arduino.h>
#include <FastLED.h>
#include "BlynkEdgent.h"
// ---- User LED Setup ----
#define DATA_PIN 2
#define NUM_LEDS 8
#define KILL_PIN 5
#define INT_PIN 4
CRGB leds[NUM_LEDS];
// ---- Blynk Virtual Pins ----
#define VPIN_RED V2
#define VPIN_GREEN V3
#define VPIN_BLUE V4
#define VPIN_BRIGHT V6
#define VPIN_JOY_X V7
#define VPIN_JOY_Y V8
#define VPIN_MODE V9
#define VPIN_WHITE V10
uint8_t colorR = 0, colorG = 0, colorB = 255;
uint8_t brightnessSlider = 255;
int joyX = 0, joyY = 0;
int mode = 1;
bool whiteOverride = false;
// ---- BlynkTimer ----
BlynkTimer timer;
// ---- Network Connection Monitoring ----
const unsigned long WIFI_CONNECT_TIMEOUT = 120UL * 1000UL; // 120 seconds
bool provisioningTriggered = false;
unsigned long wifiConnectStart = 0;
// ---- Shutdown Logic ----
unsigned long startMillis = 0;
const unsigned long SHUTDOWN_TIMEOUT = 45UL * 60UL * 1000UL; // 45 min
bool shutdownInitiated = false;
// ---- LED State Effects ----
enum LedMode { LED_NORMAL, LED_FLASH_YELLOW, LED_SOLID_BLUE };
LedMode currentLedMode = LED_NORMAL;
unsigned long lastLedEffectTime = 0;
bool blinkState = false;
// ---- Edgent-Proper Provisioning Erase ----
void forceProvisioningProper() {
Serial.println("Requesting WiFi config erase and provisioning...");
BlynkState::set(MODE_RESET_CONFIG);
unsigned long start = millis();
while (BlynkState::get() != MODE_WAIT_CONFIG && millis() - start < 5000) {
BlynkEdgent.run();
delay(10);
}
Serial.println("Erase complete! Rebooting into provisioning mode.");
ESP.restart();
}
// ---- LED Effects ----
void flashYellowEffect() {
unsigned long now = millis();
if (now - lastLedEffectTime > 300) {
blinkState = !blinkState;
fill_solid(leds, NUM_LEDS, blinkState ? CRGB::Yellow : CRGB::Black);
FastLED.show();
lastLedEffectTime = now;
}
}
void solidBlueEffect() {
fill_solid(leds, NUM_LEDS, CRGB::Blue);
FastLED.show();
}
void setLedMode(LedMode mode) {
if (currentLedMode != mode) {
currentLedMode = mode;
lastLedEffectTime = millis();
blinkState = false;
}
}
// ---- User Effects ----
void effectJoystickControl() {
if (currentLedMode != LED_NORMAL) return;
const int deadZone = 20;
fill_solid(leds, NUM_LEDS, CRGB::Black);
if (abs(joyX) < deadZone && abs(joyY) < deadZone) {
FastLED.show();
return;
}
float angle = atan2(-joyY, joyX) * 180 / PI;
if (angle < 0) angle += 360;
angle = fmod((angle + 135), 360.0);
float anglePerLED = 360.0 / NUM_LEDS;
for (int i = 0; i < NUM_LEDS; i++) {
float ledAngle = i * anglePerLED;
float diff = abs(angle - ledAngle);
if (diff > 180) diff = 360 - diff;
float fade = constrain(1.0 - (diff / anglePerLED), 0, 1);
uint8_t level = (brightnessSlider * fade);
leds[i] = CRGB((colorR * level) / 255, (colorG * level) / 255, (colorB * level) / 255);
}
FastLED.show();
}
void effectRainbowComet() {
if (currentLedMode != LED_NORMAL) return;
static uint8_t cometPos = 0;
static uint8_t hue = 0;
fadeToBlackBy(leds, NUM_LEDS, 20);
leds[cometPos] = CHSV(hue++, 255, brightnessSlider);
cometPos = (cometPos + 1) % NUM_LEDS;
FastLED.show();
}
void effectElectricChase() {
if (currentLedMode != LED_NORMAL) return;
static uint8_t pos = 0;
static uint8_t hueBase = 0;
fill_solid(leds, NUM_LEDS, CRGB::Black);
for (int i = 0; i < 3; i++) {
leds[(pos + i) % NUM_LEDS] = CHSV(hueBase + i * 20, 255, brightnessSlider);
}
pos = (pos + 1) % NUM_LEDS;
hueBase += 8;
FastLED.show();
}
void effectBlazingSparkle() {
if (currentLedMode != LED_NORMAL) return;
fadeToBlackBy(leds, NUM_LEDS, 40);
for (int i = 0; i < 3; i++) {
int idx = random8(NUM_LEDS);
leds[idx] = CHSV(random8(), 255, brightnessSlider);
}
FastLED.show();
}
void effectHandler() {
if (currentLedMode != LED_NORMAL) return;
FastLED.setBrightness(brightnessSlider);
if (whiteOverride) {
fill_solid(leds, NUM_LEDS, CRGB::White);
FastLED.show();
return;
}
switch (mode) {
case 1: effectJoystickControl(); break;
case 2: effectRainbowComet(); break;
case 3: effectElectricChase(); break;
case 4: effectBlazingSparkle(); break;
default: fill_solid(leds, NUM_LEDS, CRGB::Black); FastLED.show(); break;
}
}
// ---- Blynk Handlers ----
BLYNK_CONNECTED() {
Blynk.syncVirtual(VPIN_RED, VPIN_GREEN, VPIN_BLUE, VPIN_BRIGHT, VPIN_JOY_X, VPIN_JOY_Y, VPIN_MODE, VPIN_WHITE);
}
BLYNK_WRITE(VPIN_RED) { colorR = param.asInt(); }
BLYNK_WRITE(VPIN_GREEN) { colorG = param.asInt(); }
BLYNK_WRITE(VPIN_BLUE) { colorB = param.asInt(); }
BLYNK_WRITE(VPIN_BRIGHT) { brightnessSlider = param.asInt(); }
BLYNK_WRITE(VPIN_JOY_X) { joyX = param.asInt(); }
BLYNK_WRITE(VPIN_JOY_Y) { joyY = param.asInt(); }
BLYNK_WRITE(VPIN_MODE) { mode = param.asInt(); }
BLYNK_WRITE(VPIN_WHITE) { whiteOverride = param.asInt(); }
// ---- Setup ----
void setup() {
pinMode(KILL_PIN, OUTPUT);
digitalWrite(KILL_PIN, HIGH);
pinMode(INT_PIN, INPUT);
Serial.begin(115200);
FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, NUM_LEDS);
FastLED.clear();
FastLED.show();
timer.setInterval(100L, effectHandler);
BlynkEdgent.begin();
startMillis = millis(); // 45-min shutdown timer
// For auto-provision: start timer at boot if not connected
wifiConnectStart = millis();
}
// ---- Main Loop ----
void loop() {
// Power button shutdown (INT_PIN low = kill)
if (digitalRead(INT_PIN) == LOW) {
Serial.println("Shutdown requested");
delay(50);
digitalWrite(KILL_PIN, LOW);
while (true) delay(1000);
}
// ---- Auto-provision if not connected ----
if (!provisioningTriggered &&
(BlynkState::get() == MODE_CONNECTING_NET || BlynkState::get() == MODE_CONNECTING_CLOUD)) {
if (wifiConnectStart == 0) wifiConnectStart = millis();
if (millis() - wifiConnectStart > WIFI_CONNECT_TIMEOUT) {
provisioningTriggered = true; // only run once
forceProvisioningProper();
}
}
// If running, reset the timer
if (BlynkState::get() == MODE_RUNNING) {
wifiConnectStart = 0;
provisioningTriggered = false;
}
// ---- LED Feedback for Edgent state ----
if (BlynkState::get() == MODE_CONNECTING_NET || BlynkState::get() == MODE_CONNECTING_CLOUD) {
setLedMode(LED_FLASH_YELLOW);
flashYellowEffect();
} else if (BlynkState::get() == MODE_WAIT_CONFIG) {
setLedMode(LED_SOLID_BLUE);
solidBlueEffect();
} else {
setLedMode(LED_NORMAL);
timer.run();
}
// ---- Auto Shutdown after 45 minutes ----
if (!shutdownInitiated && (millis() - startMillis > SHUTDOWN_TIMEOUT)) {
Serial.println("45 minutes elapsed. Automatic shutdown now.");
digitalWrite(KILL_PIN, LOW);
shutdownInitiated = true;
while (true) delay(1000);
}
BlynkEdgent.run();
}