kitesurfer1404 / WS2812FX

WS2812 FX Library for Arduino and ESP8266
MIT License
1.58k stars 343 forks source link

Need help with custom effect #357

Open Bouni opened 1 month ago

Bouni commented 1 month ago

Hi,

I want to create a custom effect that looks like one of those fancy nw car blinkers where the length fills up with orange and then altogether turns off. Its almsot what the color_whipe effect does but that "empties" the length instead of turning it of at once.

I looked into the source of the color whipe effect, here: https://github.com/kitesurfer1404/WS2812FX/blob/master/src/modes_funcs.cpp#L84-L106

I also looked at a lot of custom effects and came up with this:

#include <WS2812FX.h>

#define LED_COUNT 30
#define LED_PIN 3

#define LEFT 0
#define CENTER 1
#define RIGHT 2

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

uint16_t CarBlinkerEffect(void) { 
  WS2812FX::Segment* seg = ws2812fx.getSegment(); // get the current segment
  WS2812FX::Segment_runtime* segrt = ws2812fx.getSegmentRuntime(); // get current segment runtime
  int seglen = seg->stop - seg->start + 1;

  if(segrt->counter_mode_step < seglen) { // Fill up the segment one by one
    ws2812fx.setPixelColor(segrt->counter_mode_step, ORANGE);
    segrt->counter_mode_step = (segrt->counter_mode_step + 1) % seglen;
  }
  else { // If segment is full, turn all black again
    for(uint16_t i=seg->stop; i>seg->start; i--) {
      ws2812fx.setPixelColor(i, BLACK);
    }
    segrt->counter_mode_step = 0;
  }
  return seg->speed; 
}

void setup() {
  Serial.begin(115200);
  ws2812fx.init();
  ws2812fx.setCustomMode(CarBlinkerEffect);
  ws2812fx.setSegment(LEFT,  0,  9, FX_MODE_CUSTOM, BLACK, 50, false); // segment 0 is leds 0 - 9
  ws2812fx.setSegment(CENTER, 10, 19, FX_MODE_STATIC,  BLACK, 1000, false); // segment 1 is leds 10 - 19
  ws2812fx.setSegment(RIGHT, 20, 29, FX_MODE_STATIC, BLACK, 1000, false);  // segment 2 is leds 20 - 29
  ws2812fx.start();
}

void loop() {
  ws2812fx.service();
}

The segment of 10 LEDs fills up as expected, but then nothing more happens and I don't understand why.

Can somebody help me figuring out where I made a mistake?

Bouni commented 1 month ago

I figured it out 🥳

uint16_t CarBlinkerEffect(void) { // random chase
  WS2812FX::Segment* seg = ws2812fx.getSegment(); // get the current segment
  WS2812FX::Segment_runtime* segrt = ws2812fx.getSegmentRuntime(); // get current segment runtime
  int seglen = seg->stop - seg->start + 1;
  bool isReverse = (seg->options & REVERSE) == REVERSE;

  if(segrt->counter_mode_step < seglen-1) {
    if(isReverse) {
      ws2812fx.setPixelColor(segrt->counter_mode_step, ORANGE);
    } else {
      ws2812fx.setPixelColor(seg->stop - segrt->counter_mode_step, ORANGE);
    }
    segrt->counter_mode_step = (segrt->counter_mode_step + 1) % seglen;
  }
  else {
    ws2812fx.fill(BLACK, seg->start, seglen);
    segrt->counter_mode_step = 0;
  }
  if(segrt->counter_mode_step == 0) ws2812fx.setCycle();
  return seg->speed; 
}
moose4lord commented 1 month ago

Yay! Good for you. Nice debug work.

However, I did notice that the first LED (or the last LED if REVERSE is enabled) does not illuminate. I think one more tweak is needed. Maybe this:

uint16_t CarBlinkerEffect(void) { // random chase
  WS2812FX::Segment* seg = ws2812fx.getSegment(); // get the current segment
  WS2812FX::Segment_runtime* segrt = ws2812fx.getSegmentRuntime(); // get current segment runtime
  int seglen = seg->stop - seg->start + 1;
  bool isReverse = (seg->options & REVERSE) == REVERSE;

//if(segrt->counter_mode_step < seglen - 1) {
  if(segrt->counter_mode_step < seglen) {
    if(isReverse) {
      ws2812fx.setPixelColor(segrt->counter_mode_step, ORANGE);
    } else {
      ws2812fx.setPixelColor(seg->stop - segrt->counter_mode_step, ORANGE);
    }
//  segrt->counter_mode_step = (segrt->counter_mode_step + 1) % seglen;
    segrt->counter_mode_step++;
  } else {
    ws2812fx.fill(BLACK, seg->start, seglen);
    segrt->counter_mode_step = 0;
  }
  if(segrt->counter_mode_step == 0) ws2812fx.setCycle();
  return seg->speed; 
}

But you probably already figured it out. :)

Bouni commented 1 month ago

Actually I didn't even notice 😅

But I added your tweaks anyway as they are probably correct.

I have one more question, heres my complete code:

#include <WS2812FX.h>

#define LED_COUNT 300
#define LED_PIN 3

#define LEFT 0
#define CENTER 1
#define RIGHT 2

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

uint16_t CarBlinkerEffect(void) { // Car blinker effect (Audi & co)
  WS2812FX::Segment* seg = ws2812fx.getSegment(); 
  WS2812FX::Segment_runtime* segrt = ws2812fx.getSegmentRuntime(); 
  int seglen = seg->stop - seg->start + 1;
  bool isReverse = (seg->options & REVERSE) == REVERSE;

  if(segrt->counter_mode_step < seglen) {
    if(isReverse) {
      ws2812fx.setPixelColor(seg->start + segrt->counter_mode_step, ORANGE);
    } else {
      ws2812fx.setPixelColor(seg->stop - segrt->counter_mode_step, ORANGE);
    }
    segrt->counter_mode_step++;
  }
  else {
    ws2812fx.fill(BLACK, seg->start, seglen);
    segrt->counter_mode_step = 0;
  }
  if(segrt->counter_mode_step == 0) ws2812fx.setCycle();
  return seg->speed; 
}

void blink_left() {
  ws2812fx.resetSegments();
  ws2812fx.resetSegmentRuntimes();
  ws2812fx.strip_off();
  ws2812fx.setSegment(0,  0,  9, FX_MODE_CUSTOM, BLACK, 60, false);
}

void blink_right() {
  ws2812fx.resetSegments();
  ws2812fx.resetSegmentRuntimes();
  ws2812fx.strip_off();
  ws2812fx.setSegment(0,  20,  29, FX_MODE_CUSTOM, BLACK, 60, true);
}

void full_color(int color) {
  ws2812fx.resetSegments();
  ws2812fx.resetSegmentRuntimes();
  ws2812fx.strip_off();
  ws2812fx.setSegment(0,  0,  29, FX_MODE_STATIC, color, 60, false);
}

void full_blink(int color) {
  ws2812fx.resetSegments();
  ws2812fx.resetSegmentRuntimes();
  ws2812fx.strip_off();
  ws2812fx.setSegment(0,  0,  29, FX_MODE_BLINK, color, 1000, false);
}

void off() {
  ws2812fx.resetSegments();
  ws2812fx.resetSegmentRuntimes();
  ws2812fx.strip_off();
}

void setup() {
  Serial.begin(115200);
  ws2812fx.init();
  ws2812fx.setCustomMode(CarBlinkerEffect);
  ws2812fx.start();
  off();
}

void loop() {
  while (Serial.available() > 0) {
    String cmd;
    cmd = Serial.readString();
    cmd.trim();
    if(cmd == "bll") {
        blink_left();
        Serial.println("Start Left");
    }
    if(cmd == "blr") {
        blink_right();
        Serial.println("Start Right");
    }
    if(cmd == "g") {
        full_color(GREEN);
        Serial.println("Green");
    }
    if(cmd == "r") {
        full_color(RED);
        Serial.println("Red");
    }
    if(cmd == "b") {
        full_color(BLUE);
        Serial.println("Blue");
    }
    if(cmd == "y") {
        full_color(YELLOW);
        Serial.println("Yellow");
    }
    if(cmd == "bg") {
        full_blink(GREEN);
        Serial.println("Green");
    }
    if(cmd == "br") {
        full_blink(RED);
        Serial.println("Red");
    }
    if(cmd == "bb") {
        full_blink(BLUE);
        Serial.println("Blue");
    }
    if(cmd == "by") {
        full_blink(YELLOW);
        Serial.println("Yellow");
    }
    if(cmd == "0") {
        off();
        Serial.println("Off");
    }
  }
  ws2812fx.service();
}

This works as expected but maybe can be done better. The only thing that bothers me is that when I switch between the effects, it takes like half a scond until the next effect starts.

Is there something I can do about that?

Bouni commented 1 month ago

I'm an idiot. After writing my answer I realized that readString amkes use of the Serial timeout which defaults to 1000ms. Setting that to 100ms with Serial.setTimeout(100); makes it respond as expected 🤦🏽‍♂️

moose4lord commented 1 month ago

Yeah, those blocking Serial I/O statements are a pain. Setting the Serial timeout to 100 works, but it still blocks for 100ms. To up your Arduino mojo, the Serial Input Basics tutorial has some nice examples of non-blocking Serial I/O.