Creating New Effects (Primary Only)


[Partitions image]

With the understanding from the previous topics Effect Rendering and Speed Calculations, you have the necessary information to create your own effects. This topic shows the steps required to add an effect to the controller’s code.

Yes… currently this does mean that you are going to need to modify the source code and compile your own firmware. Future updates may allow effects to be defined and stored in a separate configuration file so that code modifications aren’t necessary. But for now, you have to modify the source and compile your own version.

💻 Arduino IDE
The topic assumes you have an understanding on configuring and using the Arduino IDE. This includes adding ESP32 board support and adding libraries. You can review some of the Arduino requirements for editing the source code in the topic Modifying the Firmware

Effects are defined and executed from the PRIMARY controller. This means that you will need local copies of the primary controller’s source code:

  • standalone_controller.ino - This is the file to be edited
  • html.h - This file won’t be modified, but is required to compile the firmware

These files can be obtained from the Standalone Stair Controller repository.

Overview of Steps

To add a new effect, we need to complete the following steps in the source code:

  1. Expand the Effects Array
  2. Add Effect Name to defineEffects()
  3. Add your new effect function
  4. Update case statements in toggleLights() and getEffectName()

Global Variables and Effect Function Parameters

Your effect will likely need to know a little bit about the current system, such as the total number of LEDs, to implment your effect. Some of these variables are defined globally, others can be passed as parameters to your effect function. This section lists the variables and parameters you are most likely to use.

Effect Function Parameters

When the system calls an effect function, it can pass up to three parameters, based on the current values of the sensor settings:

sensor_settings

If Sensor1 was triggered, the colors and speed from Sensor1 are sent. If Sensor2 was triggered, its paramaters are passed instead. If the system is in manual override mode, then the manual colors and speed will be sent. In essence, your effect doesn’t need to care about how the system was triggered… it only needs the colors and speeds to use for your effect.

void effectName(CRGB color1, CRGB color2, byte speed)

  • color1: The Primary Color, passed from the sensor’s active value
  • color2: The Secondary Color, passed from the sensor’s active value
  • speed: The Effect Speed, passed from the sensor’s active value

Your effect function only needs to accept the parameters it needs. For example, if your effect will only use one color, you can simply omit color2 from your function’s parameters:

void effectName (CRGB color1, byte speed)

If fact, if your effect is going to use ‘fixed’ colors, you don’t have to accept colors at all. For example, say you are adding a “Candy Cane” effect that will always use Red and White for its colors. Then you would not need any color parameters and would instead set the colors in your effect function. Similarly, if your effect doesn’t require speed (e.g. “Solid”, “Fade”), then you don’t need the speed parameter. So for our fictitional ‘Candy Cane’ effect, if the LEDs are just supposed to all come on simutaneously (no pattern), but in fixed alternating colors of red and white, the effect function doesn’t need parameters at all:

void effectCandyCane()

There is at least one other globally defined variables you will likely need for your effect:

  • numLEDs: The total number of LEDs, as defined in the hardware setup.

There are a plethora of other global variables that you can use if needed, and all these are defined (and commented) near the top of the code.

🔺 Using Global Variables
While your custom effect function can use any global variable, it should not change the value. The system handles things like the current state of the LEDs, timers and other routine processes. If you change the value of a global variable in your function, unexpected results may occur and in extreme situations, your board may even crash or reboot.

CRGB Color Types

You may have noticed a non-standard varaible type for the color parameters: CRGB. This is a special variable type used by the FastLED library. The FastLED library also has a number of ‘predefined’ colors, that can be directly used for any color parameter:

CRGB::Red
CRGB::Green
CRGB::Blue
… etc.

In fact, there are over 140 predefined CRGB colors available in the FastLED library. To see a full list, visit the FastLED Library Reference Guide.

If you are using the system parameters for color in your function, you don’t need to worry about the color type. The firmware has handled that for you. However, if you need colors defined other formats, such as Hex (#ff0000) or RGB (255,0,0), the firmware already has conversion functions available so you don’t need to do any conversions in your effect function:

Function Accepts Example Returns
CRGB hexToCRGB(hexString)
Converts Hex Color to CRGB Color
Hex Color String #ff0000 CRGB Color (red)
CRGB rgbToCRGB(red, green, blue)
Converts RGB Colors to CRGB Color
Three numerics (0-255) 0, 255, 0 CRGB Color (green)
crgbToHex(CRGB Color)
Converts CRGB Color to Hex Color String
CRGB Color CRGB::Red #ff0000
crgbToRgb(CRGB color, &r, &g, &b)
Converts CRGB Color to RGB R, G, B values
CRGB Color CRGB::Green r=0
g=255
b=0
       

Additional color conversion and handlers are available, such as converting from Hex to RGB. These can be found at the very bottom of the .ino source code.

Adding An Effect

The best way to show the process for adding a new effect is by example. So for this fictional effect, which we’ll call ‘Out-In’, will illuminate the LED strip starting at each end. Color1 will be used for the start of the strip and Color2 will be used starting at the end. The LEDs will light up in sequence, moving towards and meeting in the middle of the strip. This is actually just the ‘reverse’ of the included ‘In-Out’ effect.

Increase the Effect Array

Near the end of the global variables defined at the top of the source code, locate the section for the Effect Array:

// ===============================
//  Effects array 
// ===============================
int numberOfEffects = 10;
String Effects[numberOfEffects]; 

Increase the numberOfEffects by one for each effect added:
int numberOfEffects = 11;

Add Effect Name to the Effect Definitions

Locate the following function:

//=================================
// LIGHT AND EFFECT FUNCTIONS
//=================================
// -----------------
//  Define Effects
// -----------------
//  Increase array size above if adding new
//  Effect name must not exceed 15 characters and must be a String
void defineEffects() {
  Effects[0] = "Solid";
  Effects[1] = "Chase";
  Effects[2] = "Chase-Reverse";
  Effects[3] = "In-Out";
  Effects[4] = "CrissCross";
  Effects[5] = "Segments";
  Effects[6] = "Segments-Reverse";
  Effects[7] = "Segments2";
  Effects[8] = "Segments2-Reverse";
  Effects[9] = "Fade";
  Effects[10] = "Out-In";   //Add your new effect
}

Add your new effect name to the end of this, increasing the array number by one:

Effects[10] = "Out-In";

Note that the effect name is limited to 15 characters!

Create and Add the Effect Function

Now we need to add the function that actually creates the desired pattern and effect.

void effectOutIn(CRGB ledColor1, CRGB ledColor2, byte speed) {
  //Get speed from function calculation
  uint32_t totalTime = getTargetDuration(speed);
  uint32_t delayTime = totalTime / (startPixel + 1);
  if (delayTime < 1) delayTime = 1;

  int midPixel = numLEDs/2;
  int mod = numLEDs % 2;
  int upCounter;
    //adjust for 0 based array
  midPixel = midPixel - 1;
  
  for (int i=0; i < (midPixel); i++) {
    //Loop through LEDs lighting from each end in different colors, meeting in the middle
    LEDs[i] = ledColor1;
    LEDs[(numLEDs - 1) - i] = ledColor2;
    FastLED.show();
    delay(delayTime);
  }    

  if (mod != 0) {
    //If number of LEDs is odd, light the remaining center pixel
    LEDs[midPixel] = ledColor;
  } 
}

This is a basic example, but create whatever effect you like.

Update Case Statements in getEffectName() and ToggleLights()

getEffectName()

The getEffectName() function returns the exact matching effect name from a parameter that may be in lower, upper or mixed case. This is primary used for the API and MQTT parameter processsing.

String getEffectName(String effect) {
  String retVal = "Solid";
  if (effect.equalsIgnoreCase("chase")) {
    retVal = "Chase";
  } else if (effect.equalsIgnoreCase("chase-reverse")) {
    retVal = "Chase-Reverse";
  } else if (effect.equalsIgnoreCase("in-out")) {
    retVal = "In-Out";
  } else if (effect.equalsIgnoreCase("crisscross")) {
    retVal = "CrissCross";
  } else if (effect.equalsIgnoreCase("segments")) {
    retVal = "Segments";
  } else if (effect.equalsIgnoreCase("segments-reverse")) {
    retVal = "Segments-Reverse";
  } else if (effect.equalsIgnoreCase("segments2")) {
    retVal = "Segments2";
  } else if (effect.equalsIgnoreCase("segments2-reverse")) {
    retVal = "Segments2-Reverse";
  } else if (effect.equalsIgnoreCase("fade")) {
    retVal = "Fade";
  } else if (effect.equalsIgnoreCase("out-in")) {   //Add your function in an addtional 'Else If'
    retVal = "Out-In";
  }
  return retVal;
}

MQTT and the HTTP API normally uses all lower case parameters or payloads. To allow your effect name to be properly recognized in the code (which is case sensitive), we use a simply function to convert lower case parameter or payload to the matching effect name.

Place your effect name, in all lower case, within an equalsIgnoreCase("lower-case-name") and then use the exact matching name for the retVal.

toggleLights()
This function actually calls the effects, based on the passed paramenter. To allow your effect to be called from a sensor or manual trigger, it must be added to this function. The top portion of this function (omitted from sample below) determines the proper colors and speed settings depending upon trigger source. You only need to locate the if-else if block and add your new function.

    if (useEffect == "Solid") {
      effectSolid(color1);
    } else if (useEffect == "Chase") {
      effectChase(color1, speed);
    } else if (useEffect == "Chase-Reverse") {
      effectChaseReverse(color1, speed);
    } else if (useEffect == "In-Out") {
      effectInOut(color1, color2, speed);
    } else if (useEffect == "CrissCross") {
      effectCrissCross(color1, color2, speed);
    } else if (useEffect == "Segments") {
      effectSegments(color1, speed);
    } else if (useEffect == "Segments-Reverse") {
      effectSegmentsReverse(color1, speed);
    } else if (useEffect == "Segments2") {
      effectSegments2(color1, color2, speed);
    } else if (useEffect == "Segments2-Reverse") {
      effectSegments2Reverse(color1, color2, speed);
    } else if (useEffect == "Fade") {
      effectFade(color1, speed);
    } else if (useEffect == "Out-In") {   //Add your new effect name and function here
      effectOutIn(color1, color2, speed);    
    } else {
      effectSolid(color1);
    }

Once the above code modifications are complete and you compile/flash the modified firmware to the PRIMARY controller, your new effect will be available for selection from the Effect drop down lists within the web app or via the HTTP API or MQTT.

<- Previous: Speed Calculations Next: Troubleshooting ->

Copyright © 2026 Resinchem Tech. Firmware documentation for personal use. Commercial use, republishing or embedding prohibited.

This site uses Just the Docs, a documentation theme for Jekyll.