kitesurfer1404 / WS2812FX

WS2812 FX Library for Arduino and ESP8266
MIT License
1.6k stars 347 forks source link

How to use external trigger? #127

Closed haganwalker closed 6 years ago

haganwalker commented 6 years ago

Hi there - first off, thanks for the awesome library. The examples work great, but I'm struggling with using a sensor to make the strip come on. My project is something I'd love to share when finished. We're working with our city to light up a dark pedestrian bridge by placing a 216' WS2812B strip inset in the concrete and covered in clear resin. The idea is to use a sensor (probably a Maxbotix waterproof ultrasonic sensor) at either end to detect when someone enters/exits the bridge. Ideally, it would work like this: digital sensor goes HIGH, random FX happens, fades into white, waits for roughly 30 seconds, then fades off. But, I'm just trying to get the bare basics going. I can't get the strip to light up when the sensor goes high by using setMode, like it is in the auto_mode_cycle example. I can get it to act crazy and quickly cycle through many FX by using trigger(), but I'm not sure the exact usage of this. Anyway, here's my code. I was just using setBrightness(0) to turn the strip off. Any help would be greatly appreciated!

#include <WS2812FX.h>

#define LED_COUNT 300
#define LED_PIN 5

// Parameter 1 = number of pixels in strip
// Parameter 2 = Arduino pin number (most are valid)
// Parameter 3 = pixel type flags, add together as needed:
//   NEO_KHZ800  800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
//   NEO_KHZ400  400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
//   NEO_GRB     Pixels are wired for GRB bitstream (most NeoPixel products)
//   NEO_RGB     Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
//   NEO_RGBW    Pixels are wired for RGBW bitstream (NeoPixel RGBW products)
WS2812FX ws2812fx = WS2812FX(LED_COUNT, LED_PIN, NEO_RGB + NEO_KHZ800);

const int PIR_PIN = 13;
int PIR_STATE = 0;

void setup() {
  Serial.begin(115200);
  Serial.println();
  Serial.println();
  Serial.println("Starting...");
  pinMode(PIR_PIN, INPUT);
  ws2812fx.init();
  ws2812fx.setBrightness(255);
  ws2812fx.setMode(FX_MODE_STATIC);
  ws2812fx.setSpeed(1000);
  ws2812fx.start();
}

void loop() {

  ws2812fx.service();

  PIR_STATE = digitalRead(PIR_PIN);

  if(PIR_STATE == HIGH) {
    Serial.print("detected motion");
    ws2812fx.setMode((ws2812fx.getMode() + 1) % ws2812fx.getModeCount());
    ws2812fx.setBrightness(255);
    Serial.print("mode is "); Serial.println(ws2812fx.getModeName(ws2812fx.getMode()));
    delay(5000);
  }

  else {
    ws2812fx.setBrightness(0);
    Serial.println("stopped");
  }
}
tobi01001 commented 6 years ago

Hi,

sounds like a nice project. You may have several (minor) challenges. For your small sketch below to get started. You have an if - else to check for the sensor state and do something when it is high. As long as it is high (e.g. motion detected) it will cycle through and call it over and over again. (including switching to the next mode...) I am sure this is not what you wanted. Another challenge with digital inputs is that they are tending to bounce. So you may need to debounce them a bit.

But one after another. Let us run through your loop() function several times and see what happens:

1. loop, no motion - sensor low.

2. loop, no motion - sensor low.

So as long as you do not detect motion, everything is dark. Now lets consider motion (considering we do not have any bouncing):

3. loop, motion detected.

4. loop, motion detected.

Now with another call to loop:

Now what to do? It would be good to work "none blocking".

Some changes in your sketch to get you started (Don't know if it will work out of the box as I am not at home to check)...


#define LIGHT_DURATION 5000 // 5 seconds, change to 30000 for 30 seconds...
void loop() {
  static uint32_t next_call = 0; // static ensures that they are not deleted when going out of "loop" and 
                                               // still keeps them local. Another option would be a global variable 
                                               // (as you did with PIR_SENSOR)
  static bool motion_detected = false;
  uint32_t now = millis(); // What time is it now?

  ws2812fx.service(); // this is good to be called everytime.

  PIR_STATE = digitalRead(PIR_PIN);

  if(PIR_STATE == HIGH && motion_detected == false) { // we do not want to retrigger (for now)
    motion_detected = true; // we have motion detected
    ws2812fx.setMode(random(ws2812fx.getModeCount()-1)); // set a random mode, 
                                                                                                   // the service call manages the rest
    ws2812fx.setBrightness(255); // just full brightness, no fading yet and not fade to white...
    next_call = now + LIGHT_DURATION;
    Serial.print("detected motion");
    Serial.print("mode is "); Serial.println(ws2812fx.getModeName(ws2812fx.getMode()));
  }
  if(now > next_call && motion_detected)  // we are done, so we can start again
  {
      motion_detected = false; // now the sensor can retrigger a new effect.
      ws2812fx.setBrightness(0); // turns the leds dark. No fading yet
      Serial.println("stopped");
  }
}

If I didn't miss anything quite important (there may be typos and other mistakes), it should get you started and hopefully give an idea on how to proceed.

Hope this helps...

Regards, Toby

haganwalker commented 6 years ago

@tobi01001 wow - that was incredibly informative. Thank you! I must admit, it's been quite a while since I've been in this realm - so my coding is rough. I know what I need to do, but sometimes struggle with how to do so.

With your help, I've figured out everything except how to fade from a random effect when the sensor is tripped into white. I have a feeling that #124 will send me in the right direction, and the sample code at the bottom is helpful, but 1) I need the effect to run at least once before transitioning to white and 2) I'll need to find some way to pass the current mode to the array, like this (except I cant put the getMode function here because its a temp array): ws2812fx.setSegment(0, 0, 3, myFadeMode, (const uint32_t[]) {ws2812fx.getMode(), BLUE}, 1500, NO_OPTIONS); This is somewhat how I think I handle part 2) from above, using the code from @ovi1337, but I haven't been able to get this to work. I'm still not sure about part one.

Any thoughts on this? Thanks again for the help!

ovi1337 commented 6 years ago

@haganwalker, yes, my implementation can give you exactly what you want to have. Later I can post you an example sketch that you can play around with it. My custom effect is running exactly once and it's not necessary to do special things anymore since my last posted version. You only have to change the target color. It's not necessary to adjust the brightness, you simply can fade from black (lights off) to your wanted color and if you want to turn off the lights, can you trigger the method again with your color to black (lights off).

ovi1337 commented 6 years ago

It's not finally tested, because i'm at work and don't have the necessary hardware here. Please also take in mind that you can't use the delay function, because this will halt the whole thread and the animations are unable to work meanwhile. Please take a look in the loop, there's a way how you can delay something without using the delay function. Please also take a look into the Ticker library for delays. Specially the once method is perfect for your use.


#include <Arduino.h>
#include <WS2812FX.h>

#define LED_COUNT 8
#define LED_PIN D3

WS2812FX ws2812fx = WS2812FX(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);

uint16_t customColorFade(void) {
    WS2812FX::Segment *segment = ws2812fx.getSegment();
    WS2812FX::Segment_runtime *runtime = ws2812fx.getSegmentRuntime();

    uint32_t color = ws2812fx.color_blend(segment->colors[0], segment->colors[1], runtime->counter_mode_step);

    if (color == segment->colors[1] && segment->options != 1) {
        runtime->counter_mode_step = 0;

        return (segment->speed / 128);
    }

    for (uint16_t i = segment->start; i <= segment->stop; i++) {
        ws2812fx.setPixelColor(i, color);
    }

    runtime->counter_mode_step += 4;

    if (runtime->counter_mode_step > 255) {
        runtime->counter_mode_step = 255;
        segment->options = 1;
    } 

    return (segment->speed / 128);
}

uint8_t colorFade;

uint32_t color_1_current = BLUE;
uint32_t color_1_new = BLUE;

uint32_t color_2_current = RED;
uint32_t color_2_new = RED;

void fade(uint8_t id, uint32_t color) {
    color_1_current = color_1_new;
    if (id == 0)
    {
        color_1_new = color;

        const uint32_t colors_1[] = {color_1_current, color_1_new};
        ws2812fx.setSegment(0, 0, 3, colorFade, colors_1, 1500, NO_OPTIONS);
    }

    color_2_current = color_2_new;
    if (id == 1)
    {
        color_2_new = color;

        const uint32_t colors_2[] = {color_2_current, color_2_new};
        ws2812fx.setSegment(1, 4, 7, colorFade, colors_2, 1500, NO_OPTIONS);
    }  
}

void setup()
{
    Serial.begin(115200);
    Serial.setDebugOutput(true);

    ws2812fx.init();

    colorFade = ws2812fx.setCustomMode(F("Custom Fade"), customColorFade);

    ws2812fx.setBrightness(255);
    ws2812fx.start();
}

bool status = false;

void loop()
{
    ws2812fx.service();

    long static then = 0;
    if (millis() > then + 5000)
    {
        if(status) {
            fade(0, WHITE);
            fade(1, BLUE);

            /*
                or without the helper method:

                ws2812fx.setSegment(0, 0, 3, colorFade, (const uint32_t[]) {BLACK, WHITE}, 1500, NO_OPTIONS);
                ws2812fx.setSegment(1, 4, 7, colorFade, (const uint32_t[]) {RED, BLUE}, 1500, NO_OPTIONS);
            */
            }
        } else {
            fade(0, BLACK);
            fade(1, RED);

            /*
                or without the helper method:

                ws2812fx.setSegment(0, 0, 3, colorFade, (const uint32_t[]) {WHITE, BLACK}, 1500, NO_OPTIONS);
                ws2812fx.setSegment(1, 4, 7, colorFade, (const uint32_t[]) {BLUE, RED}, 1500, NO_OPTIONS);
            */
        }

        status = !status;

        then = millis();
    }
}
tobi01001 commented 6 years ago

@tobi01001 wow - that was incredibly informative. Thank you! I must admit, it's been quite a while since I've been in this realm - so my coding is rough. I know what I need to do, but sometimes struggle with how to do so.

With your help, I've figured out everything except how to fade from a random effect when the sensor is tripped into white. I have a feeling that #124 will send me in the right direction, and the sample code at the bottom is helpful, but 1) I need the effect to run at least once before transitioning to white and 2) I'll need to find some way to pass the current mode to the array, like this (except I cant put the getMode function here because its a temp array): ws2812fx.setSegment(0, 0, 3, myFadeMode, (const uint32_t[]) {ws2812fx.getMode(), BLUE}, 1500, NO_OPTIONS); This is somewhat how I think I handle part 2) from above, using the code from @ovi1337, but I haven't been able to get this to work. I'm still not sure about part one.

Any thoughts on this? Thanks again for the help!

As you stated and @ovi1337 confirmed, it would give you the right directions. the challenge is (in my opinion) not to have the current mode but to fade to white from whatever is on the strip currently.

So you do not need the current mode but just a fade. And after a certain time you may want to fade to Black again (or fade with brightness - as you prefer).

Some other thoughts:

So I would use a small basic "state" machine with states:

  1. OFF
  2. FADE_IN
  3. ANIMATION
  4. FADE_WHITE
  5. STAY_ON
  6. FADE_OUT

See the example below. It just uses the library for the animation part. The rest (the fading etc) is done in the loop. It's for sure quick and dirty and there is anyway no right way to do such things apart from best practice.... So it's really up to you how to realize your ideas. Glad to help anyway....

you need to adopt this to your use case (PIR / LED_PIN) etc.

#include <Arduino.h>
#include <WS2812FX.h>

#define LED_COUNT 250
#define LED_PIN 3

// Parameter 1 = number of pixels in strip
// Parameter 2 = Arduino pin number (most are valid)
// Parameter 3 = pixel type flags, add together as needed:
//   NEO_KHZ800  800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
//   NEO_KHZ400  400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
//   NEO_GRB     Pixels are wired for GRB bitstream (most NeoPixel products)
//   NEO_RGB     Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
//   NEO_RGBW    Pixels are wired for RGBW bitstream (NeoPixel RGBW products)
WS2812FX ws2812fx = WS2812FX(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);

const int PIR_PIN = 0;
int PIR_STATE = 0;

void setup() {
  Serial.begin(115200);
  Serial.println();
  Serial.println();
  Serial.println("Starting...");
  pinMode(PIR_PIN, INPUT_PULLUP);
  ws2812fx.init();
  ws2812fx.setBrightness(0);
  ws2812fx.setMode(FX_MODE_STATIC);
  ws2812fx.setSpeed(1000);
  ws2812fx.start();
  ws2812fx.setOptions(0, 0x0);
  Serial.println("Setup Done...");
}

// states we can have
typedef enum stages{
    OFF,            // default, everything is off
    FADE_IN,        // we fade into the random effect
    ANIM,           // we keep the animation for a deinfed time
    FADE_WHITE,     // we fade to white from that
    ON,             // we keep the lights on
    FADE_OUT        // and fade out again
};

// might be "bridge friendly" effects
const uint8_t myModes[] = {
//    FX_MODE_STATIC                  ,
//    FX_MODE_BLINK                   ,
    FX_MODE_BREATH                  ,
//    FX_MODE_COLOR_WIPE              ,
//    FX_MODE_COLOR_WIPE_INV          ,
//    FX_MODE_COLOR_WIPE_REV          ,
//    FX_MODE_COLOR_WIPE_REV_INV      ,
//    FX_MODE_COLOR_WIPE_RANDOM       ,
    FX_MODE_RANDOM_COLOR            ,
    FX_MODE_SINGLE_DYNAMIC          ,
    FX_MODE_MULTI_DYNAMIC           ,
    FX_MODE_RAINBOW                 ,
    FX_MODE_RAINBOW_CYCLE           ,
//    FX_MODE_SCAN                    ,
//    FX_MODE_DUAL_SCAN               ,
//    FX_MODE_FADE                    ,
    FX_MODE_THEATER_CHASE           ,
    FX_MODE_THEATER_CHASE_RAINBOW   ,
    FX_MODE_RUNNING_LIGHTS          ,
//    FX_MODE_TWINKLE                 ,
//    FX_MODE_TWINKLE_RANDOM          ,
    FX_MODE_TWINKLE_FADE            ,
    FX_MODE_TWINKLE_FADE_RANDOM     ,
    FX_MODE_SPARKLE                 ,
//    FX_MODE_FLASH_SPARKLE           ,
//    FX_MODE_HYPER_SPARKLE           ,
//    FX_MODE_STROBE                  ,
//    FX_MODE_STROBE_RAINBOW          ,
//    FX_MODE_MULTI_STROBE            ,
//    FX_MODE_BLINK_RAINBOW           ,
//    FX_MODE_CHASE_WHITE             ,
    FX_MODE_CHASE_COLOR             ,
    FX_MODE_CHASE_RANDOM            ,
    FX_MODE_CHASE_RAINBOW           ,
//   FX_MODE_CHASE_FLASH             ,
//    FX_MODE_CHASE_FLASH_RANDOM      ,
//    FX_MODE_CHASE_RAINBOW_WHITE     ,
    FX_MODE_CHASE_BLACKOUT          ,
    FX_MODE_CHASE_BLACKOUT_RAINBOW  ,
//    FX_MODE_COLOR_SWEEP_RANDOM      ,
    FX_MODE_RUNNING_COLOR           ,
//    FX_MODE_RUNNING_RED_BLUE        ,
    FX_MODE_RUNNING_RANDOM          ,
    FX_MODE_LARSON_SCANNER          ,
//    FX_MODE_COMET                   ,
//    FX_MODE_FIREWORKS               ,
//    FX_MODE_FIREWORKS_RANDOM        ,
    FX_MODE_MERRY_CHRISTMAS         ,
//    FX_MODE_FIRE_FLICKER            ,
    FX_MODE_FIRE_FLICKER_SOFT       ,
//    FX_MODE_FIRE_FLICKER_INTENSE    ,
    FX_MODE_CIRCUS_COMBUSTUS        ,
    FX_MODE_HALLOWEEN               ,
    FX_MODE_BICOLOR_CHASE           ,
    FX_MODE_TRICOLOR_CHASE          ,
    FX_MODE_ICU                     
};
const uint8_t myModeCount = (sizeof(myModes)/sizeof(myModes[0]));

// defined times to keep certain stages running
#define FADE_IN_TIMESTEP    (1000/255)  // approximately. 
                                    // no matter if we fade colors or brightness. 
                                    // There are 255 values to be done in the milliseconds given
#define FADE_WHITE_TIMESTEP (10000/255)  
#define FADE_OUT_TIMESTEP   (2000/255)
#define ANIM_TIME           (3000)      // how long to have the effect running
#define LIGHT_ON_TIME       (20000)

void loop() {

    // static ensures that they are not deleted when going out of "loop" and 
    // still keeps them local. Another option would be a global variable 
    // (as you did with PIR_SENSOR)
    static bool new_motion_detected = false;    // to detect motion on the sensor and to debounce and detect new motion

    static bool anim_running = false;           // whether in a stage with animation running or not

    static stages stage = OFF;                  // our little basic state machine

    static uint8_t fade_amount = 0;             // used for fading to white

    static uint32_t fade_step = 0;              // time base used for fades
    static uint32_t anim_end = 0;               // time for the animation stage to last / end
    static uint32_t on_end   = 0;               // end of the on time of the white on stage

    uint32_t now = millis();        // gets the current time at every call

    uint8_t brightness = ws2812fx.getBrightness();  // get the current brightness - for fade in and fade out

    if(anim_running) ws2812fx.service();        // during animation we use the libraries service routine for the modes
    else ws2812fx.show();                       // for the fading we just use show to draw the pixels

    PIR_STATE = digitalRead(PIR_PIN);           // a button in my example

    if(PIR_STATE == LOW) {                      // button pressed i.e. motion detected
        if(new_motion_detected == false) {      // when this is a new motion
            new_motion_detected = true; // we have motion detected
            ws2812fx.setMode(random(myModeCount)); // set a random mode, 
            ws2812fx.setBrightness(0);          // start at zero brigthness

            Serial.println("detected motion");  
            Serial.print("mode is "); Serial.println(ws2812fx.getModeName(ws2812fx.getMode()));

            // now we switch to the first stage:
            stage = FADE_IN;
            fade_step = now + FADE_IN_TIMESTEP;
            anim_running = true;
        }
        else // Retrigger the timer?
        {
            on_end = now + LIGHT_ON_TIME; // new motion --> extend to further ... seconds
            if(stage == FADE_OUT) // hurry, we were fading out already.... lets return to fade white
            {
                stage = FADE_WHITE;
                fade_step = now + FADE_WHITE_TIMESTEP;
                fade_amount = 0;
            }
        }
    }

    // handle the states
    switch (stage) {
        case OFF :      // defaul stage, not much to do.
            if(brightness != 0)     // sanity.... but in case its not 0, we set it to 0
            {
                ws2812fx.setBrightness(0);
            }
            new_motion_detected = false; // if everything is off, we can surly wait for a new motion detection
        break;
        case FADE_IN :      // fade into the mode
            if(now > fade_step)
            {
                fade_step = now + FADE_IN_TIMESTEP;     // next step
                if(brightness<255)
                {
                    ws2812fx.setBrightness(brightness + 1); // as long as the brightness is not at max, increase it
                }
                else       // once we reached maximum brightness, we switch to the next mode with initial time
                {
                    stage = ANIM;
                    anim_end = now + ANIM_TIME; // end time of the next stage
                }
            }
        break;
        case ANIM :     // keep the animation running for a while
            if(now > anim_end)
            {
                anim_running = false; // stop the animation at this frame;
                stage = FADE_WHITE;     // switch to fade white stage
                fade_step = now + FADE_WHITE_TIMESTEP;
                fade_amount = 0;        // how "white" we are already - zero when starting
            }
        break;
        case FADE_WHITE :   // fade to white stage
            if(now > fade_step)
            {
                fade_step = now + FADE_WHITE_TIMESTEP;      // next fade step
                for(uint16_t i=0; i<LED_COUNT; i++)         // we fade the color of every LED
                {
                    uint32_t color = ws2812fx.getPixelColor(i);
                    color = ws2812fx.color_blend(color, WHITE, fade_amount);
                    ws2812fx.setPixelColor(i, color);       
                }
                if(fade_amount < 255) {
                    fade_amount ++;     // fade towards full wide
                } 
                else
                {
                    // at full white, switch to the on stage with
                    // the calculated on_end time
                    stage = ON;
                    on_end = now + LIGHT_ON_TIME;
                }
            }
        break;
        case ON :   // stage where plain white is shown for the defined time

            if(now > on_end)        // not much to do. At the end, switch to FADE OUT
            {
                stage = FADE_OUT;
                fade_step = now + FADE_OUT_TIMESTEP;
            }
        break;
        case FADE_OUT :  // fade from White to OFF

            if(now > fade_step)
            {
                fade_step = now + FADE_OUT_TIMESTEP;        // next step
                if(brightness > 0)
                {
                    ws2812fx.setBrightness(brightness - 1); // we dim, asd long as >0
                }
                else
                {
                    // when fading is done, we only need to switch to stage off
                    stage = OFF;
                }
            }    
        break;
        default :  // every switch - case should have a default
            stage = OFF;
        break;
    }
}

Might be worth noting that I only work with ESP8266... So Flash, RAM and performance are no issues at all and I have no idea when other hardware will be close to or beyond its limits.

haganwalker commented 6 years ago

I just had a chance to test and debug tonight. I just have to say - thanks to you both. This is incredibly helpful and gives me pretty much everything I need and more! I've set up a 1:6 scale test indoors and we'll be scoring concrete and doing a more substantial test in the coming weeks before committing to the 216 foot run. I'll be sure to keep you all in the loop. Again, I really appreciate the help and the examples. If there's anything I can do to help contribute, please let me know. Happy to pass a few bucks along for development and I'll be sure to advertise the library. Consider this one closed!