Issue with Usermods in WLED for Controlling an LED Strip with a Distance Sensor

Issue with Usermods in WLED for Controlling an LED Strip with a Distance Sensor

Hello everyone,

I’m new to WLED, but I have experience programming on ESP32 and VS code. I’m trying to implement a custom effect using a usermod, but with the limited documentation available, I haven’t been able to make much progress, even with AI assistance.

My goal is to control the progressive lighting of an LED strip using a VCNL4200 distance sensor. When the sensor detects an object, I want the LEDs to turn on one by one until the entire strip is illuminated. Then, after 1 minute, the strip should turn off until the next detection. The LED strip would be installed on a staircase, with sensors at both ends.

I have the sensor working correctly, but my issue is figuring out how to force the LED strip to be controlled exclusively from the usermod code instead of the WLED UI. When I send a command to turn off the LEDs from my code, they briefly turn off but then immediately turn back on (I assume because the web interface had them previously enabled).

What I want is that when the “enable” option of my usermod is activated, no other effects or features should work. But when this option is disabled, I should be able to use and play with the effects freely.

I also tried running the VL53L0X_gestures example from the usermods folder, but I don’t know how to activate it or make it work. Is there a detailed guide or a step-by-step example for this? When activated, should something appear in the WLED UI under usermods?

I’m attaching all the modified code I’ve written so far and will try to provide as much detail as possible.

Feel free to point out any issues with my code if it’s incorrect, as I’m not a professional.

This is the .h file inside the usermods folder.

#pragma once

#include "wled.h"
#include <Adafruit_VCNL4200.h>

class UsermodVCNL4200Staircase : public Usermod {
  private:
    // Private variables
    Adafruit_VCNL4200 vcnl4200;
    unsigned long lastTime = 0;
    bool enabled = true;
    bool isMotionDetected = false;
    bool allLedsOn = false;   // Indicates if all LEDs are turned on
    bool animationRunning = false;  // Indicates if the animation is running
    unsigned long motionStartTime = 0;
    unsigned long animationStartTime = 0;  // Animation start time
    unsigned long ledDelay = 100;  // Delay between each LED turning on (in ms)
    unsigned long onTime = 60000;  // Time the LEDs remain on (in ms)
    uint16_t proximityThreshold = 100;  // Proximity threshold for motion detection
    uint16_t proximity;
    uint16_t currentLed = 0;  // Current LED being turned on
    float proximityCM;
    uint32_t staircaseColor = RGBW32(255, 255, 255, 0);  // Default pure white color (RGB)  
    

  public:
    void setup() override {
      if (!vcnl4200.begin()) {
        DEBUG_PRINTLN(F("Failed to initialize VCNL4200 sensor!"));
        enabled = false;
      } else {
        DEBUG_PRINTLN(F("VCNL4200 sensor initialized successfully!"));
      }
      vcnl4200.setALSshutdown(true);
      vcnl4200.setProxShutdown(false);
      vcnl4200.setProxHD(false);
      vcnl4200.setProxLEDCurrent(VCNL4200_LED_I_200MA);
      vcnl4200.setProxIntegrationTime(VCNL4200_PS_IT_8T);
    }

    void loop() override {

      proximity = vcnl4200.readProxData();  // Read proximity data

      // Print proximity reading every 500 ms
      if (millis() - lastTime >= 500) {
        proximityCM = 558.62 / pow(proximity + 91.30, 0.61);
        DEBUG_PRINT(F("Raw Proximity: ")); DEBUG_PRINT(proximity);
        DEBUG_PRINT(F(" | Proximity CM: ")); DEBUG_PRINTLN(proximityCM);
        lastTime = millis();
      }

      // Start animation if proximity is detected
      if ((proximityCM < proximityThreshold) && !animationRunning) {
        DEBUG_PRINTLN(F("Motion detected! Starting animation..."));
        animationRunning = true;
        currentLed = 0;  // Reset LED counter
        allLedsOn = false;  // Reset LED state
        animationStartTime = millis();  // Store animation start time
      }

      // Check if animation is running
      if (animationRunning) {
        startStaircaseEffect();
      }  
      // Turn off LEDs after 1 minute
        stopStaircaseEffect();
    }


    void startStaircaseEffect(){
      if (millis() - motionStartTime >= ledDelay) {
        motionStartTime = millis();
        // Turn on the next LED
        strip.setPixelColor(currentLed, staircaseColor);  // Use configurable color
        strip.show();
        currentLed++;

        // Check if all LEDs are on
        if (currentLed >= strip.getLengthTotal()) {
          allLedsOn = true;
          animationStartTime = millis();  // Store animation start time
        }
      }
    }

    void stopStaircaseEffect(){
      if (allLedsOn){  //  } && (millis() - animationStartTime >= onTime)) {  //test
        DEBUG_PRINTLN(F("Animation complete. Turning off LEDs..."));
        for (int i = 0; i < strip.getLengthTotal(); i++) {
          strip.setPixelColor(i, 0);  // Turn off all LEDs
        }
        strip.show();
        animationRunning = false;  // Stop animation
        allLedsOn = false;
        currentLed = 0;
      }
    }

    // Method to save configuration
    void addToConfig(JsonObject& root) override {
      JsonObject top = root.createNestedObject("Staircase Light");  // Create an object for the usermod
      top["enabled"] = enabled;  // Save enabled/disabled state
      top["ledDelay"] = ledDelay;  // Save LED delay time
      top["onTime"] = onTime;  // Save LED on-time
      top["proximityThreshold"] = proximityThreshold;  // Save proximity threshold
      top["color"] = staircaseColor;  // Save color
    }

    // Method to load configuration
    bool readFromConfig(JsonObject& root) override {
      JsonObject top = root["Staircase Light"];  // Access the usermod object
      if (!top.isNull()) {  // If the object exists
        enabled = top["enabled"] | enabled;  // Load enabled/disabled state
        ledDelay = top["ledDelay"] | ledDelay;  // Load LED delay time
        onTime = top["onTime"] | onTime;  // Load LED on-time
        proximityThreshold = top["proximityThreshold"] | proximityThreshold;  // Load proximity threshold
        staircaseColor = top["color"] | staircaseColor;  // Load color
        return true;  // Indicate successful configuration load
      }
      return false;  // Indicate that no configuration was found
    }

    uint16_t getId() override {
      return USERMOD_ID_VCNL4200_STAIRCASE;
    }
};

I added this line to const.h

//Usermod IDs
#define USERMOD_ID_VCNL4200_STAIRCASE  1234     //Usermod "usermod_VCNL4200.h"

I added these lines to usermods_list.h

#include "../usermods/usermod_vcnl4200_staircase/usermod_vcnl4200_staircase.h"

void registerUsermods()
{
  UsermodManager::add(new UsermodVCNL4200Staircase());
...

If you need any details or clarification, I’d be glad to provide them.

I would really appreciate any help, as I’ve been struggling with this for days without a satisfactory solution.

if that is all you want to do, why not use Arduino?

Because I want to have the option to disable the “automatic control with sensors” and also use WLED effects or the Sound Reactive feature.

Additionally, WLED already has integration with Home Assistant.

If you have experience with this, I would really appreciate your help!

in that case, use the senosr as a trigger to the rest of wled, like a button does instead of running your own code in parallel (which is doable, but not without modifying a lot of code) look in other UMs for examples. then use presets.