Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions usermods/Temperature/Temperature.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include "UsermodTemperature.h"

static uint16_t mode_temperature();
static void mode_temperature();

//Dallas sensor quick (& dirty) reading. Credit to - Author: Peter Scargill, August 17th, 2013
float UsermodTemperature::readDallas() {
Expand Down Expand Up @@ -369,13 +369,12 @@ const char UsermodTemperature::_temperature[] PROGMEM = "temperature";
const char UsermodTemperature::_Temperature[] PROGMEM = "/temperature";
const char UsermodTemperature::_data_fx[] PROGMEM = "Temperature@Min,Max;;!;01;pal=54,sx=255,ix=0";

static uint16_t mode_temperature() {
static void mode_temperature() {
float low = roundf(mapf((float)SEGMENT.speed, 0.f, 255.f, -150.f, 150.f)); // default: 15°C, range: -15°C to 15°C
float high = roundf(mapf((float)SEGMENT.intensity, 0.f, 255.f, 300.f, 600.f)); // default: 30°C, range 30°C to 60°C
float temp = constrain(UsermodTemperature::getInstance()->getTemperatureC()*10.f, low, high); // get a little better resolution (*10)
unsigned i = map(roundf(temp), (unsigned)low, (unsigned)high, 0, 248);
SEGMENT.fill(SEGMENT.color_from_palette(i, false, false, 255));
return FRAMETIME;
}


Expand Down
6 changes: 2 additions & 4 deletions usermods/TetrisAI_v2/TetrisAI_v2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,13 @@ void drawGrid(TetrisAIGame* tetris, TetrisAI_data* tetrisai_data)
////////////////////////////
// 2D Tetris AI //
////////////////////////////
uint16_t mode_2DTetrisAI()
void mode_2DTetrisAI()
{
if (!strip.isMatrix || !SEGENV.allocateData(sizeof(tetrisai_data)))
{
// not a 2D set-up
SEGMENT.fill(SEGCOLOR(0));
return 350;
return;
}
TetrisAI_data* tetrisai_data = reinterpret_cast<TetrisAI_data*>(SEGENV.data);

Expand Down Expand Up @@ -222,8 +222,6 @@ uint16_t mode_2DTetrisAI()
{
tetrisai_data->tetris.poll();
}

return FRAMETIME;
} // mode_2DTetrisAI()
static const char _data_FX_MODE_2DTETRISAI[] PROGMEM = "Tetris AI@!,Look ahead,Intelligence,Rotate color,Mistake free,Show next,Border,Mistakes;Game Over,!,Border;!;2;sx=127,ix=64,c1=255,c2=0,c3=31,o1=1,o2=1,o3=0,pal=11";

Expand Down
33 changes: 17 additions & 16 deletions usermods/pixels_dice_tray/led_effects.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
#include "dice_state.h"

// Reuse FX display functions.
extern uint16_t mode_breath();
extern uint16_t mode_blends();
extern uint16_t mode_glitter();
extern uint16_t mode_gravcenter();
extern void mode_breath();
extern void mode_blends();
extern void mode_glitter();
extern void mode_gravcenter();

static constexpr uint8_t USER_ANY_DIE = 0xFF;
/**
Expand Down Expand Up @@ -41,7 +41,7 @@ static pixels::RollEvent GetLastRollForSegment() {
*/
// paletteBlend: 0 - wrap when moving, 1 - always wrap, 2 - never wrap, 3 - none (undefined)
#define PALETTE_SOLID_WRAP (strip.paletteBlend == 1 || strip.paletteBlend == 3)
static uint16_t running_copy(uint32_t color1, uint32_t color2, bool theatre = false) {
static void running_copy(uint32_t color1, uint32_t color2, bool theatre = false) {
int width = (theatre ? 3 : 1) + (SEGMENT.intensity >> 4); // window
uint32_t cycleTime = 50 + (255 - SEGMENT.speed);
uint32_t it = strip.now / cycleTime;
Expand All @@ -63,10 +63,9 @@ static uint16_t running_copy(uint32_t color1, uint32_t color2, bool theatre = fa
SEGENV.aux0 = (SEGENV.aux0 +1) % (theatre ? width : (width<<1));
SEGENV.step = it;
}
return FRAMETIME;
}

static uint16_t simple_roll() {
static void simple_roll() {
auto roll = GetLastRollForSegment();
if (roll.state != pixels::RollState::ON_FACE) {
SEGMENT.fill(0);
Expand All @@ -79,7 +78,6 @@ static uint16_t simple_roll() {
SEGMENT.setPixelColor(i, SEGCOLOR(1));
}
}
return FRAMETIME;
}
// See https://kno.wled.ge/interfaces/json-api/#effect-metadata
// Name - DieSimple
Expand All @@ -92,31 +90,34 @@ static uint16_t simple_roll() {
static const char _data_FX_MODE_SIMPLE_DIE[] PROGMEM =
"DieSimple@,,Selected Die;!,!;;1;c1=255";

static uint16_t pulse_roll() {
static void pulse_roll() {
auto roll = GetLastRollForSegment();
if (roll.state != pixels::RollState::ON_FACE) {
return mode_breath();
mode_breath();
return;
} else {
uint16_t ret = mode_blends();
mode_blends();
uint16_t num_segments = float(roll.current_face + 1) / 20.0 * SEGLEN;
for (int i = num_segments; i < SEGLEN; i++) {
SEGMENT.setPixelColor(i, SEGCOLOR(1));
}
return ret;
}
}
static const char _data_FX_MODE_PULSE_DIE[] PROGMEM =
"DiePulse@!,!,Selected Die;!,!;!;1;sx=24,pal=50,c1=255";

static uint16_t check_roll() {
static void check_roll() {
auto roll = GetLastRollForSegment();
if (roll.state != pixels::RollState::ON_FACE) {
return running_copy(SEGCOLOR(0), SEGCOLOR(2));
running_copy(SEGCOLOR(0), SEGCOLOR(2));
return;
} else {
if (roll.current_face + 1 >= SEGMENT.custom2) {
return mode_glitter();
mode_glitter();
return;
} else {
return mode_gravcenter();
mode_gravcenter();
return;
}
}
}
Expand Down
14 changes: 7 additions & 7 deletions usermods/pov_display/pov_display.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,37 @@ static const char _data_FX_MODE_POV_IMAGE[] PROGMEM = "POV Image@!;;;;";

static POV s_pov;

uint16_t mode_pov_image(void) {
void mode_pov_image(void) {
Segment& mainseg = strip.getMainSegment();
const char* segName = mainseg.name;
if (!segName) {
return FRAMETIME;
return;
}
// Only proceed for files ending with .bmp (case-insensitive)
size_t segLen = strlen(segName);
if (segLen < 4) return FRAMETIME;
if (segLen < 4) return;
const char* ext = segName + (segLen - 4);
// compare case-insensitive to ".bmp"
if (!((ext[0]=='.') &&
(ext[1]=='b' || ext[1]=='B') &&
(ext[2]=='m' || ext[2]=='M') &&
(ext[3]=='p' || ext[3]=='P'))) {
return FRAMETIME;
return;
}

const char* current = s_pov.getFilename();
if (current && strcmp(segName, current) == 0) {
s_pov.showNextLine();
return FRAMETIME;
return;
}

static unsigned long s_lastLoadAttemptMs = 0;
unsigned long nowMs = millis();
// Retry at most twice per second if the image is not yet loaded.
if (nowMs - s_lastLoadAttemptMs < 500) return FRAMETIME;
if (nowMs - s_lastLoadAttemptMs < 500) return;
s_lastLoadAttemptMs = nowMs;
s_pov.loadImage(segName);
return FRAMETIME;
return;
}

class PovDisplayUsermod : public Usermod {
Expand Down
49 changes: 19 additions & 30 deletions usermods/user_fx/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,14 @@ The first line of the code imports the [wled.h](https://github.com/wled/WLED/blo
### Static Effect Definition
The next code block is the `mode_static` definition. This is usually left as `SEGMENT.fill(SEGCOLOR(0));` to leave all pixels off if the effect fails to load, but in theory one could use this as a 'fallback effect' to take on a different behavior, such as displaying some other color instead of leaving the pixels off.

`FX_FALLBACK_STATIC` is a macro that calls `mode_static()` and then returns.

### User Effect Definitions
Pre-loaded in this template is an example 2D Effect called "Diffusion Fire". (This is the name that would be shown in the UI once the binary is compiled and run on your device, as defined in the metadata string.)
The effect starts off by checking to see if the segment that the effect is being applied to is a 2D Matrix, and if it is not, then it returns the static effect which displays no pattern:
The effect starts off by checking to see if the segment that the effect is being applied to is a 2D Matrix, and if it is not, then it runs the static effect which displays no pattern:
```cpp
if (!strip.isMatrix || !SEGMENT.is2D())
return mode_static(); // not a 2D set-up
FX_FALLBACK_STATIC; // not a 2D set-up
```
The next code block contains several constant variable definitions which essentially serve to extract the dimensions of the user's 2D matrix and allow WLED to interpret the matrix as a 1D coordinate system (WLED must do this for all 2D animations):
```cpp
Expand Down Expand Up @@ -128,7 +130,7 @@ unsigned dataSize = cols * rows; // SEGLEN (virtual length) is equivalent to vW

```cpp
if (!SEGENV.allocateData(dataSize))
return mode_static(); // allocation failed
FX_FALLBACK_STATIC; // allocation failed
```
* Upon the first call, this section allocates a persistent data buffer tied to the segment environment (`SEGENV.data`). All subsequent calls simply ensure that the data is still valid.
* The syntax `SEGENV.allocateData(n)` requests a buffer of size n bytes (1 byte per pixel here).
Expand Down Expand Up @@ -250,20 +252,14 @@ After calculating tmp_row, we now handle rendering the pixels by updating the ac
* `SEGCOLOR(0)` gets the first user-selected color for the segment.
* The final line of code fades that base color according to the heat value (acts as brightness multiplier).

The final piece of this custom effect returns the frame time:
* Even though the effect logic itself controls when to update based on refresh_ms, WLED will still call this function at roughly FRAMETIME intervals (the FPS limit set in config) to check whether an update is needed. If nothing needs to change, the frame still needs to be re-rendered so color or brightness transitions will be smooth.

If you want to run your effect at a fixed frame rate you can use the following code to not update your effect state, be aware however that transitions for your effect will also run at this frame rate - for example if you limit your effect to say 5 FPS, brightness changes and color changes may not look smooth. Also `SEGMENT.call` is still incremented on each function call.
```cpp
}
return FRAMETIME;
}
//limit update rate
if (strip.now - SEGENV.step < FRAMETIME_FIXED) return;
SEGENV.step = strip.now;
```
* The first bracket closes the earlier `if ((strip.now - SEGENV.step) >= refresh_ms)` block.
* It ensures that the fire simulation (scrolling, sparking, diffusion, rendering) only runs when enough time has passed since the last update.
* returning the frame time tells WLED how soon this effect wants to be called again.
* `FRAMETIME` is a predefined macro in WLED, typically set to ~16ms, corresponding to ~60 FPS (frames per second).
* Even though the effect logic itself controls when to update based on refresh_ms, WLED will still call this function at roughly FRAMETIME intervals to check whether an update is needed.
* ⚠️ Important: Because the actual frame logic is gated by strip.now - SEGENV.step, returning FRAMETIME here doesn’t cause excessive updates — it just keeps the engine responsive. **Also note that an Effect should ALWAYS return FRAMETIME. Not doing so can cause glitches.**
* The final bracket closes the `mode_diffusionfire()` function itself.


### The Metadata String
At the end of every effect is an important line of code called the **metadata string**.
Expand Down Expand Up @@ -310,13 +306,13 @@ We will break this effect down step by step.
(This effect was originally one of the FastLED example effects; more information on FastLED can be found [here](https://fastled.io/).)

```cpp
static uint16_t sinelon_base(bool dual, bool rainbow=false) {
static void sinelon_base(bool dual, bool rainbow=false) {
```
* The first line of code defines `sinelon base` as static helper function. This is how all effects are initially defined.
* Notice that it has some optional flags; these parameters will allow us to easily define the effect in different ways in the UI.

```cpp
if (SEGLEN <= 1) return mode_static();
if (SEGLEN <= 1) FX_FALLBACK_STATIC;
```
* If segment length ≤ 1, there’s nothing to animate. Just show static mode.

Expand Down Expand Up @@ -396,28 +392,21 @@ This final part of the effect function will fill in the 'trailing' pixels to com
* Works in both directions: Forward (if new pos > old pos), and Backward (if new pos < old pos).
* Updates `SEGENV.aux0` to current position at the end.

Finally, we return the `FRAMETIME`, as with all effect functions:
```cpp
return FRAMETIME;
}
```
* Returns `FRAMETIME` constant to set effect update rate (usually ~16 ms).

The last part of this effect has the Wrapper functions for different Sinelon modes.
Notice that there are three different modes that we can define from the single effect definition by leveraging the arguments in the function:
```cpp
uint16_t mode_sinelon(void) {
return sinelon_base(false);
void mode_sinelon(void) {
sinelon_base(false);
}
// Calls sinelon_base with dual = false and rainbow = false

uint16_t mode_sinelon_dual(void) {
return sinelon_base(true);
void mode_sinelon_dual(void) {
sinelon_base(true);
}
// Calls sinelon_base with dual = true and rainbow = false

uint16_t mode_sinelon_rainbow(void) {
return sinelon_base(false, true);
void mode_sinelon_rainbow(void) {
sinelon_base(false, true);
}
// Calls sinelon_base with dual = false and rainbow = true
```
Expand Down
12 changes: 6 additions & 6 deletions usermods/user_fx/user_fx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@
// for information how FX metadata strings work see https://kno.wled.ge/interfaces/json-api/#effect-metadata

// static effect, used if an effect fails to initialize
static uint16_t mode_static(void) {
static void mode_static(void) {
SEGMENT.fill(SEGCOLOR(0));
return strip.isOffRefreshRequired() ? FRAMETIME : 350;
}

#define FX_FALLBACK_STATIC { mode_static(); return; }

/////////////////////////
// User FX functions //
/////////////////////////

// Diffusion Fire: fire effect intended for 2D setups smaller than 16x16
static uint16_t mode_diffusionfire(void) {
static void mode_diffusionfire(void) {
if (!strip.isMatrix || !SEGMENT.is2D())
return mode_static(); // not a 2D set-up
FX_FALLBACK_STATIC; // not a 2D set-up

const int cols = SEG_W;
const int rows = SEG_H;
Expand All @@ -29,7 +30,7 @@ static uint16_t mode_diffusionfire(void) {

unsigned dataSize = cols * rows; // SEGLEN (virtual length) is equivalent to vWidth()*vHeight() for 2D
if (!SEGENV.allocateData(dataSize))
return mode_static(); // allocation failed
FX_FALLBACK_STATIC; // allocation failed

if (SEGENV.call == 0) {
SEGMENT.fill(BLACK);
Expand Down Expand Up @@ -84,7 +85,6 @@ unsigned dataSize = cols * rows; // SEGLEN (virtual length) is equivalent to vW
}
}
}
return FRAMETIME;
}
static const char _data_FX_MODE_DIFFUSIONFIRE[] PROGMEM = "Diffusion Fire@!,Spark rate,Diffusion Speed,Turbulence,,Use palette;;Color;;2;pal=35";

Expand Down
Loading