diff --git a/LedString.cpp b/LedString.cpp index 2d97132..05e4227 100644 --- a/LedString.cpp +++ b/LedString.cpp @@ -1,272 +1,321 @@ /* -LedString class by Doug Leary 2018 -Requires FastLED library +LedString class by Doug Leary 2020 +Simple FastLED consumer to control lighting in model buildings for a Christmas village, railroad layout or the like. + Tested Controllers: -Arduino Uno -NodeMCU 1.0 (ESP-12E) + Arduino Uno + NodeMCU 1.0 (ESP-12E) + ESP-01s Tested LED devices: -WS2811 -NEOPIXEL ring + WS2811 + NEOPIXEL */ #include "FastLED.h" #include "LedString.h" -void LedString::setHandler(char label, LedStringHandler f) { - for (int i=0; ilabel = label; + this->interval = interval; } -////// standard handlers -void setRed(CRGB* leds, int i) { - leds[i] = CRGB::Red; +void LedHandler::setup(LedString ls) +{ + // do when a pattern is created or changed } -void setGreen(CRGB* leds, int i) { - leds[i] = CRGB::Green; +void LedHandler::start(LedString ls) +{ + // if handler is enabled, do before cycling through leds } -void setBlue(CRGB* leds, int i) { - leds[i] = CRGB::Blue; +void LedHandler::loop(LedString ls, int i) +{ + // do for each led with this label } -void setYellow(CRGB* leds, int i) { - leds[i] = CRGB::Yellow; -} +/////////////// SimpleHandler -void setWhite(CRGB* leds, int i) { - leds[i] = CRGB::White; +SimpleHandler::SimpleHandler(char label, uint32_t interval, CRGB color) + : LedHandler(label, interval) +{ + this->color = color; } -void setBlack(CRGB* leds, int i) { - leds[i] = CRGB::Black; +void SimpleHandler::loop(LedString ls, int i) +{ + ls.leds[i] = color; } -void flicker(CRGB* leds, int led) { +/////////////// Flame + +FlameHandler::FlameHandler(char label, uint32_t interval) + : LedHandler(label, interval) +{ } + +void FlameHandler::loop(LedString ls, int i) +{ int value = random(LedString::FIRE_MIN, LedString::FIRE_MAX); // occasional intense flicker - if (value <= LedString::FIRE_MIN + LedString::FLICKER_EXTRA) { + if (value <= LedString::FIRE_MIN + LedString::FLICKER_EXTRA) + { value = LedString::FIRE_MIN - ((LedString::FIRE_MIN - LedString::FLICKER_MIN) / (value - LedString::FIRE_MIN + 1)); } - else if (value >= LedString::FIRE_MAX - LedString::FLICKER_EXTRA) { + else if (value >= LedString::FIRE_MAX - LedString::FLICKER_EXTRA) + { value = LedString::FIRE_MAX + ((LedString::FLICKER_MAX - LedString::FIRE_MAX) / (LedString::FIRE_MAX - value)); } - leds[led] = CHSV(25, 187, value); - FastLED.show(); + ls.leds[i] = CHSV(25, 187, value); +}; + +/////////////// ActiveGroup + +ActiveGroup::ActiveGroup(char label, uint32_t minInterval, uint32_t maxInterval, CRGB color, int percentOn) + : LedHandler(label, 0) +{ + this->minInterval = minInterval; + this->maxInterval = max(maxInterval, minInterval+1); // in case minInterval = maxInterval + this->color = color; + this->percentOn = percentOn; +} + +void ActiveGroup::setup(LedString ls) { + // count the leds in this group and turn each one on/off randomly according to percentOn + groupCount = 0; + groupCountOn = 0; + for (int i = 0; i < ls.ledCount; i++) + { + if (ls.pattern.charAt(i) == this->label) { + groupCount++; + if (random(0, 100) < percentOn) { + ls.leds[i] = color; + groupCountOn++; + } else { + ls.leds[i] = CRGB::Black; + } + } + } + isTurningOn = (percentOn >= 50); // will be reversed when start() first executes +} + +void ActiveGroup::start(LedString ls) { + isTurningOn = (groupCountOn < (ls.ledCount * percentOn / 100)); + + if (isTurningOn) { + countdown = random(0, groupCount - groupCountOn); + } else { + countdown = random(0, groupCountOn); + } +} + +void ActiveGroup::loop(LedString ls, int ledNumber) { + bool isOn = ls.isOn(ledNumber); + if (isTurningOn && !isOn) { + if (countdown > 0) { + countdown--; + } else { + ls.leds[ledNumber] = color; + groupCountOn++; + enabled = false; + interval = random(minInterval, maxInterval); + } + } else if (!isTurningOn && isOn) { + if (countdown > 0) + { + countdown--; + } + else + { + ls.leds[ledNumber] = CRGB::Black; + groupCountOn--; + enabled = false; + interval = random(minInterval, maxInterval); + } + } else { + } +} + +/////////////// LedString + +uint32_t _time = 0; +uint32_t _previousTime = 0; + +uint32_t LedString::currentTime() { + return _time; } +uint32_t LedString::previousTime() { + return _previousTime; +} + +// array of defined handlers the system knows about +LedHandler* handlers[MAX_LED_STRING_HANDLERS]; +int handler_count = 0; + +// behaviors is an array of handlers, one for each led; +LedHandler* behaviors[MAX_LEDS]; -////// +void LedString::addHandler(LedHandler* h) { + // if exists replace + for (int i=0; ilabel == h->label) { + handlers[i] = h; + return; + } + } + // not found so append + handlers[handler_count] = h; + handler_count++; +} -void LedString::resetAll() { - FastLED.clear(); +void LedString::addSimpleHandler(char label, uint32_t interval, CRGB color) +{ + SimpleHandler* h = new SimpleHandler(label, interval, color); + addHandler(h); } -void LedString::setCycleSetup(LedStringCycleSetup f) { - cycleSetup = f; +void LedString::setLed(int i, CRGB color) { + leds[i] = color; } bool LedString::isOn(int led) { return ( - (leds[led].red == 255) && - (leds[led].green == 255) && - (leds[led].blue == 255)); + (leds[led].red > 0) || + (leds[led].green > 0) || + (leds[led].blue > 0)); } -void LedString::turnOn(int led) { - setWhite(leds, led); - FastLED.show(); +void LedString::turnOn(int i) { + leds[i] = CRGB::White; } -void LedString::turnOff(int led) { - setBlack(leds, led); - FastLED.show(); +void LedString::turnOff(int i) { + leds[i] = CRGB::Black; } void LedString::turnAllOn() { - for (int i = 0; i < _length; i++) { - setWhite(leds, i); + for (int i = 0; i < ledCount; i++) { + leds[i] = CRGB::White; } - FastLED.show(); } void LedString::turnAllOff() { - for (int i = 0; i < _length; i++) { - setBlack(leds, i); + for (int i = 0; i < ledCount; i++) { + leds[i] = CRGB::Black; } - FastLED.show(); -} - -void LedString::turnOnSteadies() { - // turn on all switched or constantly lit leds - for (int i = 0; i < _length; i++) { - char ch = pattern.charAt(i); - switch (ch) { - case 'W': - leds[i] = CRGB::White; - break; - case 'S': - leds[i] = CRGB::White; - break; - case 'R': - leds[i] = CRGB::Red; - break; - case 'G': - leds[i] = CRGB::Green; - break; - case 'B': - leds[i] = CRGB::Blue; - break; - case 'Y': - leds[i] = CRGB::Yellow; - break; - } - } - FastLED.show(); } -bool LedString::isEventTime() { - long msNow = millis(); - long gap = msNow - lastEventTime; - if (gap >= FLICKER_RATE) { - lastEventTime = msNow; +bool LedString::isEventTime(uint32_t interval, uint32_t &previousTime) { + if (_time - previousTime >= interval) { + previousTime = _time; return true; } else { - // note: this includes when millis() rolls over to 0, because gap will be < 0 + // this includes when millis() rolls over to 0, because gap will be < 0 return false; } } -int numSwitches = 0; -int switchIndex = 0; -int switchIndexToToggle = 0; -long nextSwitchTime = 0; - -void LedString::setupSwitches() { - for (int i = 0; i < _length; i++) { - if (pattern.charAt(i) == 'S') { - numSwitches++; - } - } - if (numSwitches > 1) { - switchIndexToToggle = random(0, numSwitches); - nextSwitchTime = millis() + (max(LedString::AVERAGE_SWITCH_INTERVAL / numSwitches, LedString::MIN_SWITCH_INTERVAL)); - } +void LedString::addBuiltInHandlers() { + addSimpleHandler('O', 0, CRGB::Black); // also used as the dummy handler for unknown pattern chars + addSimpleHandler('R', 0, CRGB::Red); + addSimpleHandler('G', 0, CRGB::Green); + addSimpleHandler('B', 0, CRGB::Blue); + addSimpleHandler('Y', 0, CRGB::Yellow); + addSimpleHandler('W', 0, CRGB::White); + FlameHandler *fh = new FlameHandler('F', FLICKER_RATE); + addHandler(fh); + ActiveGroup *ag = new ActiveGroup('A', HABITATION_MIN_INTERVAL, HABITATION_MAX_INTERVAL, CRGB::White, HABITATION_DEFAULT_PERCENT); + addHandler(ag); } -void checkSwitch(CRGB* leds, int i) { - // skip if it's not time to switch - long msNow = millis(); - if (msNow < nextSwitchTime) return; - if (switchIndex == switchIndexToToggle) { - // toggle this switch, pick another to toggle next, and reset the index - if (leds[i].red > 0) { - leds[i] = CRGB::Black; - } else { - leds[i] = CRGB::White; +void LedString::addBehavior(char label, int ledNumber) { + // find the handler for the label and append it to behaviors + for (int i=0; ilabel == label) { + behaviors[ledNumber] = handlers[i]; + return; } - switchIndexToToggle = random(0, numSwitches); - nextSwitchTime = msNow + nextSwitchTime; - switchIndex = 0; - } else { - switchIndex++; } + behaviors[ledNumber] = handlers[0]; // use dummy handler } -void LedString::setupStandardHandlers() { - setHandler('R', &setRed); - setHandler('G', &setGreen); - setHandler('B', &setBlue); - setHandler('Y', &setYellow); - setHandler('W', &setWhite); - setHandler('O', &setBlack); // O=Off - setHandler('F', &flicker); - setHandler('S', &checkSwitch); -} - -void LedString::addBehavior(char label) { - for (int i=0; isetup(*this); } } void LedString::setPattern(String newPattern) { // if new string is longer than original it is truncated; // if shorter it is padded with Os to turn off the unused leds. - String st = newPattern; - st.replace(" ", ""); - st.remove(_length); - st.toUpperCase(); - int shortness = _length - st.length(); + pattern = newPattern; + pattern.replace(" ", ""); + pattern.remove(ledCount); + int shortness = ledCount - pattern.length(); for (int i=0; ienabled = (isEventTime(h->interval, h->whenLast)); + if (h->enabled) { + h->whenLast = _time; + h->start(*this); + } + } } -void LedString::doCycle() { - cycleSetup(); - // perform each led behavior, passing it the led number - for (int i = 0; i < behavior_count; i++) { - behaviors[i](leds, i); +void LedString::doBehaviors() { + + for (int i = 0; i < ledCount; i++) { + LedHandler* h = behaviors[i]; + if (h->enabled) { + h->loop(*this, i); + } } } -void LedString::doStart() { - turnAllOff(); - turnOnSteadies(); - doCycle(); -} +//void LedString::setup(String ledPattern, int ledCount) { +// leds = (CRGB*)malloc(ledCount * sizeof(CRGB)); +// FastLED.addLeds(leds, ledCount); +// //FastLED.addLeds(leds, ledCount); +// setup(ledPattern, leds); +//} -void LedString::doSetup(String _pattern, CRGB* ledArray) { +void LedString::setup(CRGB *ledArray, int numLeds) { leds = ledArray; - _length = FastLED.size(); - setupStandardHandlers(); - setCycleSetup(dummyCycleSetup); - setPattern(_pattern); + ledCount = numLeds; + turnAllOff(); + addBuiltInHandlers(); } -//void LedString::doSetup(String ledPattern) { -// leds = (CRGB*)malloc(_length * sizeof(CRGB)); -// FastLED.addLeds(leds, _length); -// //FastLED.addLeds(leds, _length); -// doSetup(ledPattern, leds); -// doStart(); -//} +void LedString::begin(String newPattern) { + setPattern(newPattern); +} -void LedString::doLoop() { - if (isEventTime()) { - doCycle(); - } +void LedString::loop() { + _time = millis(); + enableHandlers(); + doBehaviors(); + FastLED.show(); } \ No newline at end of file diff --git a/LedString.h b/LedString.h index 43fd76e..bb1433f 100644 --- a/LedString.h +++ b/LedString.h @@ -4,37 +4,80 @@ #include #define MAX_LEDS 100 // max number of behaviors, i.e. leds; need to make this dynamic and depend on the size of the pattern set by the app +#define MAX_LED_STRING_HANDLERS 20 -typedef void(*LedStringHandler)(CRGB*, int); -typedef void(*LedStringCycleSetup)(); +class LedString; // needed because handlers and LedString mutually reference each other. + +class LedHandler { // implements led behaviors + public: + char label; // 1-char label to use in pattern + uint32_t interval = 0; // milliseconds between events; if 0 do once only at startup + uint32_t whenLast = 0; // time of last event + bool enabled = false; // true if the handler should execute during this cycle + LedHandler(char label, uint32_t interval); + virtual void setup(LedString ls); // executed when a pattern is created or changed + virtual void start(LedString ls); // executed before cycling through the leds + virtual void loop(LedString ls, int i); // executed on each led with this label +}; + +class SimpleHandler : public LedHandler { + public: + CRGB color; + SimpleHandler(char label, uint32_t interval, CRGB color); + virtual void loop(LedString ls, int i); +}; + +class FlameHandler : public LedHandler { + public: + FlameHandler(char label, uint32_t interval); + virtual void loop(LedString ls, int i); +}; + +class ActiveGroup : public LedHandler { + public: + int groupCount; + int groupCountOn; + int countdown; + int percentOn; + bool isTurningOn; + CRGB color; + uint32_t minInterval; + uint32_t maxInterval; + +// public: + ActiveGroup(char label, uint32_t minInterval, uint32_t maxInterval, CRGB color, int percentOn); + virtual void setup(LedString ls); + virtual void start(LedString ls); + virtual void loop(LedString ls, int i); +}; -#define MAX_LED_STRING_HANDLERS 20 class LedString { public: CRGB* leds = 0; String pattern; - long lastEventTime; - // min/max brightness range of normal and intense flickers - static const int FIRE_MIN = 150; + static const int FLICKER_RATE = 80; // ms between brightness changes + static const int FLICKER_EXTRA = 2; // when brightness is this close to FIRE_MIN or MAX, FLICKER_MIN or MAX is used + static const int FIRE_MIN = 150; static const int FIRE_MAX = 190; static const int FLICKER_MIN = 130; static const int FLICKER_MAX = 225; ////// WS28xx // int FIRE_MIN = 80; int FIRE_MAX = 160; int FLICKER_MIN = 10; int FLICKER_MAX = 230; ////// NEOPIXEL - static const int FLICKER_RATE = 80; // ms between brightness changes - static const int FLICKER_EXTRA = 2; // when brightness is this close to FIRE_MIN or MAX, FLICKER_MIN or MAX is used + const int HABITATION_MIN_INTERVAL = 2000; // default interval range for built-in ActiveGroup + const int HABITATION_MAX_INTERVAL = 10000; + const int HABITATION_DEFAULT_PERCENT = 75; // default percentage of leds that should be on at any time - // timing for switched lights - static const long AVERAGE_SWITCH_INTERVAL = 20000L; // desired average ms between toggling a random switched led - static const long MIN_SWITCH_INTERVAL = 5000L; // shortest time between toggling + int ledCount; - void doSetup(String pattern, CRGB* ledArray); -// void doSetup(String pattern); - void doStart(); - void doLoop(); - void setHandler(char, LedStringHandler); + void setup(CRGB *ledArray, int ledCount); + void begin(String pattern); + void loop(); + uint32_t currentTime(); + uint32_t previousTime(); + void addHandler(LedHandler* h); + void addSimpleHandler(char label, uint32_t interval, CRGB color); void setPattern(String st); bool isOn(int led); @@ -42,34 +85,16 @@ class LedString void turnOff(int led); void turnAllOn(); void turnAllOff(); - void resetAll(); - void setLed(int i, CRGB::HTMLColorCode color); - void setCycleSetup(LedStringCycleSetup); // sets the function to call at the start of each cycle through the leds - + void setLed(int i, CRGB color); + private: - int _length; - - // labels is an array of characters, each attached to a handler function that applies a behavior to a led - // setHandler adds labels and their handlers to these lists - int handler_count = 0; - char labels[MAX_LED_STRING_HANDLERS] = "\0"; - LedStringHandler handlers[MAX_LED_STRING_HANDLERS]; - - // behaviors is an array of handler pointers, one for each led, executed in sequence by doCycle; - // this array is essentially the program for the led string - int behavior_count = 0; - LedStringHandler behaviors[MAX_LEDS]; - - static void dummyCycleSetup(); - LedStringCycleSetup cycleSetup; - - void setupStandardHandlers(); - void addBehavior(char label); - void parsePattern(String pattern); - void turnLitOn(); - void turnOnSteadies(); - bool isEventTime(); - void setupSwitches(); + void addBuiltInHandlers(); + void addBehavior(char label, int ledNumber); + void populateBehaviors(); + bool isEventTime(uint32_t interval, uint32_t &previousTime); + void setupHandlers(); + void enableHandlers(); + void doBehaviors(); void doCycle(); }; diff --git a/README.md b/README.md index 70f5a6d..4f2602d 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,44 @@ +## READ THIS FIRST +This project has been neglected for several years. The README doesn't include major improvements in adding custom behaviors, and I see a few other omissions, errors and awkward wording. Same with some of the code examples, which I think are cryptic due to lack of comments. I'm in the process of making code improvements, better examples, and bringing the README completely up to date - ETA early Feb 2026. This library isn't designed for spectacular animations or dazzling effects, it's goal is simplicity - to make it dead easy to use FastLED for simple tasks, while retaining some versatility, to control programmable RGB lighting in model buildings, railroad layouts and the like. If this interests you, please check back in February. Thanks! - DL + +------- + # LedString -FastLED wrapper to simplify lighting for model towns, castles, villages. +A wrapper class for [FastLED](https://fastled.io/) to simplify proammable LED lighting. I use it for a model Christmas town display, but it would work for anything similar - castles, villages, model train layouts, etc. It wasn't designed to generally animate Christmas lights, but who knows, give it a try. LEDString Has been tested with WS2811, WS2812, WS2812B, WS2813, and NEOPIXEL rings and matrices. -This wrapper class uses FastLED to set up lighting for buildings in a model town, medieval village, etc. -LEDs are animated individually by assigning a predefined behavior to each led. The code can be used with WS2811, WS2812, WS2812B, WS2813, or NEOPIXEL rings and matrices. +Behaviors are assigned to a string of programmable LEDs using a simple text string, one character per LED. Blanks can be included anywhere for readability, and are ignored. For example, the behavior string for two small cottages, each lit by a flickering fire, next to a brightly lit candy store with 3 white leds, could be "F F WWW". This is the same as "FFWWW" but a little more readable. When you edit a long string that defines dozens of leds, blanks make it easier to find the right section. -Behavior for individual LEDs is defined using a character string, one character per LED. Blanks can be inserted anywhere for readability, and are ignored. +#### Built-in Behaviors +**Static Colors** +* W: White +* R: Red +* G: Green +* B: Blue +* Y: Yellow +* O: Off -#### Behaviors -W: White, always lit -R: Red, always lit -G: Green, always lit -B: Blue, always lit -Y: Yellow, always lit -O = Off (uppercase "O" not zero) -S = Switched on and off semi-randomly to give an appearance of habitation. At a random interval of SWITCH_MIN..SWITCH_MAX milliseconds, one randomly chosen led marked "S" is toggled on or off. -F = Fire (flickering simulation of a fireplace, torch, etc). Fire LEDs flicker independently of each other. -C = Custom behavior (not working in this branch) +**Animated** +* F: Fire (a flickering simulation of a fireplace, torch, etc). Fire LEDs flicker independently of each other. +* A: Active group; leds marked "A" are switched on and off at semi-random times, to create the feel of places being inhabited. +At a random interval between SWITCH_MIN and SWITCH_MAX milliseconds, one "A" led is randomly chosen and toggled on or off. -Example: "WWOFW SSFWO OOWSS FSSSW" +**Custom Behaviors** +Using the Custom class you can define a custom behavior, assign it a letter, and use that letter to assign the behavior to leds. +See [examples/Custom/Custom.ino](https://github.com/DougLeary/LedString/blob/master/examples/Custom/Custom.ino). +You can even override a built-in behavior by reassigning its letter to a new behavior. -The constant NUM_LEDS must be defined as per the FastLED docs. If the pattern string (excluding blanks) is longer or shorter than this, it is truncated or padded with "O" respectively. +The constant NUM_LEDS must be defined as per the FastLED docs. If the pattern string (excluding blanks) is longer or shorter than NUM_LEDS, it is truncated or padded with the letter "O" respectively. -Note: As per FastLED docs the value for DATA_PIN used to call addLeds must be a constant (or multiple constants for multiple led strings). +Note: As per FastLED docs, the value for DATA_PIN used to call addLeds must be a _constant_ (or multiple constants for multiple led strings). This code works on Arduino and ESP8266. -***** TO DO: FINISH EDITING BEYOND THIS POINT ***** +Since only one "S" node is toggled on or off per interval, the more "S" leds you use, the less often an individual one will be switched. So if you want the appearance of more activity, edit LedString.h and try assigning a lower value for SWITCH_MAX. -The default hardware is WS2811. To select different hardware you must make two edits: -- in LedString.h uncomment the appropriate line to set FIRE_MIN, etc. -- in LedString.cpp uncomment the appropriate call to FastLED.addLeds for your hardware. +The led type is assumed to be WS2811 or WS2812. To select a different type you must make two edits: +- in LedString.h: uncomment the appropriate line to set FIRE_MIN, etc for the type of leds you are using. +- in LedString.cpp: uncomment the appropriate call to FastLED.addLeds for your type of leds. -These edits are necessary because of requirements of the FastLED library, which LedString is based on. Due to compile-time optimizations in FastLED, certain values cannot be passed in as parameters. +These edits are necessary because of FastLED reqirements, which LedString is based on. Due to compile-time optimizations in FastLED, certain values cannot be passed in as parameters. ### Usage Example @@ -60,4 +68,5 @@ Custom behavior is a limited way to add more lighting options (see examples/Cust 3. All fire LEDs are updated at the same regular interval, but this pattern is not apparent because each fire's brightness changes by a random amount. -4. The fire flicker interval acts as the master timing interval for the LED refresh cycle, even if no LEDs are designated as fires. This is simply because firelight was the first thing I implemented. The Arduino doLoop() checks whether it's time to flicker. If so, it makes a pass through the behavior string and updates fire leds and any other leds that need updating at that time. This means all effects occur at some multiple of the flicker rate. I figured this limitation was acceptable in a package designed to handle slow-paced lighting for towns, villages and such. +4. The fire flicker interval acts as the master timing interval for the LED refresh cycle, even if no LEDs are designated as fires. This is simply because firelight was the first thing I implemented. The Arduino doLoop() checks whether it's time to flicker. If so, it makes a pass through the behavior string and updates fire leds and any other leds that need updating at that time. This means all effects occur at some multiple of the flicker rate. I figured this limitation was acceptable in a package designed to handle slow-paced lighting for towns, villages, Christmas trees and such. A tree full of flickering fire leds would be interesting - I have not tried that, but in my tests 50 fires look fine with an 80ms flicker rate. + diff --git a/examples/ActiveGroup/ActiveGroup.ino b/examples/ActiveGroup/ActiveGroup.ino new file mode 100644 index 0000000..1d74f9f --- /dev/null +++ b/examples/ActiveGroup/ActiveGroup.ino @@ -0,0 +1,23 @@ +// LedString ActiveGroup Test +// Uses an ActiveGroup to simulate habitation activity in buildings + +#include +LedString lights; + +#define DATA_PIN 2 +#define NUM_LEDS 20 + +String pattern = "AAAAAAAAAAAAAAAAAAAA"; + +// allocate space for leds +CRGB leds[NUM_LEDS]; + +void setup() { + FastLED.addLeds(leds, NUM_LEDS); + lights.setup(leds, NUM_LEDS); + lights.begin(pattern); +} + +void loop() { + lights.loop(); +} diff --git a/examples/Custom/Custom.ino b/examples/Custom/Custom.ino new file mode 100644 index 0000000..603a5b9 --- /dev/null +++ b/examples/Custom/Custom.ino @@ -0,0 +1,24 @@ +// Custom ActiveGroup +// This sketch demonstrates how to customize the ActiveGroup behavior. +// To do this you simply create an instance of ActiveGroup with non-default parameters. + +#include +LedString lights; + +#define DATA_PIN 2 +#define NUM_LEDS 10 +CRGB leds[NUM_LEDS]; + +void setup() { + FastLED.addLeds(leds, NUM_LEDS); + lights.setup(leds, NUM_LEDS); + + // Turn leds on and off every quarter second, keeping about 20% of the leds on at once + ActiveGroup *group = new ActiveGroup('X', 250, 250, CRGB::Green, 20); + lights.addHandler(group); + lights.begin("XXXXXXXXXX"); +} + +void loop() { + lights.loop(); +} diff --git a/examples/CustomBehavior/CustomBehavior.ino b/examples/CustomBehavior/CustomBehavior.ino deleted file mode 100644 index 0ab2187..0000000 --- a/examples/CustomBehavior/CustomBehavior.ino +++ /dev/null @@ -1,46 +0,0 @@ -// Custom behavior example - toggles leds between red and blue every second - -#include "LedString.h" -LedString lights; - -#define DATA_PIN 3 -#define NUM_LEDS 8 - -// pattern is Red, Green, Blue, White, Custom, Custom, Custom, Custom -String pattern = "RGBWCCCC"; - -// allocate space for 8 leds -CRGB leds[NUM_LEDS]; - -uint32_t customInterval = 1000; // ms between toggle events -uint32_t lastCustomTime = -customInterval; // make the first event happen immediately - -void customBehavior(int led) { - uint32_t msNow = millis(); - // if customInterval has elapsed since the last toggle, it's time to toggle - if (msNow - lastCustomTime > customInterval) - { - lastCustomTime = msNow; - // if the led is red make it blue, else make it red - if (lights.leds[led].red == 255) { - lights.leds[led] = CRGB::Blue; - } else { - lights.leds[led] = CRGB::Red; - } - } - FastLED.show(); // physically update the leds -} - -void setup() { - // addLeds must be called here rather than being done by the library - // because FastLED requires the pin number to be a compile-time constant. - FastLED.addLeds(leds, NUM_LEDS); - - lights.doSetup(pattern, leds); - lights.setCustom(customBehavior); - lights.doStart(); -} - -void loop() { - lights.doLoop(); -} diff --git a/examples/LedString.cpp b/examples/LedString.cpp deleted file mode 100644 index 11f6488..0000000 --- a/examples/LedString.cpp +++ /dev/null @@ -1,207 +0,0 @@ -/* -LedString class by Doug Leary 2018 -Requires FastLED library -Tested Controllers: -Arduino Uno -NodeMCU 1.0 (ESP-12E) -Tested LED devices tested: -WS2811 -NEOPIXEL ring -*/ - -#include "FastLED.h" -#include "LedString.h" - -void LedString::resetAll() { - FastLED.clear(); -} - -void LedString::flicker(int led) { - int value = random(FIRE_MIN, FIRE_MAX); - // occasional intense flicker - if (value <= FIRE_MIN + FLICKER_EXTRA) { - value = FIRE_MIN - ((FIRE_MIN - FLICKER_MIN) / (value - FIRE_MIN + 1)); - } - else if (value >= FIRE_MAX - FLICKER_EXTRA) { - value = FIRE_MAX + ((FLICKER_MAX - FIRE_MAX) / (FIRE_MAX - value)); - } - leds[led] = CHSV(25, 187, value); - FastLED.show(); -} - -void LedString::setCustom(LedStringCustomFunction f) { - customHandler = f; -} - -void LedString::setCycleSetup(LedStringCycleSetup f) { - cycleSetup = f; -} - -bool LedString::isOn(int led) { - return ( - (leds[led].red == 255) && - (leds[led].green == 255) && - (leds[led].blue == 255)); -} - -void LedString::turnOn(int led) { - leds[led] = CRGB::White; - FastLED.show(); -} - -void LedString::turnOff(int led) { - leds[led] = CRGB::Black; - FastLED.show(); -} - -void LedString::turnAllOn() { - for (int i = 0; i < _length; i++) { - leds[i] = CRGB::White; - } - FastLED.show(); -} - -void LedString::turnAllOff() { - for (int i = 0; i < _length; i++) { - leds[i] = CRGB::Black; - } - FastLED.show(); -} - -void LedString::turnOnSteadies() { - // turn on all switched or constantly lit leds - for (int i = 0; i < _length; i++) { - char ch = pattern.charAt(i); - switch (ch) { - case 'W': - leds[i] = CRGB::White; - break; - case 'S': - leds[i] = CRGB::White; - break; - case 'R': - leds[i] = CRGB::Red; - break; - case 'G': - leds[i] = CRGB::Green; - break; - case 'B': - leds[i] = CRGB::Blue; - break; - case 'Y': - leds[i] = CRGB::Yellow; - break; - } - } - FastLED.show(); -} - -bool LedString::isEventTime() { - long msNow = millis(); - long gap = msNow - lastEventTime; - if (gap >= FLICKER_RATE) { - lastEventTime = msNow; - return true; - } - else { - // note: this includes when millis() rolls over to 0, because gap will be < 0 - return false; - } -} - -void LedString::checkSwitch(int led) { - if (switchIndex < switchIndexToToggle) { - // this isn't the switch to toggle - switchIndex++; - } - else { - if (isOn(led)) { - turnOff(led); - } - else { - turnOn(led); - } - switchIndexToToggle = random(0, numSwitches); - nextSwitchTime = millis() + random(SWITCH_MIN, SWITCH_MAX); - switchIndex = 0; - } -} - -void LedString::setupSwitches() { - for (int i = 0; i < _length; i++) { - if (pattern.charAt(i) == 'S') { - numSwitches++; - } - } - switchIndexToToggle = random(0, numSwitches); - nextSwitchTime = millis(); -} - -void LedString::setPattern(String newPattern) { - // if new string is longer than original it is truncated; - // if shorter it is padded with Os to turn off the unused leds. - String st = newPattern; - st.replace(" ", ""); - st.remove(_length); - st.toUpperCase(); - int shortness = _length - st.length(); - for (int i=0; i= nextSwitchTime) checkSwitch(i); - break; - case 'F': - flicker(i); - break; - case 'C': - customHandler(i); - break; - } - } -} - -void LedString::doStart() { - turnAllOff(); - turnOnSteadies(); - doCycle(); -} - -void LedString::doSetup(String _pattern, CRGB* ledArray) { - leds = ledArray; - _length = FastLED.size(); - setCustom(dummyCustom); - setCycleSetup(dummyCycleSetup); - setPattern(_pattern); -} - -//void LedString::doSetup(String ledPattern) { -// leds = (CRGB*)malloc(_length * sizeof(CRGB)); -// FastLED.addLeds(leds, _length); -// //FastLED.addLeds(leds, _length); -// doSetup(ledPattern, leds); -// doStart(); -//} - -void LedString::doLoop() { - if (isEventTime()) { - doCycle(); - } -} diff --git a/examples/LedString.h b/examples/LedString.h deleted file mode 100644 index e842a63..0000000 --- a/examples/LedString.h +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef LedString_h -#define LedString_h - -#include - -typedef void(*LedStringCustomFunction)(CRGB led); -typedef void(*LedStringCycleSetup)(); -typedef void(*LedStringAddLeds)(CRGB leds, int length); - -class LedString -{ -public: - CRGB* leds = 0; - long lastEventTime = 0L; - int numSwitches = 0; - int switchIndex = 0; - int switchIndexToToggle = 0; - long nextSwitchTime = 0; - String pattern; - - void doSetup(String pattern, CRGB* ledArray); -// void doSetup(String pattern); - void doStart(); - void doLoop(); - void setPattern(String st); - - bool isOn(int led); - void turnOn(int led); - void turnOff(int led); - void turnAllOn(); - void turnAllOff(); - void resetAll(); - - void setCustom(LedStringCustomFunction); // sets the function to call for behavior "C" - void setCycleSetup(LedStringCycleSetup); // sets the function to call at the start of each cycle through the leds - -private: - int _length; - // min/max brightness range of normal and intense flickers - int FIRE_MIN = 150; int FIRE_MAX = 190; int FLICKER_MIN = 130; int FLICKER_MAX = 225; ////// WS28xx -// int FIRE_MIN = 80; int FIRE_MAX = 160; int FLICKER_MIN = 10; int FLICKER_MAX = 230; ////// NEOPIXEL - - - int FLICKER_RATE = 80; // ms between brightness changes - int FLICKER_EXTRA = 2; // when brightness is this close to FIRE_MIN or MAX, FLICKER_MIN or MAX is used - - long SWITCH_MIN = 2000L; // min/max ms between toggling a random Switched light - long SWITCH_MAX = 5000L; - - void flicker(int led); - void turnLitOn(); - void turnOnSteadies(); - bool isEventTime(); - void checkSwitch(int led); - void setupSwitches(); - LedStringCycleSetup cycleSetup; - LedStringCustomFunction customHandler; - void doCycle(); - static void dummyCustom(CRGB led); - static void dummyCycleSetup(); -}; - -#endif diff --git a/examples/Sequence/Sequence.ino b/examples/Sequence/Sequence.ino new file mode 100644 index 0000000..302fbda --- /dev/null +++ b/examples/Sequence/Sequence.ino @@ -0,0 +1,56 @@ +#include +#define DATA_PIN 2 +#define NUM_LEDS 8 + +LedString myLedString; +CRGB leds[NUM_LEDS]; + +class ColorSequence : public LedHandler { + private: + CRGB* colors; + int colorCount; + int nextColor; + public: + ColorSequence (char label, uint32_t interval, CRGB colors[], int count) : LedHandler(label, interval) { + this->colors = colors; + this->colorCount = count; + this->nextColor = count; + } + void start(LedString ls) { + if (this->enabled) { + this->nextColor++; + if (this->nextColor >= this->colorCount) { + this->nextColor = 0; + } + } + } + void loop(LedString ls, int ledNumber) { + ls.leds[ledNumber] = this->colors[this->nextColor]; + } +}; + +CRGB colors1[] = { CRGB::Red, CRGB::Green, CRGB::Blue, CRGB::Black }; +CRGB colors2[] = { CRGB::Blue, CRGB::White }; + +void setup() { + Serial.begin(115200); while (!Serial) { ; } + Serial.println("\n*****"); + + FastLED.clear(); + FastLED.addLeds(leds, NUM_LEDS); + myLedString.setup(leds, NUM_LEDS); + + Serial.println("Adding custom handlers"); + ColorSequence *cs1 = new ColorSequence('1', 500, colors1, 4); + myLedString.addHandler(cs1); + ColorSequence *cs2 = new ColorSequence('2', 500, colors2, 2); + myLedString.addHandler(cs2); + + Serial.println("Calling begin"); + myLedString.begin("11112222"); +} + +void loop() { + myLedString.loop(); +} + diff --git a/examples/SimpleTest/SimpleTest.ino b/examples/SimpleTest/SimpleTest.ino index ffb9ed5..051d06f 100644 --- a/examples/SimpleTest/SimpleTest.ino +++ b/examples/SimpleTest/SimpleTest.ino @@ -3,24 +3,27 @@ #include LedString lights; -#define DATA_PIN 3 +#define DATA_PIN 2 #define NUM_LEDS 8 -// pattern is Red, Green, Blue, White, Yellow, Fire, Switched, Switched -String pattern = "RGBYWFSS"; +// Red, Green, Blue and Fire +String pattern = "RRGGBBFF"; // allocate space for 8 leds CRGB leds[NUM_LEDS]; void setup() { + Serial.begin(115200); + while (!Serial) { ; } + Serial.println("\n**************"); // addLeds must be called here rather than being done by the library // because FastLED requires the pin number to be a compile-time constant. FastLED.addLeds(leds, NUM_LEDS); - lights.doSetup(pattern, leds); - lights.doStart(); + lights.setup(leds, NUM_LEDS); + lights.begin(pattern); } void loop() { - lights.doLoop(); + lights.loop(); }