esphome / feature-requests

ESPHome Feature Request Tracker
https://esphome.io/
417 stars 26 forks source link

FastLED Fire Effect #265

Closed adas4190 closed 2 years ago

adas4190 commented 5 years ago

Describe the problem you have/What new integration you would like

Hi. It would be awesome if You could add FastLED fire effect please.

Thank You. Please describe your use case for this integration and alternatives you've tried:

Additional context

poldim commented 5 years ago

I'd add maybe a bit more explanation on how to write your own lambda functions or import other ones we can find in standard Arduino code online.

OttoWinter commented 5 years ago

import other ones we can find in standard Arduino code online.

that is not possible - ESPHome uses its own API for addressable lights. It's similar so it should be easy to convert; but it won't be copy-paste.

Anyway, contributions to docs about that are always welcome.

Please explain this "FastLED fire effect" @adas4190 - what does it look like?

adas4190 commented 5 years ago

I think (I'm not a programer) it starts from led 1 and shoot up like a regular flame from red to yellow and that flickering has random lenght - that is the best I can do. Thats a video: https://youtu.be/SWMu-a9pbyk

Joshfindit commented 5 years ago

Here's an example where a single row of addressable LEDs is mounted vertically: https://github.com/FastLED/FastLED/blob/master/examples/Fire2012WithPalette/Fire2012WithPalette.ino ( https://www.youtube.com/watch?v=_oVVCXOFDkw )

Here's a different sample of fire generation (incl c code): http://fabiensanglard.net/doom_fire_psx/

ghost commented 5 years ago

+1 from me. I have a number of fireplaces around our house i'd love to put a load of LEDs in to make them look like real fires. Never seen an effect yet which looks as great as the ones in these videos. I guess either something "built in" to esphome or documented somewhere as a lamda example we could copy would work.

This one looks like the best example from above:

https://m.youtube.com/watch?v=SWMu-a9pbyk&feature=youtu.be

It seems to use a 16x16 matrix.

And his code seems to be here: https://gist.github.com/StefanPetrick

So can this be converted / imported to esphome in some way ?

CountParadox commented 5 years ago

A flame effect would be super handy!

thesoftwarejedi commented 5 years ago

I've taken the fire effect code from Tweaking4all.com here at converted it to lambda for you all. Please note that the number of LEDs is located in this code and needs to be the same as the number of lights set on the strip. This is due to the way I need a second array initialized. If a dev can point me to a const I could use or some other method, that would work too. My length is set to 82 below, that matches what I have for the light (not shown).

Also not that this effect can be reversed by switching the commented out lines toward the end. There's 3 sets, I've documented it a bit.

      - addressable_lambda:
          name: "Fire"
          update_interval: 15ms
          lambda: |-
            int Cooling = 55;
            int Sparking = 110;
            static byte heat[82];
            int cooldown;

            // Step 1.  Cool down every cell a little
            for( int i = 0; i < it.size(); i++) {
              cooldown = random(0, ((Cooling * 10) / it.size()) + 2);

              if(cooldown>heat[i]) {
                heat[i]=0;
              } else {
                heat[i]=heat[i]-cooldown;
              }
            }

            // Step 2.  Heat from each cell drifts 'up' and diffuses a little
            for( int k= it.size() - 1; k >= 2; k--) {
              heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2]) / 3;
            }

            // Step 3.  Randomly ignite new 'sparks' near the bottom
            if( random(255) < Sparking ) {
              int y = random(7);
              heat[y] = heat[y] + random(160,255);
            }

            // Step 4.  Convert heat to LED colors
            for( int Pixel = 0; Pixel < it.size(); Pixel++) {
              // Scale 'heat' down from 0-255 to 0-191
              byte t192 = round((heat[Pixel]/255.0)*191);

              // calculate ramp up from
              byte heatramp = t192 & 0x3F; // 0..63
              heatramp <<= 2; // scale up to 0..252

              // figure out which third of the spectrum we're in:
              //this is where you can reverse the effect by switching the commented out lines in all 3 places.
              if( t192 > 0x80) {                     // hottest
                //it[it.size() - Pixel - 1] = ESPColor(255, 255, heatramp);
                it[Pixel] = ESPColor(255, 255, heatramp);
              } else if( t192 > 0x40 ) {             // middle
                //it[it.size() - Pixel - 1] = ESPColor(255, heatramp, 0);
                it[Pixel] = ESPColor(255, heatramp, 0);
              } else {                               // coolest
                //it[it.size() - Pixel - 1] = ESPColor(heatramp, 0, 0);
                it[Pixel] = ESPColor(heatramp, 0, 0);
              }
            }

Someone could also probably use this example of the converted code side by side with the old code and figure out how to convert other effects themselves.

ghost commented 5 years ago

Nice one - I'll give it a go over the next week

adas4190 commented 5 years ago

It works great. I have slow it down by changing the update_interval to 100. It correct way? Thank You for your hard work !

thesoftwarejedi commented 5 years ago

It works great. I have slow it down by changing the update_interval to 100. It correct way? Thank You for your hard work !

Yes, you could also change the cooling and sparking variables on the first few lines to change the way it works. Happy to have helped.

poldim commented 5 years ago

Great job @thesoftwarejedi !

I've added this to one of my crown molding strips just to test it out and it works as expected.

However, I'm thinking of how I could implement this into my no-longer-working fireplace. I could build a panel with 30 columns of strips, say 20 pixels high. But now comes the issue of splitting that code up for these columns. And to ease wiring, you'd want the data signal to snake around, so every other column would need to be inverted.

Any thoughts?

thesoftwarejedi commented 5 years ago

@poldim There are a couple approaches to that. If you want to generate the fire like this code does, you'd need to create a 30x20 array for the LEDs and a 30x20 array for the heat. Then apply similar logic, then at the end copy the array from your 2-dimensional array into the single array in the order you need. It's not a simple programming task, but not terribly difficult either.

There would be more memory and performance efficient ways to do things and that would lend itself to a more advanced implementation.

Bottom line, if you aren't already an ametuer programmer this would be far too advanced. Start smaller. :)

EDIT: Check out this example which has a MatrixLEDDisplay class. Looks promising.

ghost commented 5 years ago

Just tested this out - its great! thanks !!

XavitoHA19 commented 4 years ago

I've taken the fire effect code from Tweaking4all.com here at converted it to lambda for you all. Please note that the number of LEDs is located in this code and needs to be the same as the number of lights set on the strip. This is due to the way I need a second array initialized. If a dev can point me to a const I could use or some other method, that would work too. My length is set to 82 below, that matches what I have for the light (not shown).

Also not that this effect can be reversed by switching the commented out lines toward the end. There's 3 sets, I've documented it a bit.

      - addressable_lambda:
          name: "Fire"
          update_interval: 15ms
          lambda: |-
            int Cooling = 55;
            int Sparking = 110;
            static byte heat[82];
            int cooldown;

            // Step 1.  Cool down every cell a little
            for( int i = 0; i < it.size(); i++) {
              cooldown = random(0, ((Cooling * 10) / it.size()) + 2);

              if(cooldown>heat[i]) {
                heat[i]=0;
              } else {
                heat[i]=heat[i]-cooldown;
              }
            }

            // Step 2.  Heat from each cell drifts 'up' and diffuses a little
            for( int k= it.size() - 1; k >= 2; k--) {
              heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2]) / 3;
            }

            // Step 3.  Randomly ignite new 'sparks' near the bottom
            if( random(255) < Sparking ) {
              int y = random(7);
              heat[y] = heat[y] + random(160,255);
            }

            // Step 4.  Convert heat to LED colors
            for( int Pixel = 0; Pixel < it.size(); Pixel++) {
              // Scale 'heat' down from 0-255 to 0-191
              byte t192 = round((heat[Pixel]/255.0)*191);

              // calculate ramp up from
              byte heatramp = t192 & 0x3F; // 0..63
              heatramp <<= 2; // scale up to 0..252

              // figure out which third of the spectrum we're in:
              //this is where you can reverse the effect by switching the commented out lines in all 3 places.
              if( t192 > 0x80) {                     // hottest
                //it[it.size() - Pixel - 1] = ESPColor(255, 255, heatramp);
                it[Pixel] = ESPColor(255, 255, heatramp);
              } else if( t192 > 0x40 ) {             // middle
                //it[it.size() - Pixel - 1] = ESPColor(255, heatramp, 0);
                it[Pixel] = ESPColor(255, heatramp, 0);
              } else {                               // coolest
                //it[it.size() - Pixel - 1] = ESPColor(heatramp, 0, 0);
                it[Pixel] = ESPColor(heatramp, 0, 0);
              }
            }

Someone could also probably use this example of the converted code side by side with the old code and figure out how to convert other effects themselves.

Thank you for posting this code. What about a police siren lights effect? :)

Luc3as commented 4 years ago

Hello all, I would like to contribute to this thread, I spent few hours converting code I found for arduino to esphome. I had problem with code above because I think it is meant to be used with panel of leds, But I needed neopixel ring. so here are codes 🔥

and here is brief video of effect https://youtu.be/zXl-MS1SBCU

      - addressable_lambda:
          name: "Fire Ring"
          update_interval: 5ms
          lambda: |-
            for( int Pixel = 0; Pixel <= it.size(); Pixel++) {
              it[Pixel] = ESPColor(155, 83, 27);
              ESPColor color1;
              uint8_t r1,g1,b1;
              uint8_t r2,g2,b2;
              uint8_t r3,g3,b3;
              int16_t r,g,b;

              color1 = it[Pixel].get();
              //int color2[] = {80,35,0};
              int color2[] = {179, 121, 7};
              //ESP_LOGD("main", "Color1 :(%d,%d,%d)", color1[0],color1[1],color1[2]);

              r1 = (color1[0] ),
              g1 = (color1[1] ),
              b1 = (color1[2] );

              r2 = (color2[0] ),
              g2 = (color2[1] ),
              b2 = (color2[2] );

              // Add Color
              it[Pixel] = ESPColor(constrain(r1+r2, 0, 255), constrain(g1+g2, 0, 255), constrain(b1+b2, 0, 255));
              int rc = random(120);

              // subtract

              color2[0] = rc;
              color2[1] = rc/2;
              color2[2] = rc/2;

              r1 = (color1[0] ),
              g1 = (color1[1] ),
              b1 = (color1[2] );

              r2 = (color2[0] ),
              g2 = (color2[1] ),
              b2 = (color2[2] );

              r=(int16_t)r1-(int16_t)r2;
              g=(int16_t)g1-(int16_t)g2;
              b=(int16_t)b1-(int16_t)b2;
              if(r<0) r=0;
              if(g<0) g=0;
              if(b<0) b=0;

              //ESP_LOGD("main", "Color1 :(%d,%d,%d)", r,g,b);
              // Substract Color
              it[Pixel] = ESPColor(r, g, b);
            }

            delay(random(10,120));

      - addressable_lambda:
          name: "Blue fire"
          update_interval: 5ms
          lambda: |-
            for( int Pixel = 0; Pixel <= it.size(); Pixel++) {
              it[Pixel] = ESPColor(22, 24, 130);
              ESPColor color1;
              uint8_t r1,g1,b1;
              uint8_t r2,g2,b2;
              uint8_t r3,g3,b3;
              int16_t r,g,b;

              color1 = it[Pixel].get();
              //int color2[] = {80,35,0};
              int color2[] = {70, 185, 242};
              //ESP_LOGD("main", "Color1 :(%d,%d,%d)", color1[0],color1[1],color1[2]);

              r1 = (color1[0] ),
              g1 = (color1[1] ),
              b1 = (color1[2] );

              r2 = (color2[0] ),
              g2 = (color2[1] ),
              b2 = (color2[2] );

              // Add Color
              it[Pixel] = ESPColor(constrain(r1+r2, 0, 255), constrain(g1+g2, 0, 255), constrain(b1+b2, 0, 255));
              int rc = random(160);

              // subtract

              color2[0] = rc/4;
              color2[1] = rc/4;
              color2[2] = rc;

              r1 = (color1[0] ),
              g1 = (color1[1] ),
              b1 = (color1[2] );

              r2 = (color2[0] ),
              g2 = (color2[1] ),
              b2 = (color2[2] );

              r=(int16_t)r1-(int16_t)r2;
              g=(int16_t)g1-(int16_t)g2;
              b=(int16_t)b1-(int16_t)b2;
              if(r<0) r=0;
              if(g<0) g=0;
              if(b<0) b=0;

              //ESP_LOGD("main", "Color1 :(%d,%d,%d)", r,g,b);
              // Substract Color
              it[Pixel] = ESPColor(r, g, b);
            }

            delay(random(10,120));
soundstorm commented 3 years ago

I've created a repository for effects, so nobody has to search various issues. Feel free to submit your effects via PR. @thesoftwarejedi I'll check your flame effect and add it 👍 @XavitoHA19 Do you mean an effect like "Police All" in WLED? Just added that effect.

https://github.com/soundstorm/esphome_led_effects

soundstorm commented 3 years ago

I've taken the fire effect code from Tweaking4all.com here at converted it to lambda for you all. Please note that the number of LEDs is located in this code and needs to be the same as the number of lights set on the strip. This is due to the way I need a second array initialized. If a dev can point me to a const I could use or some other method, that would work too. My length is set to 82 below, that matches what I have for the light (not shown).

Just added to the library, instead of a byte array I used a pointer and calloc so you can adjust the strip length in the light component and the effect will match the size.

cromulus commented 3 years ago

Would love a version for a LED matrix!

Here's a fastled matrix flame effect for Arduino: https://gist.github.com/StefanPetrick/819e873492f344ebebac5bcd2fdd8aa8

tomdb-BE commented 2 years ago

Hi all,

I have converted a really great looking fastled based fireplace effect to lambda: https://www.youtube.com/watch?v=KcE_DxXfV1g

I'm using it with a home made 21*15 addressable led matrix made of a WS2811 addressable led strip controlled by an ESP32 WROOM. The matrix is installed inside an old cole stove in the living room and I'm very satisfied with the results.

My C++ knowledge is very rusty (basing myself on what I recall of my C++ courses in school 20 years ago), but I have attempted to rewrite it without the need of additional helpers and using a single function.

Configurable through te relevant consts:

It goes without saying all credits go to the original author: https://github.com/toggledbits/MatrixFireFast

light:
  - platform: fastled_clockless
    chipset: WS2811
    pin: GPIO13
    num_leds: 315
    rgb_order: GRB
    id: ${ha_id}
    name: ${ha_name}
    effects:
      - addressable_lambda:
          name: Fire
          update_interval: 70ms
          lambda: |-
            const bool colmajor = false;
            const bool mattop = true;
            const bool matleft = true;
            const bool zigzag = true;
            const uint16_t rows = 15;
            const uint16_t cols = 21;
            const uint16_t offsetx = 0;
            const uint16_t offsety = 0;
            const uint8_t maxflare = 3;
            const uint8_t flarerows = 7;
            const uint8_t flarechance = 30;
            const uint8_t flaredecay = 14;
            const uint32_t colors[] = {0x000000,0x100000,0x300000,0x600000,0x800000,0xA00000,0xC02000,0xC04000,0xC06000,0xC08000,0x807080};
            const uint8_t NCOLORS = (sizeof(colors)/sizeof(colors[0]));

            static uint8_t nflare = 0;
            static uint32_t flare[maxflare];
            static uint8_t pix[rows][cols];
            static bool needsinit = true;
            static long t = 0;

            uint16_t b, d, i, j, k, l, n, x, y, z;
            uint16_t phy_w = cols;
            uint16_t phy_h = rows;
            uint16_t phy_x = 0;
            uint16_t phy_y = 0;

            if ( needsinit == true ) {
              needsinit = false;
              for ( i=0; i<rows; ++i ) {
                for ( j=0; j<cols; ++j ) {
                  if ( i == 0 ) pix[i][j] = NCOLORS - 1;
                  else pix[i][j] = 0;
                }
              }
            }

            // First, move all existing heat points up the display and fade
            for ( i=rows-1; i>0; --i ) {
              for ( j=0; j<cols; ++j ) {
                uint8_t n = 0;
                if ( pix[i-1][j] > 0 )
                  n = pix[i-1][j] - 1;
                pix[i][j] = n;
              }
            }

            // Heat the bottom row
            for ( j=0; j<cols; ++j ) {
              i = pix[0][j];
              if ( i > 0 ) {
                pix[0][j] = random(NCOLORS-6, NCOLORS-2);
              }
            }

            // Update existing flares
            for ( i=0; i<nflare; ++i ) {
              x = flare[i] & 0xff;
              y = (flare[i] >> 8) & 0xff;
              z = (flare[i] >> 16) & 0xff;
              b = z * 10 / flaredecay + 1;
              for ( k=(y-b); k<(y+b); ++k ) {
                for ( int l=(x-b); l<(x+b); ++l ) {
                  if ( k >=0 && l >= 0 && k < rows && l < cols ) {
                    d = ( flaredecay * sqrt16((x-l)*(x-l) + (y-k)*(y-k)) + 5 ) / 10;
                    n = 0;
                    if ( z > d ) n = z - d;
                    if ( n > pix[k][l] ) { // can only get brighter
                      pix[k][l] = n;
                    }
                  }
                }
              }
              if ( z > 1 ) {
                flare[i] = (flare[i] & 0xffff) | ((z-1)<<16);
              } else {
                // This flare is out
                for ( j=i+1; j<nflare; ++j ) {
                  flare[j-1] = flare[j];
                }
                --nflare;
              }
            }
            // New Flare
            if ( nflare < maxflare && random(1,101) <= flarechance ) {
              x = random(0, cols);
              y = random(0, flarerows);
              z = NCOLORS - 1;
              b = z * 10 / flaredecay + 1;
              flare[nflare++] = (z<<16) | (y<<8) | (x&0xff);
              for ( k=(y-b); k<(y+b); ++k ) {
                for ( int l=(x-b); l<(x+b); ++l ) {
                  if ( k >=0 && l >= 0 && k < rows && l < cols ) {
                    d = ( flaredecay * sqrt16((x-l)*(x-l) + (y-k)*(y-k)) + 5 ) / 10;
                    n = 0;
                    if ( z > d ) n = z - d;
                    if ( n > pix[k][l] ) { // can only get brighter
                      pix[k][l] = n;
                    }
                  }
                }
              }
            }
            // Draw
            if ( colmajor == true ) {
              phy_w = rows;
              phy_h = cols;
            }
            for ( uint16_t row=0; row<rows; ++row ) {
              for ( uint16_t col=0; col<cols; ++col ) {
                if ( colmajor == true ) {
                    phy_x = offsetx + (uint16_t) row;
                    phy_y = offsety + (uint16_t) col;
                } else {
                    phy_x = offsetx + (uint16_t) col;
                    phy_y = offsety + (uint16_t) row;
                }
                if ( matleft == true && zigzag == true ) {
                  if ( ( phy_y & 1 ) == 1 ) {
                    phy_x = phy_w - phy_x - 1;
                  }
                } else if ( matleft == false && zigzag == true ) {
                  if ( ( phy_y & 1 ) == 0 ) {
                    phy_x = phy_w - phy_x - 1;
                  }
                } else if ( matleft == false ) {
                  phy_x = phy_w - phy_x - 1;
                }
                if ( mattop == true && colmajor == true ) {
                  phy_x = phy_w - phy_x - 1;
                } else if (mattop) {
                  phy_y = phy_h - phy_y - 1;
                }
                it[phy_x + phy_y * phy_w] = ESPColor(colors[pix[row][col]]);
              }
            }