From 82d567d45a54da217e7d73c56316a7e5463afe7f Mon Sep 17 00:00:00 2001 From: Doug Leary Date: Fri, 25 Dec 2020 15:55:40 -0800 Subject: [PATCH 01/16] Custom --- LedString.cpp | 93 ++++++++++++++++++++------------------------------- LedString.h | 8 ++--- 2 files changed, 40 insertions(+), 61 deletions(-) diff --git a/LedString.cpp b/LedString.cpp index 2d97132..f428b33 100644 --- a/LedString.cpp +++ b/LedString.cpp @@ -12,6 +12,17 @@ NEOPIXEL ring #include "FastLED.h" #include "LedString.h" +uint32_t _time = 0; +uint32_t _lastEventTime = 0; + +uint32_t LedString::currentTime() { + return _time; +} + +uint32_t LedString::lastEventTime() { + return _lastEventTime; +} + void LedString::setHandler(char label, LedStringHandler f) { for (int i=0; i= FLICKER_RATE) { - lastEventTime = msNow; + _lastEventTime = _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; } } @@ -154,7 +133,8 @@ bool LedString::isEventTime() { int numSwitches = 0; int switchIndex = 0; int switchIndexToToggle = 0; -long nextSwitchTime = 0; +uint32_t nextSwitchTime = 0; +uint32_t switchInterval = 0; void LedString::setupSwitches() { for (int i = 0; i < _length; i++) { @@ -164,14 +144,14 @@ void LedString::setupSwitches() { } if (numSwitches > 1) { switchIndexToToggle = random(0, numSwitches); - nextSwitchTime = millis() + (max(LedString::AVERAGE_SWITCH_INTERVAL / numSwitches, LedString::MIN_SWITCH_INTERVAL)); + switchInterval = (max(LedString::AVERAGE_SWITCH_INTERVAL / numSwitches, LedString::MIN_SWITCH_INTERVAL)); + nextSwitchTime = _time + switchInterval; } } void checkSwitch(CRGB* leds, int i) { // skip if it's not time to switch - long msNow = millis(); - if (msNow < nextSwitchTime) return; + if (_time < nextSwitchTime) return; if (switchIndex == switchIndexToToggle) { // toggle this switch, pick another to toggle next, and reset the index if (leds[i].red > 0) { @@ -180,7 +160,7 @@ void checkSwitch(CRGB* leds, int i) { leds[i] = CRGB::White; } switchIndexToToggle = random(0, numSwitches); - nextSwitchTime = msNow + nextSwitchTime; + nextSwitchTime = _time + switchInterval; switchIndex = 0; } else { switchIndex++; @@ -227,9 +207,6 @@ void LedString::setPattern(String newPattern) { st += "O"; } pattern = st; - parsePattern(pattern); - setupSwitches(); - doStart(); } void LedString::dummyCycleSetup() { @@ -241,13 +218,16 @@ void LedString::doCycle() { for (int i = 0; i < behavior_count; i++) { behaviors[i](leds, i); } + FastLED.show(); } -void LedString::doStart() { - turnAllOff(); - turnOnSteadies(); - doCycle(); -} +//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::doSetup(String _pattern, CRGB* ledArray) { leds = ledArray; @@ -257,13 +237,12 @@ void LedString::doSetup(String _pattern, CRGB* ledArray) { 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::doStart() { + parsePattern(pattern); + setupSwitches(); + turnAllOff(); + doCycle(); +} void LedString::doLoop() { if (isEventTime()) { diff --git a/LedString.h b/LedString.h index 43fd76e..f23ba12 100644 --- a/LedString.h +++ b/LedString.h @@ -14,7 +14,6 @@ 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; @@ -27,13 +26,14 @@ class LedString static const int FLICKER_EXTRA = 2; // when brightness is this close to FIRE_MIN or MAX, FLICKER_MIN or MAX is used // 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 + static const uint32_t AVERAGE_SWITCH_INTERVAL = 20000; // desired average ms between toggling a random switched led + static const uint32_t MIN_SWITCH_INTERVAL = 5000; // shortest time between toggling void doSetup(String pattern, CRGB* ledArray); -// void doSetup(String pattern); void doStart(); void doLoop(); + uint32_t currentTime(); + uint32_t lastEventTime(); void setHandler(char, LedStringHandler); void setPattern(String st); From 141d1b8c1720a18594049fdf41d1607a9c60deeb Mon Sep 17 00:00:00 2001 From: Doug Leary Date: Fri, 25 Dec 2020 15:59:03 -0800 Subject: [PATCH 02/16] Custom example --- examples/CustomTest/CustomTest.ino | 47 ++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 examples/CustomTest/CustomTest.ino diff --git a/examples/CustomTest/CustomTest.ino b/examples/CustomTest/CustomTest.ino new file mode 100644 index 0000000..610c358 --- /dev/null +++ b/examples/CustomTest/CustomTest.ino @@ -0,0 +1,47 @@ +#include +#define DATA_PIN 2 +#define NUM_LEDS 5 + +LedString myLedString; +CRGB leds[5]; + +void sequence(CRGB* leds, int ledNumber, CRGB colors[], int count, uint32_t interval) { + // switch between two colors at intervals + static uint32_t lastChange = 0; + uint32_t timeNow = myLedString.currentTime(); + if (lastChange + interval < timeNow || timeNow < lastChange) { + for (int i=0; i(leds, NUM_LEDS); + myLedString.doSetup("21rgb", leds); + myLedString.setHandler('2', &blink2); + myLedString.setHandler('1', &blink1); + myLedString.doStart(); +} + +void loop() { + myLedString.doLoop(); +} From a7e2b09e443586df0e784b7d7bc511c6cfcf8689 Mon Sep 17 00:00:00 2001 From: Doug Leary Date: Fri, 1 Jan 2021 22:01:34 -0800 Subject: [PATCH 03/16] New custom --- README.md | 11 +- examples/Custom/Custom.ino | 49 +++++++++ examples/LedString.cpp | 207 ------------------------------------- examples/LedString.h | 63 ----------- 4 files changed, 55 insertions(+), 275 deletions(-) create mode 100644 examples/Custom/Custom.ino delete mode 100644 examples/LedString.cpp delete mode 100644 examples/LedString.h diff --git a/README.md b/README.md index 70f5a6d..aa82ff0 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,11 @@ 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) +O: Off +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. + +An application may assign any other single character to a custom behavior, or override any of the standard ones (see example code Custom.ino). Example: "WWOFW SSFWO OOWSS FSSSW" @@ -26,7 +27,7 @@ This code works on Arduino and ESP8266. ***** TO DO: FINISH EDITING BEYOND THIS POINT ***** -The default hardware is WS2811. To select different hardware you must make two edits: +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. diff --git a/examples/Custom/Custom.ino b/examples/Custom/Custom.ino new file mode 100644 index 0000000..d03a322 --- /dev/null +++ b/examples/Custom/Custom.ino @@ -0,0 +1,49 @@ +#include +#define DATA_PIN 2 +#define NUM_LEDS 5 + +LedString myLedString; +CRGB leds[5]; + +void sequence(CRGB* leds, int ledNumber, CRGB colors[], int count, uint32_t interval) { + // step through an array of colors at intervals; + // Limitation: each color must occur only once or it will get stuck alternating between + // the first occurrence of that color and the next color. + static uint32_t lastChange = 0; + uint32_t timeNow = myLedString.currentTime(); + if (lastChange + interval < timeNow || timeNow < lastChange) { + for (int i=0; i(leds, NUM_LEDS); + myLedString.doSetup("21rgb", leds); + myLedString.setHandler('2', &blink2); + myLedString.setHandler('1', &blink1); + myLedString.doStart(); +} + +void loop() { + myLedString.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 From 3b31d1e783c1671c6ed9b0e55ee2c56328da7360 Mon Sep 17 00:00:00 2001 From: Doug Leary Date: Fri, 1 Jan 2021 23:08:00 -0800 Subject: [PATCH 04/16] Remove test code --- examples/CustomTest/CustomTest.ino | 47 ------------------------------ 1 file changed, 47 deletions(-) delete mode 100644 examples/CustomTest/CustomTest.ino diff --git a/examples/CustomTest/CustomTest.ino b/examples/CustomTest/CustomTest.ino deleted file mode 100644 index 610c358..0000000 --- a/examples/CustomTest/CustomTest.ino +++ /dev/null @@ -1,47 +0,0 @@ -#include -#define DATA_PIN 2 -#define NUM_LEDS 5 - -LedString myLedString; -CRGB leds[5]; - -void sequence(CRGB* leds, int ledNumber, CRGB colors[], int count, uint32_t interval) { - // switch between two colors at intervals - static uint32_t lastChange = 0; - uint32_t timeNow = myLedString.currentTime(); - if (lastChange + interval < timeNow || timeNow < lastChange) { - for (int i=0; i(leds, NUM_LEDS); - myLedString.doSetup("21rgb", leds); - myLedString.setHandler('2', &blink2); - myLedString.setHandler('1', &blink1); - myLedString.doStart(); -} - -void loop() { - myLedString.doLoop(); -} From 973ea7740fd3def64587b526ebc4db52721d222e Mon Sep 17 00:00:00 2001 From: Doug Leary Date: Sat, 2 Jan 2021 23:50:09 -0800 Subject: [PATCH 05/16] cleanup before more mods --- LedString.cpp | 10 +++++++--- LedString.h | 2 ++ examples/Custom/Custom.ino | 10 +++++----- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/LedString.cpp b/LedString.cpp index f428b33..b9e90aa 100644 --- a/LedString.cpp +++ b/LedString.cpp @@ -116,12 +116,12 @@ void LedString::turnAllOff() { } } -bool LedString::isEventTime() { +bool LedString::isEventTime(uint32_t &previousTime) { // time sync function; everything else is based on _time and _lastEventTime _time = millis(); - uint32_t gap = _time - _lastEventTime; + uint32_t gap = _time - previousTime; if (gap >= FLICKER_RATE) { - _lastEventTime = _time; + previousTime = _time; return true; } else { @@ -130,6 +130,10 @@ bool LedString::isEventTime() { } } +bool LedString::isEventTime() { + return isEventTime(_lastEventTime); +} + int numSwitches = 0; int switchIndex = 0; int switchIndexToToggle = 0; diff --git a/LedString.h b/LedString.h index f23ba12..cb990f2 100644 --- a/LedString.h +++ b/LedString.h @@ -29,6 +29,7 @@ class LedString static const uint32_t AVERAGE_SWITCH_INTERVAL = 20000; // desired average ms between toggling a random switched led static const uint32_t MIN_SWITCH_INTERVAL = 5000; // shortest time between toggling + //void doSetup(String ledPattern); void doSetup(String pattern, CRGB* ledArray); void doStart(); void doLoop(); @@ -69,6 +70,7 @@ class LedString void turnLitOn(); void turnOnSteadies(); bool isEventTime(); + bool isEventTime(uint32_t &previousTime); void setupSwitches(); void doCycle(); }; diff --git a/examples/Custom/Custom.ino b/examples/Custom/Custom.ino index d03a322..7d206b6 100644 --- a/examples/Custom/Custom.ino +++ b/examples/Custom/Custom.ino @@ -26,19 +26,19 @@ void sequence(CRGB* leds, int ledNumber, CRGB colors[], int count, uint32_t inte } void blink1(CRGB* leds, int i) { - CRGB colors[] = { CRGB::Green, CRGB::Red, CRGB::Yellow, CRGB::Orange, CRGB::Blue }; - sequence(leds, i, colors, 5, 3000); + CRGB colors[] = { CRGB::Red, CRGB::White, CRGB::Blue }; + sequence(leds, i, colors, 3, 1000); } void blink2(CRGB* leds, int i) { - CRGB colors[] = { CRGB::Red, CRGB::Green }; - sequence(leds, i, colors, 2, 1000); + CRGB colors[] = { CRGB::Red, CRGB::Green, CRGB::Blue }; + sequence(leds, i, colors, 3, 1000); } void setup() { FastLED.clear(); FastLED.addLeds(leds, NUM_LEDS); - myLedString.doSetup("21rgb", leds); + myLedString.doSetup("12rgb", leds); myLedString.setHandler('2', &blink2); myLedString.setHandler('1', &blink1); myLedString.doStart(); From 096afb61f2b5431245b42cbda641cc7a8d3a8e21 Mon Sep 17 00:00:00 2001 From: Doug Leary Date: Tue, 5 Jan 2021 23:46:57 -0800 Subject: [PATCH 06/16] Make built-in and custom handlers work the same way. --- LedString.cpp | 218 +++++++++++++++++++++++-------------- LedString.h | 61 +++++------ examples/Custom/Custom.ino | 46 ++++---- 3 files changed, 194 insertions(+), 131 deletions(-) diff --git a/LedString.cpp b/LedString.cpp index b9e90aa..72dd340 100644 --- a/LedString.cpp +++ b/LedString.cpp @@ -1,39 +1,60 @@ /* -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" uint32_t _time = 0; -uint32_t _lastEventTime = 0; +uint32_t _previousTime = 0; uint32_t LedString::currentTime() { return _time; } -uint32_t LedString::lastEventTime() { - return _lastEventTime; +uint32_t LedString::previousTime() { + return _previousTime; +} + +// array of defined handlers the system knows about +Handler* handlers[MAX_LED_STRING_HANDLERS]; +int handler_count = 0; + +// behaviors is an array of handlers, one for each led; +Handler* behaviors[MAX_LEDS]; +int behavior_count = 0; + +void Handler::init(char label, LedStringHandler f, uint32_t interval) { + this->label = label; + this->f = f; + this->interval = interval; + this->whenLast = 0; + this->enabled = false; } -void LedString::setHandler(char label, LedStringHandler f) { +void LedString::addHandler(char label, LedStringHandler f, uint32_t interval) { + // instantiate a handler on the heap + Handler* h = new Handler(); + h->init(label, f, interval); + + // if exists replace for (int i=0; ilabel == label) { + handlers[i] = h; } } - // new label; add handler - labels[handler_count] = label; - handlers[handler_count] = f; + // not found so append + Serial.print("Adding handler "); Serial.println(label); + handlers[handler_count] = h; handler_count++; } @@ -43,26 +64,32 @@ void LedString::setLed(int i, CRGB::HTMLColorCode color) { ////// standard handlers void setRed(CRGB* leds, int i) { + Serial.print(" Red"); leds[i] = CRGB::Red; } void setGreen(CRGB* leds, int i) { + Serial.print(" Green"); leds[i] = CRGB::Green; } void setBlue(CRGB* leds, int i) { + Serial.print(" Blue"); leds[i] = CRGB::Blue; } void setYellow(CRGB* leds, int i) { + Serial.print(" Yellow"); leds[i] = CRGB::Yellow; } void setWhite(CRGB* leds, int i) { + Serial.print(" White"); leds[i] = CRGB::White; } void setBlack(CRGB* leds, int i) { + Serial.print(" Black"); leds[i] = CRGB::Black; } @@ -85,10 +112,6 @@ void LedString::resetAll() { FastLED.clear(); } -void LedString::setCycleSetup(LedStringCycleSetup f) { - cycleSetup = f; -} - bool LedString::isOn(int led) { return ( (leds[led].red == 255) && @@ -116,11 +139,8 @@ void LedString::turnAllOff() { } } -bool LedString::isEventTime(uint32_t &previousTime) { - // time sync function; everything else is based on _time and _lastEventTime - _time = millis(); - uint32_t gap = _time - previousTime; - if (gap >= FLICKER_RATE) { +bool LedString::isEventTime(uint32_t interval, uint32_t &previousTime) { + if (_time - previousTime >= interval) { previousTime = _time; return true; } @@ -130,27 +150,26 @@ bool LedString::isEventTime(uint32_t &previousTime) { } } -bool LedString::isEventTime() { - return isEventTime(_lastEventTime); -} - int numSwitches = 0; -int switchIndex = 0; +int switchIndex = 0; int switchIndexToToggle = 0; uint32_t nextSwitchTime = 0; uint32_t switchInterval = 0; -void LedString::setupSwitches() { +void LedString::countSwitches() { + Serial.println("Counting switches"); for (int i = 0; i < _length; i++) { if (pattern.charAt(i) == 'S') { numSwitches++; } } - if (numSwitches > 1) { - switchIndexToToggle = random(0, numSwitches); - switchInterval = (max(LedString::AVERAGE_SWITCH_INTERVAL / numSwitches, LedString::MIN_SWITCH_INTERVAL)); - nextSwitchTime = _time + switchInterval; - } + // Serial.printf("%n switches\n", numSwitches); + // if (numSwitches > 1) { + // switchIndexToToggle = random(0, numSwitches); + // switchInterval = (max(LedString::AVERAGE_SWITCH_INTERVAL / numSwitches, LedString::MIN_SWITCH_INTERVAL)); + // nextSwitchTime = _time + switchInterval; + // } + Serial.printf("%d Switches\n", numSwitches); } void checkSwitch(CRGB* leds, int i) { @@ -171,85 +190,126 @@ void checkSwitch(CRGB* leds, int i) { } } -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::addStandardHandlers() { + addHandler('O', &setBlack, 0); // also used as the dummy handler for unknown pattern chars + addHandler('R', &setRed, 0); + addHandler('G', &setGreen, 0); + addHandler('B', &setBlue, 0); + addHandler('Y', &setYellow, 0); + addHandler('W', &setWhite, 0); + addHandler('F', &flicker, FLICKER_RATE); + addHandler('S', &checkSwitch, 0); + Serial.print("Standard Handlers Added: "); + Serial.println(handler_count); + char ch = handlers[handler_count - 1]->label; + Serial.printf("Last one is %c \n",ch); + } void LedString::addBehavior(char label) { + // find the handler for the label and append it to behaviors for (int i=0; ilabel == label) { + Serial.print("Using behavior "); + Serial.println(label); behaviors[behavior_count++] = handlers[i]; return; } } + Serial.print("Behavior "); Serial.print(label); Serial.println(" not found; using default"); + behaviors[behavior_count++] = handlers[0]; // use dummy handler } -void LedString::parsePattern(String pat) { +void LedString::populateBehaviors() { + // for each character in the pattern add the corresponding handler to the behaviors list behavior_count = 0; + Serial.printf("Populating %d Behaviors for pattern ", _length); + Serial.println(pattern); for (int i = 0; i < _length; i++) { - char label = pat.charAt(i); + char label = pattern.charAt(i); addBehavior(label); } + Serial.print(behavior_count); Serial.println(" behaviors added"); } 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. + Serial.print("newPattern: "); Serial.println(newPattern); String st = newPattern; - st.replace(" ", ""); - st.remove(_length); - st.toUpperCase(); - int shortness = _length - st.length(); - for (int i=0; ienabled = (isEventTime(handlers[i]->interval, handlers[i]->whenLast)); + Serial.print(i); + if (handlers[i]->enabled) { Serial.print(" "); Serial.print(handlers[i]->label); Serial.println(" enabled"); } + else { Serial.println(" disabled"); } + } } -void LedString::dummyCycleSetup() { +void LedString::saveEventTime() { + Serial.println("===== saveEventTime"); + for (int i=0; i < handler_count; i++) { + if (handlers[i]->enabled) { + handlers[i]->whenLast = _time; + } + } } -void LedString::doCycle() { - cycleSetup(); - // perform each led behavior, passing it the led number +void LedString::doBehaviors() { + Serial.println("===== doBehaviors"); for (int i = 0; i < behavior_count; i++) { - behaviors[i](leds, i); + if (behaviors[i]->enabled) { + Serial.print("Doing "); + Serial.print(behaviors[i]->label); + behaviors[i]->f(leds, i); + Serial.println(); + } + else Serial.println(); } - FastLED.show(); } -//void LedString::doSetup(String ledPattern) { +//void LedString::setup(String ledPattern) { // leds = (CRGB*)malloc(_length * sizeof(CRGB)); // FastLED.addLeds(leds, _length); // //FastLED.addLeds(leds, _length); -// doSetup(ledPattern, leds); -// doStart(); +// setup(ledPattern, leds); //} -void LedString::doSetup(String _pattern, CRGB* ledArray) { +void LedString::setup(CRGB* ledArray) { leds = ledArray; - _length = FastLED.size(); - setupStandardHandlers(); - setCycleSetup(dummyCycleSetup); - setPattern(_pattern); + turnAllOff(); + addStandardHandlers(); } -void LedString::doStart() { - parsePattern(pattern); - setupSwitches(); - turnAllOff(); - doCycle(); +void LedString::begin(String _pattern) { + Serial.print("Calling setPattern for "); + Serial.println(_pattern); + setPattern(_pattern); + Serial.println("pattern set"); + countSwitches(); + Serial.println("setup Done"); } -void LedString::doLoop() { - if (isEventTime()) { - doCycle(); - } +void LedString::loop() { + _time = millis(); + enableHandlers(); + doBehaviors(); + FastLED.show(); + saveEventTime(); } \ No newline at end of file diff --git a/LedString.h b/LedString.h index cb990f2..45233f0 100644 --- a/LedString.h +++ b/LedString.h @@ -5,10 +5,23 @@ #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 -typedef void(*LedStringHandler)(CRGB*, int); -typedef void(*LedStringCycleSetup)(); +typedef void(*LedStringHandler)(CRGB*, int); // implements behavior on one led +typedef void(*LedStringCycleStart)(); // handler init code to run at the start of every doCycle() #define MAX_LED_STRING_HANDLERS 20 + +// a handler is a labeled function that operates on a led, with an optional timer interval; +// handlers are assigned to leds by label chars in the pattern string. +class Handler { + public: + char label; // 1-char label to use in patterns + LedStringHandler f; // handler function + uint32_t interval = 0; // milliseconds between events + uint32_t whenLast = 0; // time of last event + bool enabled = false; // true if it's time to do this behavior + void init(char label, LedStringHandler f, uint32_t interval); +}; + class LedString { public: @@ -29,14 +42,16 @@ class LedString static const uint32_t AVERAGE_SWITCH_INTERVAL = 20000; // desired average ms between toggling a random switched led static const uint32_t MIN_SWITCH_INTERVAL = 5000; // shortest time between toggling - //void doSetup(String ledPattern); - void doSetup(String pattern, CRGB* ledArray); - void doStart(); - void doLoop(); + //void setup(String ledPattern); + void setup(CRGB* ledArray); + void begin(String pattern); + void loop(); uint32_t currentTime(); - uint32_t lastEventTime(); - void setHandler(char, LedStringHandler); + uint32_t previousTime(); +// void addHandler(char label, LedStringHandler, LedStringCycleStart, uint32_t interval); + void addHandler(char label, LedStringHandler, uint32_t interval); void setPattern(String st); + void countSwitches(); bool isOn(int led); void turnOn(int led); @@ -45,33 +60,17 @@ class LedString 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 - + 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 addStandardHandlers(); void addBehavior(char label); - void parsePattern(String pattern); - void turnLitOn(); - void turnOnSteadies(); - bool isEventTime(); - bool isEventTime(uint32_t &previousTime); - void setupSwitches(); + void populateBehaviors(); + bool isEventTime(uint32_t interval, uint32_t &previousTime); + void enableHandlers(); + void saveEventTime(); + void doBehaviors(); void doCycle(); }; diff --git a/examples/Custom/Custom.ino b/examples/Custom/Custom.ino index 7d206b6..fc78c3a 100644 --- a/examples/Custom/Custom.ino +++ b/examples/Custom/Custom.ino @@ -5,45 +5,49 @@ LedString myLedString; CRGB leds[5]; -void sequence(CRGB* leds, int ledNumber, CRGB colors[], int count, uint32_t interval) { +void sequence(CRGB* leds, int ledNumber, CRGB colors[], int count) { // step through an array of colors at intervals; // Limitation: each color must occur only once or it will get stuck alternating between // the first occurrence of that color and the next color. - static uint32_t lastChange = 0; - uint32_t timeNow = myLedString.currentTime(); - if (lastChange + interval < timeNow || timeNow < lastChange) { - for (int i=0; i(leds, NUM_LEDS); - myLedString.doSetup("12rgb", leds); - myLedString.setHandler('2', &blink2); - myLedString.setHandler('1', &blink1); - myLedString.doStart(); + myLedString.setup(leds); + + Serial.println("Adding custom handlers"); + myLedString.addHandler('2', &blink2, 3000); + myLedString.addHandler('1', &blink1, 2000); + Serial.println("Calling begin"); + myLedString.begin("21RGB"); } void loop() { - myLedString.doLoop(); + myLedString.loop(); } From 6a40427eb5034b12f313fd1392d88b60ac7289ed Mon Sep 17 00:00:00 2001 From: Doug Leary Date: Sat, 9 Jan 2021 14:58:07 -0800 Subject: [PATCH 07/16] incremental --- LedString.cpp | 84 +++++++++++++++++++++++++++------------------------ LedString.h | 25 +++++++-------- 2 files changed, 55 insertions(+), 54 deletions(-) diff --git a/LedString.cpp b/LedString.cpp index 72dd340..d77907b 100644 --- a/LedString.cpp +++ b/LedString.cpp @@ -26,38 +26,44 @@ uint32_t LedString::previousTime() { } // array of defined handlers the system knows about -Handler* handlers[MAX_LED_STRING_HANDLERS]; +LedHandler* handlers[MAX_LED_STRING_HANDLERS]; int handler_count = 0; // behaviors is an array of handlers, one for each led; -Handler* behaviors[MAX_LEDS]; +LedHandler* behaviors[MAX_LEDS]; int behavior_count = 0; -void Handler::init(char label, LedStringHandler f, uint32_t interval) { +void LedHandler::init(char label, LedStart start, LedLoop loop, uint32_t interval) { this->label = label; - this->f = f; + this->start = start; + this->loop = loop; this->interval = interval; this->whenLast = 0; this->enabled = false; } -void LedString::addHandler(char label, LedStringHandler f, uint32_t interval) { - // instantiate a handler on the heap - Handler* h = new Handler(); - h->init(label, f, interval); - +void LedString::addHandler(LedHandler* h) { // if exists replace for (int i=0; ilabel == label) { + if (handlers[i]->label == h->label) { + Serial.print("Replacing handler "); Serial.println(h->label); handlers[i] = h; + return; } } // not found so append - Serial.print("Adding handler "); Serial.println(label); + Serial.print("Adding handler "); Serial.println(h->label); handlers[handler_count] = h; handler_count++; } +void LedString::addHandler(char label, LedStart start, LedLoop loop, uint32_t interval) { + // instantiate a handler on the heap + LedHandler* h = new LedHandler(); + h->init(label, start, loop, interval); + addHandler(h); +} + void LedString::setLed(int i, CRGB::HTMLColorCode color) { leds[i] = color; } @@ -190,15 +196,15 @@ void checkSwitch(CRGB* leds, int i) { } } -void LedString::addStandardHandlers() { - addHandler('O', &setBlack, 0); // also used as the dummy handler for unknown pattern chars - addHandler('R', &setRed, 0); - addHandler('G', &setGreen, 0); - addHandler('B', &setBlue, 0); - addHandler('Y', &setYellow, 0); - addHandler('W', &setWhite, 0); - addHandler('F', &flicker, FLICKER_RATE); - addHandler('S', &checkSwitch, 0); +void LedString::addBuiltInHandlers() { + addHandler('O', 0, &setBlack, 0); // also used as the dummy handler for unknown pattern chars + addHandler('R', 0, &setRed, 0); + addHandler('G', 0, &setGreen, 0); + addHandler('B', 0, &setBlue, 0); + addHandler('Y', 0, &setYellow, 0); + addHandler('W', 0, &setWhite, 0); + addHandler('F', 0, &flicker, FLICKER_RATE); + addHandler('S', 0, &checkSwitch, 0); Serial.print("Standard Handlers Added: "); Serial.println(handler_count); char ch = handlers[handler_count - 1]->label; @@ -252,35 +258,34 @@ void LedString::setPattern(String newPattern) { } void LedString::enableHandlers() { - // enable whichever handlers should execute based on timing + // based on current time and interval, enable handlers that should execute and run their cycle start functions Serial.println("===== enableHandlers"); for (int i=0; i < handler_count; i++) { - handlers[i]->enabled = (isEventTime(handlers[i]->interval, handlers[i]->whenLast)); + LedHandler* h = handlers[i]; + h->enabled = (isEventTime(h->interval, h->whenLast)); Serial.print(i); - if (handlers[i]->enabled) { Serial.print(" "); Serial.print(handlers[i]->label); Serial.println(" enabled"); } - else { Serial.println(" disabled"); } - } -} - -void LedString::saveEventTime() { - Serial.println("===== saveEventTime"); - for (int i=0; i < handler_count; i++) { - if (handlers[i]->enabled) { - handlers[i]->whenLast = _time; - } + if (h->enabled) { + Serial.print(" "); Serial.print(h->label); Serial.println(" enabled"); + h->whenLast = _time; + if (h->start) { + h->start; + } + } else { Serial.println(" disabled"); } } } void LedString::doBehaviors() { Serial.println("===== doBehaviors"); for (int i = 0; i < behavior_count; i++) { - if (behaviors[i]->enabled) { + LedHandler* h = behaviors[i]; + if (h->enabled) { Serial.print("Doing "); - Serial.print(behaviors[i]->label); - behaviors[i]->f(leds, i); + Serial.print(h->label); + if (h->loop) { h->loop(leds, i); } Serial.println(); - } - else Serial.println(); + } else { + Serial.print(h->label); Serial.println(" not enabled"); + } } } @@ -294,7 +299,7 @@ void LedString::doBehaviors() { void LedString::setup(CRGB* ledArray) { leds = ledArray; turnAllOff(); - addStandardHandlers(); + addBuiltInHandlers(); } void LedString::begin(String _pattern) { @@ -311,5 +316,4 @@ void LedString::loop() { enableHandlers(); doBehaviors(); FastLED.show(); - saveEventTime(); } \ No newline at end of file diff --git a/LedString.h b/LedString.h index 45233f0..f7a29ae 100644 --- a/LedString.h +++ b/LedString.h @@ -5,21 +5,20 @@ #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 -typedef void(*LedStringHandler)(CRGB*, int); // implements behavior on one led -typedef void(*LedStringCycleStart)(); // handler init code to run at the start of every doCycle() +typedef void (*LedStart) (); // run at the start of every cycle through leds +typedef void (*LedLoop) (CRGB*, int); // update one led #define MAX_LED_STRING_HANDLERS 20 -// a handler is a labeled function that operates on a led, with an optional timer interval; -// handlers are assigned to leds by label chars in the pattern string. -class Handler { +class LedHandler { // implements led behaviors public: char label; // 1-char label to use in patterns - LedStringHandler f; // handler function - uint32_t interval = 0; // milliseconds between events + LedStart start; // setup for cycling through all leds + LedLoop loop; // individual led behavior + uint32_t interval = 0; // milliseconds between events; if 0 do once only uint32_t whenLast = 0; // time of last event - bool enabled = false; // true if it's time to do this behavior - void init(char label, LedStringHandler f, uint32_t interval); + bool enabled = false; // true if the handler should execute during this cycle + void init(char label, LedStart s, LedLoop loop, uint32_t interval); }; class LedString @@ -42,14 +41,13 @@ class LedString static const uint32_t AVERAGE_SWITCH_INTERVAL = 20000; // desired average ms between toggling a random switched led static const uint32_t MIN_SWITCH_INTERVAL = 5000; // shortest time between toggling - //void setup(String ledPattern); void setup(CRGB* ledArray); void begin(String pattern); void loop(); uint32_t currentTime(); uint32_t previousTime(); -// void addHandler(char label, LedStringHandler, LedStringCycleStart, uint32_t interval); - void addHandler(char label, LedStringHandler, uint32_t interval); + void addHandler(LedHandler* h); + void addHandler(char label, LedStart start, LedLoop loop, uint32_t interval); void setPattern(String st); void countSwitches(); @@ -64,12 +62,11 @@ class LedString private: int _length; - void addStandardHandlers(); + void addBuiltInHandlers(); void addBehavior(char label); void populateBehaviors(); bool isEventTime(uint32_t interval, uint32_t &previousTime); void enableHandlers(); - void saveEventTime(); void doBehaviors(); void doCycle(); }; From b2ffa50ce2c0386118edcf3facacb0dd5ade2eb1 Mon Sep 17 00:00:00 2001 From: Doug Leary Date: Sun, 10 Jan 2021 03:27:37 -0800 Subject: [PATCH 08/16] Handlers are objects now --- LedString.cpp | 73 ++++++++++++++++++++++------------ LedString.h | 28 +++++++------ examples/Sequence/Sequence.ino | 62 +++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+), 38 deletions(-) create mode 100644 examples/Sequence/Sequence.ino diff --git a/LedString.cpp b/LedString.cpp index d77907b..f006924 100644 --- a/LedString.cpp +++ b/LedString.cpp @@ -14,6 +14,41 @@ Tested LED devices: #include "FastLED.h" #include "LedString.h" +//////////// LedHandler + +LedHandler::LedHandler(char label, uint32_t interval) +{ + this->label = label; + this->interval = interval; +} + +void LedHandler::start() { + // if handler is enabled, do before cycling through leds +} + +void LedHandler::loop(CRGB* leds, int i) { + // do for each led with this label + Serial.println(" base class handler"); +} + +/////////////// SimpleHandler + +SimpleHandler::SimpleHandler(char label, uint32_t interval, CRGB::HTMLColorCode color) + : LedHandler(label, interval) +{ + this->color = color; +} + +void SimpleHandler::loop(CRGB* leds, int i) { + Serial.print("Setting led "); + Serial.print(i); + Serial.print(" to color "); + Serial.println(color); + leds[i] = color; +} + +/////////////// LedString + uint32_t _time = 0; uint32_t _previousTime = 0; @@ -33,15 +68,6 @@ int handler_count = 0; LedHandler* behaviors[MAX_LEDS]; int behavior_count = 0; -void LedHandler::init(char label, LedStart start, LedLoop loop, uint32_t interval) { - this->label = label; - this->start = start; - this->loop = loop; - this->interval = interval; - this->whenLast = 0; - this->enabled = false; -} - void LedString::addHandler(LedHandler* h) { // if exists replace for (int i=0; iinit(label, start, loop, interval); +void LedString::addSimpleHandler(char label, uint32_t interval, CRGB::HTMLColorCode color) +{ + SimpleHandler* h = new SimpleHandler(label, interval, color); addHandler(h); } @@ -197,14 +222,14 @@ void checkSwitch(CRGB* leds, int i) { } void LedString::addBuiltInHandlers() { - addHandler('O', 0, &setBlack, 0); // also used as the dummy handler for unknown pattern chars - addHandler('R', 0, &setRed, 0); - addHandler('G', 0, &setGreen, 0); - addHandler('B', 0, &setBlue, 0); - addHandler('Y', 0, &setYellow, 0); - addHandler('W', 0, &setWhite, 0); - addHandler('F', 0, &flicker, FLICKER_RATE); - addHandler('S', 0, &checkSwitch, 0); + 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); +// addHandler('F', 0, &flicker, FLICKER_RATE); +// addHandler('S', 0, &checkSwitch, 0); Serial.print("Standard Handlers Added: "); Serial.println(handler_count); char ch = handlers[handler_count - 1]->label; @@ -267,9 +292,7 @@ void LedString::enableHandlers() { if (h->enabled) { Serial.print(" "); Serial.print(h->label); Serial.println(" enabled"); h->whenLast = _time; - if (h->start) { - h->start; - } + h->start(); } else { Serial.println(" disabled"); } } } @@ -281,7 +304,7 @@ void LedString::doBehaviors() { if (h->enabled) { Serial.print("Doing "); Serial.print(h->label); - if (h->loop) { h->loop(leds, i); } + h->loop(leds, i); Serial.println(); } else { Serial.print(h->label); Serial.println(" not enabled"); diff --git a/LedString.h b/LedString.h index f7a29ae..4371c4a 100644 --- a/LedString.h +++ b/LedString.h @@ -4,21 +4,23 @@ #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 - -typedef void (*LedStart) (); // run at the start of every cycle through leds -typedef void (*LedLoop) (CRGB*, int); // update one led - #define MAX_LED_STRING_HANDLERS 20 - class LedHandler { // implements led behaviors public: - char label; // 1-char label to use in patterns - LedStart start; // setup for cycling through all leds - LedLoop loop; // individual led behavior - uint32_t interval = 0; // milliseconds between events; if 0 do once only - uint32_t whenLast = 0; // time of last event - bool enabled = false; // true if the handler should execute during this cycle - void init(char label, LedStart s, LedLoop loop, uint32_t interval); + 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 start(); // executed before cycling through the leds + virtual void loop(CRGB *leds, int i); // executed on each led with this label +}; + +class SimpleHandler : public LedHandler { + public: + CRGB::HTMLColorCode color; + SimpleHandler(char label, uint32_t interval, CRGB::HTMLColorCode color); + virtual void loop(CRGB *leds, int i); }; class LedString @@ -47,7 +49,7 @@ class LedString uint32_t currentTime(); uint32_t previousTime(); void addHandler(LedHandler* h); - void addHandler(char label, LedStart start, LedLoop loop, uint32_t interval); + void addSimpleHandler(char label, uint32_t interval, CRGB::HTMLColorCode color); void setPattern(String st); void countSwitches(); diff --git a/examples/Sequence/Sequence.ino b/examples/Sequence/Sequence.ino new file mode 100644 index 0000000..de0bd40 --- /dev/null +++ b/examples/Sequence/Sequence.ino @@ -0,0 +1,62 @@ +#include +#define DATA_PIN 2 +#define NUM_LEDS 3 + +LedString myLedString; +CRGB leds[NUM_LEDS]; + +int loopLimit = 600; +int loopCount = 0; + + +class ColorSequence : public LedHandler { + private: + CRGB::HTMLColorCode* colors; + int colorCount; + int nextColor; + public: + ColorSequence (char label, uint32_t interval, CRGB::HTMLColorCode colors[], int count) : LedHandler(label, interval) { + this->colors = colors; + this->colorCount = count; + this->nextColor = count; + } + void start() { + if (this->enabled) { + this->nextColor++; + if (this->nextColor >= this->colorCount) { + this->nextColor = 0; + } + } + } + void loop(CRGB* leds, int ledNumber) { + loopCount++; + leds[ledNumber] = this->colors[this->nextColor]; + } +}; + +CRGB::HTMLColorCode colors1[] = { CRGB::Blue, CRGB::Black, CRGB::Green, CRGB::Black }; +CRGB::HTMLColorCode colors2[] = { CRGB::Red, CRGB::Green, CRGB::Blue }; + +void setup() { + Serial.begin(115200); while (!Serial) { ; } + Serial.println("\n*****"); + + FastLED.clear(); + FastLED.addLeds(leds, NUM_LEDS); + myLedString.setup(leds); + + Serial.println("Adding custom handlers"); + ColorSequence *cs1 = new ColorSequence('1', 500, colors1, 4); + myLedString.addHandler(cs1); + ColorSequence *cs2 = new ColorSequence('2', 500, colors2, 3); + myLedString.addHandler(cs2); + + Serial.println("Calling begin"); + myLedString.begin("1GB"); +} + +void loop() { + if (loopCount < loopLimit) { + myLedString.loop(); + } +} From c62d2962263905df6417c371e54c7b8e59113863 Mon Sep 17 00:00:00 2001 From: Doug Leary Date: Mon, 11 Jan 2021 01:04:41 -0800 Subject: [PATCH 09/16] SwitchGroup example, add handler setup() --- LedString.cpp | 109 +++++++++------------------ LedString.h | 2 + examples/SwitchGroup/SwitchGroup.ino | 100 ++++++++++++++++++++++++ 3 files changed, 137 insertions(+), 74 deletions(-) create mode 100644 examples/SwitchGroup/SwitchGroup.ino diff --git a/LedString.cpp b/LedString.cpp index f006924..7516d4a 100644 --- a/LedString.cpp +++ b/LedString.cpp @@ -22,13 +22,16 @@ LedHandler::LedHandler(char label, uint32_t interval) this->interval = interval; } +void LedHandler::setup() { + // do when a pattern is created or changed +} + void LedHandler::start() { // if handler is enabled, do before cycling through leds } void LedHandler::loop(CRGB* leds, int i) { // do for each led with this label - Serial.println(" base class handler"); } /////////////// SimpleHandler @@ -93,37 +96,6 @@ void LedString::setLed(int i, CRGB::HTMLColorCode color) { leds[i] = color; } -////// standard handlers -void setRed(CRGB* leds, int i) { - Serial.print(" Red"); - leds[i] = CRGB::Red; -} - -void setGreen(CRGB* leds, int i) { - Serial.print(" Green"); - leds[i] = CRGB::Green; -} - -void setBlue(CRGB* leds, int i) { - Serial.print(" Blue"); - leds[i] = CRGB::Blue; -} - -void setYellow(CRGB* leds, int i) { - Serial.print(" Yellow"); - leds[i] = CRGB::Yellow; -} - -void setWhite(CRGB* leds, int i) { - Serial.print(" White"); - leds[i] = CRGB::White; -} - -void setBlack(CRGB* leds, int i) { - Serial.print(" Black"); - leds[i] = CRGB::Black; -} - void flicker(CRGB* leds, int led) { int value = random(LedString::FIRE_MIN, LedString::FIRE_MAX); // occasional intense flicker @@ -136,9 +108,6 @@ void flicker(CRGB* leds, int led) { leds[led] = CHSV(25, 187, value); } - -////// - void LedString::resetAll() { FastLED.clear(); } @@ -150,23 +119,23 @@ bool LedString::isOn(int led) { (leds[led].blue == 255)); } -void LedString::turnOn(int led) { - setWhite(leds, led); +void LedString::turnOn(int i) { + leds[i] = CRGB::White; } -void LedString::turnOff(int led) { - setBlack(leds, led); +void LedString::turnOff(int i) { + leds[i] = CRGB::Black; } void LedString::turnAllOn() { for (int i = 0; i < _length; i++) { - setWhite(leds, i); + leds[i] = CRGB::White; } } void LedString::turnAllOff() { for (int i = 0; i < _length; i++) { - setBlack(leds, i); + leds[i] = CRGB::Black; } } @@ -187,22 +156,6 @@ int switchIndexToToggle = 0; uint32_t nextSwitchTime = 0; uint32_t switchInterval = 0; -void LedString::countSwitches() { - Serial.println("Counting switches"); - for (int i = 0; i < _length; i++) { - if (pattern.charAt(i) == 'S') { - numSwitches++; - } - } - // Serial.printf("%n switches\n", numSwitches); - // if (numSwitches > 1) { - // switchIndexToToggle = random(0, numSwitches); - // switchInterval = (max(LedString::AVERAGE_SWITCH_INTERVAL / numSwitches, LedString::MIN_SWITCH_INTERVAL)); - // nextSwitchTime = _time + switchInterval; - // } - Serial.printf("%d Switches\n", numSwitches); -} - void checkSwitch(CRGB* leds, int i) { // skip if it's not time to switch if (_time < nextSwitchTime) return; @@ -241,28 +194,34 @@ void LedString::addBehavior(char label) { // find the handler for the label and append it to behaviors for (int i=0; ilabel == label) { - Serial.print("Using behavior "); - Serial.println(label); + // Serial.print("Using behavior "); + // Serial.println(label); behaviors[behavior_count++] = handlers[i]; return; } } - Serial.print("Behavior "); Serial.print(label); Serial.println(" not found; using default"); + // Serial.print("Behavior "); Serial.print(label); Serial.println(" not found; using default"); behaviors[behavior_count++] = handlers[0]; // use dummy handler } void LedString::populateBehaviors() { // for each character in the pattern add the corresponding handler to the behaviors list behavior_count = 0; - Serial.printf("Populating %d Behaviors for pattern ", _length); - Serial.println(pattern); + // Serial.printf("Populating %d Behaviors for pattern ", _length); + // Serial.println(pattern); for (int i = 0; i < _length; i++) { char label = pattern.charAt(i); addBehavior(label); } - Serial.print(behavior_count); Serial.println(" behaviors added"); + // Serial.print(behavior_count); Serial.println(" behaviors added"); } +void LedString::setupHandlers() { + for (int i = 0; i < handler_count; i++) + { + handlers[i]->setup(); + } +} 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. @@ -280,34 +239,38 @@ void LedString::setPattern(String newPattern) { _length = pattern.length(); Serial.print("pattern: "); Serial.println(pattern); populateBehaviors(); + setupHandlers(); } void LedString::enableHandlers() { // based on current time and interval, enable handlers that should execute and run their cycle start functions - Serial.println("===== enableHandlers"); +// Serial.println("===== enableHandlers"); for (int i=0; i < handler_count; i++) { LedHandler* h = handlers[i]; h->enabled = (isEventTime(h->interval, h->whenLast)); - Serial.print(i); +// Serial.print(i); if (h->enabled) { - Serial.print(" "); Serial.print(h->label); Serial.println(" enabled"); +// Serial.print(" "); Serial.print(h->label); Serial.println(" enabled"); h->whenLast = _time; h->start(); - } else { Serial.println(" disabled"); } + } else + { +// Serial.println(" disabled"); + } } } void LedString::doBehaviors() { - Serial.println("===== doBehaviors"); +// Serial.println("===== doBehaviors"); for (int i = 0; i < behavior_count; i++) { LedHandler* h = behaviors[i]; if (h->enabled) { - Serial.print("Doing "); - Serial.print(h->label); +// Serial.print("Doing "); +// Serial.print(h->label); h->loop(leds, i); - Serial.println(); +// Serial.println(); } else { - Serial.print(h->label); Serial.println(" not enabled"); +// Serial.print(h->label); Serial.println(" not enabled"); } } } @@ -330,8 +293,6 @@ void LedString::begin(String _pattern) { Serial.println(_pattern); setPattern(_pattern); Serial.println("pattern set"); - countSwitches(); - Serial.println("setup Done"); } void LedString::loop() { diff --git a/LedString.h b/LedString.h index 4371c4a..66a132b 100644 --- a/LedString.h +++ b/LedString.h @@ -12,6 +12,7 @@ class LedHandler { // implements led behaviors 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(); // executed when a pattern is created or changed virtual void start(); // executed before cycling through the leds virtual void loop(CRGB *leds, int i); // executed on each led with this label }; @@ -68,6 +69,7 @@ class LedString void addBehavior(char label); void populateBehaviors(); bool isEventTime(uint32_t interval, uint32_t &previousTime); + void setupHandlers(); void enableHandlers(); void doBehaviors(); void doCycle(); diff --git a/examples/SwitchGroup/SwitchGroup.ino b/examples/SwitchGroup/SwitchGroup.ino new file mode 100644 index 0000000..b09a466 --- /dev/null +++ b/examples/SwitchGroup/SwitchGroup.ino @@ -0,0 +1,100 @@ +/* + SwitchGroup + + Demonstrates a LedString Handler subclass that toggles a random led in a group + on and off at random times, to simulate habitation in a model building or town. + For each group you create a new instance of SwitchGroup, assign it a label for + the pattern string, an ON color for the led, and min/max interval times. + + The handler needs only setup() and loop(). In setup() we count the leds that have + its label and pick a random interval and random led. In loop() we decrement a + countdown; when it reaches 0 we toggle the current led, disable the handler so + only this led is toggled, then pick a new random interval and led. +*/ + +#include +#define DATA_PIN 2 +#define NUM_LEDS 8 + +LedString myLedString; +CRGB leds[NUM_LEDS]; + +void sayColor(String msg, CRGB color) { + Serial.print(" " + msg + ": "); Serial.print(color.red); + Serial.print(", "); Serial.print(color.green); + Serial.print(", "); Serial.println(color.blue); +} + +class SwitchGroup : public LedHandler { + private: + int switchCount; + int countdown; + CRGB color; + uint32_t minInterval; + uint32_t maxInterval; + public: + SwitchGroup(char label, uint32_t minInterval, CRGB color, uint32_t maxInterval) : LedHandler(label, 0) { + this->interval = 0; + this->minInterval = interval; + this->maxInterval = maxInterval; + this->color = color; + sayColor("color", color); + sayColor("this->color", this->color); + } + + void setup() { + // count the switched leds in this group + switchCount = 0; + countdown = 0; + int len = myLedString.pattern.length(); + for (int i = 0; i < len; i++) { + if (myLedString.pattern.charAt(i) == this->label) { + switchCount++; + } + } + } + + void loop(CRGB* leds, int ledNumber) { + // toggle a led when the countdown reaches 0 + if (this->countdown > 0) { + countdown--; + } else { + if (leds[ledNumber] == color) { + Serial.print(" led "); Serial.print(ledNumber); Serial.println(" black"); + leds[ledNumber] = CRGB::Black; + } else { + Serial.print(" led "); Serial.print(ledNumber); + sayColor(" toggle", this->color); + leds[ledNumber] = this->color; + } + // set random time and led for next toggle + this->enabled = false; // once we've toggled, disable until the next cycle + this->countdown = random(switchCount); + this->interval = random(minInterval, maxInterval); + Serial.print("New countdown "); Serial.println(this->countdown); + } + } +}; + +void setup() { + Serial.begin(115200); while (!Serial) { ; } + Serial.println("\n*****"); + + FastLED.clear(); + FastLED.addLeds(leds, NUM_LEDS); + myLedString.setup(leds); + + Serial.println("Adding custom handlers"); + SwitchGroup *sg1 = new SwitchGroup('1', 1000, CRGB::Green, 8000); + myLedString.addHandler(sg1); + SwitchGroup *sg2 = new SwitchGroup('2', 1000, CRGB::White, 4000); + myLedString.addHandler(sg2); + + Serial.println("Calling begin"); + myLedString.begin("11111222"); +} + +void loop() { + myLedString.loop(); +} + From 19f99ce05f34d8a14abfa9f8ffe9a8a7510e3597 Mon Sep 17 00:00:00 2001 From: Doug Leary Date: Wed, 13 Jan 2021 21:28:02 -0800 Subject: [PATCH 10/16] More cleanup, add built-ins --- LedString.cpp | 159 ++++++++++++++++----------- LedString.h | 38 +++++-- examples/Sequence/Sequence.ino | 28 ++--- examples/SwitchGroup/SwitchGroup.ino | 74 ++----------- 4 files changed, 145 insertions(+), 154 deletions(-) diff --git a/LedString.cpp b/LedString.cpp index 7516d4a..2500dd6 100644 --- a/LedString.cpp +++ b/LedString.cpp @@ -22,32 +22,103 @@ LedHandler::LedHandler(char label, uint32_t interval) this->interval = interval; } -void LedHandler::setup() { +void LedHandler::setup(LedString ls) +{ // do when a pattern is created or changed } -void LedHandler::start() { +void LedHandler::start(LedString ls) +{ // if handler is enabled, do before cycling through leds } -void LedHandler::loop(CRGB* leds, int i) { +void LedHandler::loop(LedString ls, int i) +{ // do for each led with this label } /////////////// SimpleHandler -SimpleHandler::SimpleHandler(char label, uint32_t interval, CRGB::HTMLColorCode color) - : LedHandler(label, interval) +SimpleHandler::SimpleHandler(char label, uint32_t interval, CRGB color) + : LedHandler(label, interval) { this->color = color; } -void SimpleHandler::loop(CRGB* leds, int i) { - Serial.print("Setting led "); - Serial.print(i); - Serial.print(" to color "); - Serial.println(color); - leds[i] = color; +void SimpleHandler::loop(LedString ls, int i) +{ + ls.leds[i] = color; +} + +/////////////// 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) + { + value = LedString::FIRE_MIN - ((LedString::FIRE_MIN - LedString::FLICKER_MIN) / (value - LedString::FIRE_MIN + 1)); + } + else if (value >= LedString::FIRE_MAX - LedString::FLICKER_EXTRA) + { + value = LedString::FIRE_MAX + ((LedString::FLICKER_MAX - LedString::FIRE_MAX) / (LedString::FIRE_MAX - value)); + } + ls.leds[i] = CHSV(25, 187, value); +}; + +/////////////// SwitchGroup + +SwitchGroup::SwitchGroup(char label, uint32_t minInterval, uint32_t maxInterval, CRGB color) + : LedHandler(label, 0) +{ + this->minInterval = minInterval; + this->maxInterval = maxInterval; + this->color = color; +} + +void SwitchGroup::setup(LedString ls) +{ + // count the switched leds in this group + switchCount = 0; + countdown = random(switchCount); + int len = ls.pattern.length(); + for (int i = 0; i < len; i++) + { + if (ls.pattern.charAt(i) == this->label) + { + switchCount++; + ls.leds[i] = color; + } + } +} + +void SwitchGroup::loop(LedString ls, int ledNumber) +{ + // toggle a led when the countdown reaches 0 + if (countdown > 0) + { + countdown--; + } + else + { + if (ls.leds[ledNumber] == color) + { + ls.leds[ledNumber] = CRGB::Black; + } + else + { + ls.leds[ledNumber] = color; + } + // set random time and led for next toggle + enabled = false; // once we've toggled, disable until the next cycle + countdown = random(switchCount); + interval = random(minInterval, maxInterval); + } } /////////////// LedString @@ -75,39 +146,25 @@ void LedString::addHandler(LedHandler* h) { // if exists replace for (int i=0; ilabel == h->label) { - Serial.print("Replacing handler "); Serial.println(h->label); handlers[i] = h; return; } } // not found so append - Serial.print("Adding handler "); Serial.println(h->label); handlers[handler_count] = h; handler_count++; } -void LedString::addSimpleHandler(char label, uint32_t interval, CRGB::HTMLColorCode color) +void LedString::addSimpleHandler(char label, uint32_t interval, CRGB color) { SimpleHandler* h = new SimpleHandler(label, interval, color); addHandler(h); } -void LedString::setLed(int i, CRGB::HTMLColorCode color) { +void LedString::setLed(int i, CRGB color) { leds[i] = color; } -void flicker(CRGB* leds, int led) { - int value = random(LedString::FIRE_MIN, LedString::FIRE_MAX); - // occasional intense flicker - 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) { - value = LedString::FIRE_MAX + ((LedString::FLICKER_MAX - LedString::FIRE_MAX) / (LedString::FIRE_MAX - value)); - } - leds[led] = CHSV(25, 187, value); -} - void LedString::resetAll() { FastLED.clear(); } @@ -181,53 +238,34 @@ void LedString::addBuiltInHandlers() { addSimpleHandler('B', 0, CRGB::Blue); addSimpleHandler('Y', 0, CRGB::Yellow); addSimpleHandler('W', 0, CRGB::White); -// addHandler('F', 0, &flicker, FLICKER_RATE); -// addHandler('S', 0, &checkSwitch, 0); - Serial.print("Standard Handlers Added: "); - Serial.println(handler_count); - char ch = handlers[handler_count - 1]->label; - Serial.printf("Last one is %c \n",ch); - + FlameHandler *fire = new FlameHandler('F', FLICKER_RATE); + addHandler(fire); } void LedString::addBehavior(char label) { // find the handler for the label and append it to behaviors for (int i=0; ilabel == label) { - // Serial.print("Using behavior "); - // Serial.println(label); behaviors[behavior_count++] = handlers[i]; return; } } - // Serial.print("Behavior "); Serial.print(label); Serial.println(" not found; using default"); behaviors[behavior_count++] = handlers[0]; // use dummy handler } void LedString::populateBehaviors() { // for each character in the pattern add the corresponding handler to the behaviors list behavior_count = 0; - // Serial.printf("Populating %d Behaviors for pattern ", _length); - // Serial.println(pattern); for (int i = 0; i < _length; i++) { char label = pattern.charAt(i); addBehavior(label); } - // Serial.print(behavior_count); Serial.println(" behaviors added"); } -void LedString::setupHandlers() { - for (int i = 0; i < handler_count; i++) - { - handlers[i]->setup(); - } -} 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. - Serial.print("newPattern: "); Serial.println(newPattern); String st = newPattern; - Serial.print("st: "); Serial.println(st); // st.replace(" ", ""); // st.remove(_length); // st.toUpperCase(); @@ -237,40 +275,30 @@ void LedString::setPattern(String newPattern) { // } pattern = st; _length = pattern.length(); - Serial.print("pattern: "); Serial.println(pattern); populateBehaviors(); - setupHandlers(); + for (int i = 0; i < handler_count; i++) + { + handlers[i]->setup(*this); + } } void LedString::enableHandlers() { // based on current time and interval, enable handlers that should execute and run their cycle start functions -// Serial.println("===== enableHandlers"); for (int i=0; i < handler_count; i++) { LedHandler* h = handlers[i]; h->enabled = (isEventTime(h->interval, h->whenLast)); -// Serial.print(i); if (h->enabled) { -// Serial.print(" "); Serial.print(h->label); Serial.println(" enabled"); h->whenLast = _time; - h->start(); - } else - { -// Serial.println(" disabled"); + h->start(*this); } } } void LedString::doBehaviors() { -// Serial.println("===== doBehaviors"); for (int i = 0; i < behavior_count; i++) { LedHandler* h = behaviors[i]; if (h->enabled) { -// Serial.print("Doing "); -// Serial.print(h->label); - h->loop(leds, i); -// Serial.println(); - } else { -// Serial.print(h->label); Serial.println(" not enabled"); + h->loop(*this, i); } } } @@ -289,10 +317,7 @@ void LedString::setup(CRGB* ledArray) { } void LedString::begin(String _pattern) { - Serial.print("Calling setPattern for "); - Serial.println(_pattern); setPattern(_pattern); - Serial.println("pattern set"); } void LedString::loop() { diff --git a/LedString.h b/LedString.h index 66a132b..907e4a5 100644 --- a/LedString.h +++ b/LedString.h @@ -5,6 +5,9 @@ #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 + +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 @@ -12,16 +15,35 @@ class LedHandler { // implements led behaviors 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(); // executed when a pattern is created or changed - virtual void start(); // executed before cycling through the leds - virtual void loop(CRGB *leds, int i); // executed on each led with this label + 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::HTMLColorCode color; - SimpleHandler(char label, uint32_t interval, CRGB::HTMLColorCode color); - virtual void loop(CRGB *leds, int i); + 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 SwitchGroup : public LedHandler { + private: + int switchCount; + int countdown; + CRGB color; + uint32_t minInterval; + uint32_t maxInterval; + public: + SwitchGroup(char label, uint32_t minInterval, uint32_t maxInterval, CRGB color); + virtual void setup(LedString ls); + virtual void loop(LedString ls, int i); }; class LedString @@ -50,7 +72,7 @@ class LedString uint32_t currentTime(); uint32_t previousTime(); void addHandler(LedHandler* h); - void addSimpleHandler(char label, uint32_t interval, CRGB::HTMLColorCode color); + void addSimpleHandler(char label, uint32_t interval, CRGB color); void setPattern(String st); void countSwitches(); @@ -60,7 +82,7 @@ class LedString void turnAllOn(); void turnAllOff(); void resetAll(); - void setLed(int i, CRGB::HTMLColorCode color); + void setLed(int i, CRGB color); private: int _length; diff --git a/examples/Sequence/Sequence.ino b/examples/Sequence/Sequence.ino index de0bd40..bb1575b 100644 --- a/examples/Sequence/Sequence.ino +++ b/examples/Sequence/Sequence.ino @@ -1,26 +1,22 @@ #include #define DATA_PIN 2 -#define NUM_LEDS 3 +#define NUM_LEDS 8 LedString myLedString; CRGB leds[NUM_LEDS]; -int loopLimit = 600; -int loopCount = 0; - - class ColorSequence : public LedHandler { private: - CRGB::HTMLColorCode* colors; + CRGB* colors; int colorCount; int nextColor; public: - ColorSequence (char label, uint32_t interval, CRGB::HTMLColorCode colors[], int count) : LedHandler(label, interval) { + ColorSequence (char label, uint32_t interval, CRGB colors[], int count) : LedHandler(label, interval) { this->colors = colors; this->colorCount = count; this->nextColor = count; } - void start() { + void start(LedString ls) { if (this->enabled) { this->nextColor++; if (this->nextColor >= this->colorCount) { @@ -28,14 +24,13 @@ class ColorSequence : public LedHandler { } } } - void loop(CRGB* leds, int ledNumber) { - loopCount++; - leds[ledNumber] = this->colors[this->nextColor]; + void loop(LedString ls, int ledNumber) { + ls.leds[ledNumber] = this->colors[this->nextColor]; } }; -CRGB::HTMLColorCode colors1[] = { CRGB::Blue, CRGB::Black, CRGB::Green, CRGB::Black }; -CRGB::HTMLColorCode colors2[] = { CRGB::Red, CRGB::Green, CRGB::Blue }; +CRGB colors1[] = { CRGB::Red, CRGB::Green, CRGB::Blue, CRGB::Black }; +CRGB colors2[] = { CRGB::Blue, CRGB::White }; void setup() { Serial.begin(115200); while (!Serial) { ; } @@ -48,15 +43,14 @@ void setup() { Serial.println("Adding custom handlers"); ColorSequence *cs1 = new ColorSequence('1', 500, colors1, 4); myLedString.addHandler(cs1); - ColorSequence *cs2 = new ColorSequence('2', 500, colors2, 3); + ColorSequence *cs2 = new ColorSequence('2', 500, colors2, 2); myLedString.addHandler(cs2); Serial.println("Calling begin"); - myLedString.begin("1GB"); + myLedString.begin("11112222"); } void loop() { - if (loopCount < loopLimit) { myLedString.loop(); - } } + diff --git a/examples/SwitchGroup/SwitchGroup.ino b/examples/SwitchGroup/SwitchGroup.ino index b09a466..dff5c80 100644 --- a/examples/SwitchGroup/SwitchGroup.ino +++ b/examples/SwitchGroup/SwitchGroup.ino @@ -14,67 +14,16 @@ #include #define DATA_PIN 2 -#define NUM_LEDS 8 +#define NUM_LEDS 10 LedString myLedString; CRGB leds[NUM_LEDS]; -void sayColor(String msg, CRGB color) { - Serial.print(" " + msg + ": "); Serial.print(color.red); - Serial.print(", "); Serial.print(color.green); - Serial.print(", "); Serial.println(color.blue); -} - -class SwitchGroup : public LedHandler { - private: - int switchCount; - int countdown; - CRGB color; - uint32_t minInterval; - uint32_t maxInterval; - public: - SwitchGroup(char label, uint32_t minInterval, CRGB color, uint32_t maxInterval) : LedHandler(label, 0) { - this->interval = 0; - this->minInterval = interval; - this->maxInterval = maxInterval; - this->color = color; - sayColor("color", color); - sayColor("this->color", this->color); - } - - void setup() { - // count the switched leds in this group - switchCount = 0; - countdown = 0; - int len = myLedString.pattern.length(); - for (int i = 0; i < len; i++) { - if (myLedString.pattern.charAt(i) == this->label) { - switchCount++; - } - } - } - - void loop(CRGB* leds, int ledNumber) { - // toggle a led when the countdown reaches 0 - if (this->countdown > 0) { - countdown--; - } else { - if (leds[ledNumber] == color) { - Serial.print(" led "); Serial.print(ledNumber); Serial.println(" black"); - leds[ledNumber] = CRGB::Black; - } else { - Serial.print(" led "); Serial.print(ledNumber); - sayColor(" toggle", this->color); - leds[ledNumber] = this->color; - } - // set random time and led for next toggle - this->enabled = false; // once we've toggled, disable until the next cycle - this->countdown = random(switchCount); - this->interval = random(minInterval, maxInterval); - Serial.print("New countdown "); Serial.println(this->countdown); - } - } -}; +//void sayColor(String msg, CRGB color) { +// Serial.print(" " + msg + ": "); Serial.print(color.red); +// Serial.print(", "); Serial.print(color.green); +// Serial.print(", "); Serial.println(color.blue); +//} void setup() { Serial.begin(115200); while (!Serial) { ; } @@ -85,13 +34,14 @@ void setup() { myLedString.setup(leds); Serial.println("Adding custom handlers"); - SwitchGroup *sg1 = new SwitchGroup('1', 1000, CRGB::Green, 8000); - myLedString.addHandler(sg1); - SwitchGroup *sg2 = new SwitchGroup('2', 1000, CRGB::White, 4000); - myLedString.addHandler(sg2); + SwitchGroup *group1 = new SwitchGroup('1', 1000, 8000, CRGB::Green); + myLedString.addHandler(group1); + SwitchGroup *group2 = new SwitchGroup('2', 200, 200, CRGB::White); + myLedString.addHandler(group2); + Serial.println("Calling begin"); - myLedString.begin("11111222"); + myLedString.begin("1111122222"); } void loop() { From 7a3d3640342b652a8ac1a982c92301d86211238b Mon Sep 17 00:00:00 2001 From: Doug Leary Date: Sun, 17 Jan 2021 00:12:50 -0800 Subject: [PATCH 11/16] Handlers are now subclasses of LedHandler. --- LedString.cpp | 179 ++++++++++----------- LedString.h | 32 ++-- README.md | 20 +-- examples/ActiveGroup/ActiveGroup.ino | 23 +++ examples/Custom/Custom.ino | 53 ------ examples/CustomBehavior/CustomBehavior.ino | 46 ------ examples/Sequence/Sequence.ino | 2 +- examples/SimpleTest/SimpleTest.ino | 15 +- examples/SwitchGroup/SwitchGroup.ino | 50 ------ 9 files changed, 149 insertions(+), 271 deletions(-) create mode 100644 examples/ActiveGroup/ActiveGroup.ino delete mode 100644 examples/Custom/Custom.ino delete mode 100644 examples/CustomBehavior/CustomBehavior.ino delete mode 100644 examples/SwitchGroup/SwitchGroup.ino diff --git a/LedString.cpp b/LedString.cpp index 2500dd6..17211b1 100644 --- a/LedString.cpp +++ b/LedString.cpp @@ -71,53 +71,70 @@ void FlameHandler::loop(LedString ls, int i) ls.leds[i] = CHSV(25, 187, value); }; -/////////////// SwitchGroup +/////////////// ActiveGroup -SwitchGroup::SwitchGroup(char label, uint32_t minInterval, uint32_t maxInterval, CRGB color) - : LedHandler(label, 0) +ActiveGroup::ActiveGroup(char label, uint32_t minInterval, uint32_t maxInterval, CRGB color, int percentOn) + : LedHandler(label, 0) { this->minInterval = minInterval; - this->maxInterval = maxInterval; + this->maxInterval = max(maxInterval, minInterval+1); // in case minInterval = maxInterval this->color = color; + this->percentOn = percentOn; } -void SwitchGroup::setup(LedString ls) -{ - // count the switched leds in this group - switchCount = 0; - countdown = random(switchCount); - int len = ls.pattern.length(); - for (int i = 0; i < len; i++) +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) - { - switchCount++; - ls.leds[i] = color; + 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 SwitchGroup::loop(LedString ls, int ledNumber) -{ - // toggle a led when the countdown reaches 0 - if (countdown > 0) - { - countdown--; +void ActiveGroup::start(LedString ls) { + isTurningOn = (groupCountOn < (ls.ledCount * percentOn / 100)); + + if (isTurningOn) { + countdown = random(0, groupCount - groupCountOn); + } else { + countdown = random(0, groupCountOn); } - else - { - if (ls.leds[ledNumber] == color) +} + +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) { - ls.leds[ledNumber] = CRGB::Black; + countdown--; } else { - ls.leds[ledNumber] = color; + ls.leds[ledNumber] = CRGB::Black; + groupCountOn--; + enabled = false; + interval = random(minInterval, maxInterval); } - // set random time and led for next toggle - enabled = false; // once we've toggled, disable until the next cycle - countdown = random(switchCount); - interval = random(minInterval, maxInterval); + } else { } } @@ -140,7 +157,6 @@ int handler_count = 0; // behaviors is an array of handlers, one for each led; LedHandler* behaviors[MAX_LEDS]; -int behavior_count = 0; void LedString::addHandler(LedHandler* h) { // if exists replace @@ -171,9 +187,9 @@ void LedString::resetAll() { 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 i) { @@ -185,13 +201,13 @@ void LedString::turnOff(int i) { } void LedString::turnAllOn() { - for (int i = 0; i < _length; i++) { + for (int i = 0; i < ledCount; i++) { leds[i] = CRGB::White; } } void LedString::turnAllOff() { - for (int i = 0; i < _length; i++) { + for (int i = 0; i < ledCount; i++) { leds[i] = CRGB::Black; } } @@ -207,30 +223,6 @@ bool LedString::isEventTime(uint32_t interval, uint32_t &previousTime) { } } -int numSwitches = 0; -int switchIndex = 0; -int switchIndexToToggle = 0; -uint32_t nextSwitchTime = 0; -uint32_t switchInterval = 0; - -void checkSwitch(CRGB* leds, int i) { - // skip if it's not time to switch - if (_time < 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; - } - switchIndexToToggle = random(0, numSwitches); - nextSwitchTime = _time + switchInterval; - switchIndex = 0; - } else { - switchIndex++; - } -} - void LedString::addBuiltInHandlers() { addSimpleHandler('O', 0, CRGB::Black); // also used as the dummy handler for unknown pattern chars addSimpleHandler('R', 0, CRGB::Red); @@ -238,50 +230,53 @@ void LedString::addBuiltInHandlers() { addSimpleHandler('B', 0, CRGB::Blue); addSimpleHandler('Y', 0, CRGB::Yellow); addSimpleHandler('W', 0, CRGB::White); - FlameHandler *fire = new FlameHandler('F', FLICKER_RATE); - addHandler(fire); + 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 LedString::addBehavior(char label) { +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[behavior_count++] = handlers[i]; - return; + behaviors[ledNumber] = handlers[i]; + return; } } - behaviors[behavior_count++] = handlers[0]; // use dummy handler + behaviors[ledNumber] = handlers[0]; // use dummy handler } void LedString::populateBehaviors() { // for each character in the pattern add the corresponding handler to the behaviors list - behavior_count = 0; - for (int i = 0; i < _length; i++) { + for (int i = 0; i < ledCount; i++) { char label = pattern.charAt(i); - addBehavior(label); + addBehavior(label, i); } } -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; 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. + pattern = newPattern; + pattern.replace(" ", ""); + pattern.remove(ledCount); + int shortness = ledCount - pattern.length(); + for (int i=0; ienabled) { h->loop(*this, i); @@ -303,21 +299,22 @@ void LedString::doBehaviors() { } } -//void LedString::setup(String ledPattern) { -// leds = (CRGB*)malloc(_length * sizeof(CRGB)); -// FastLED.addLeds(leds, _length); -// //FastLED.addLeds(leds, _length); +//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::setup(CRGB* ledArray) { +void LedString::setup(CRGB *ledArray, int numLeds) { leds = ledArray; + ledCount = numLeds; turnAllOff(); addBuiltInHandlers(); } -void LedString::begin(String _pattern) { - setPattern(_pattern); +void LedString::begin(String newPattern) { + setPattern(newPattern); } void LedString::loop() { diff --git a/LedString.h b/LedString.h index 907e4a5..1e6f56a 100644 --- a/LedString.h +++ b/LedString.h @@ -33,16 +33,21 @@ class FlameHandler : public LedHandler { virtual void loop(LedString ls, int i); }; -class SwitchGroup : public LedHandler { - private: - int switchCount; +class ActiveGroup : public LedHandler { + public: + int groupCount; + int groupCountOn; int countdown; + int percentOn; + bool isTurningOn; CRGB color; uint32_t minInterval; uint32_t maxInterval; - public: - SwitchGroup(char label, uint32_t minInterval, uint32_t maxInterval, CRGB color); + +// 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); }; @@ -53,7 +58,7 @@ class LedString String pattern; // min/max brightness range of normal and intense flickers - static const int FIRE_MIN = 150; + 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 @@ -62,11 +67,13 @@ class LedString 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 - // timing for switched lights - static const uint32_t AVERAGE_SWITCH_INTERVAL = 20000; // desired average ms between toggling a random switched led - static const uint32_t MIN_SWITCH_INTERVAL = 5000; // shortest time between toggling + 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 - void setup(CRGB* ledArray); + int ledCount; + + void setup(CRGB *ledArray, int ledCount); void begin(String pattern); void loop(); uint32_t currentTime(); @@ -74,7 +81,6 @@ class LedString void addHandler(LedHandler* h); void addSimpleHandler(char label, uint32_t interval, CRGB color); void setPattern(String st); - void countSwitches(); bool isOn(int led); void turnOn(int led); @@ -85,10 +91,8 @@ class LedString void setLed(int i, CRGB color); private: - int _length; - void addBuiltInHandlers(); - void addBehavior(char label); + void addBehavior(char label, int ledNumber); void populateBehaviors(); bool isEventTime(uint32_t interval, uint32_t &previousTime); void setupHandlers(); diff --git a/README.md b/README.md index aa82ff0..d408d62 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,20 @@ # LedString -FastLED wrapper to simplify lighting for model towns, castles, villages. +FastLED wrapper to simplify using LED lighting for model towns, castles, villages. 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. Behavior for individual LEDs is defined using a character string, one character per LED. Blanks can be inserted anywhere for readability, and are ignored. -#### Behaviors -W: White, always lit -R: Red, always lit -G: Green, always lit -B: Blue, always lit -Y: Yellow, always lit -O: Off -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. +#### Built-in Behaviors +* W: White, always lit +* R: Red, always lit +* G: Green, always lit +* B: Blue, always lit +* Y: Yellow, always lit +* O: Off (same as setting the color to Black) +* 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. An application may assign any other single character to a custom behavior, or override any of the standard ones (see example code Custom.ino). 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 deleted file mode 100644 index fc78c3a..0000000 --- a/examples/Custom/Custom.ino +++ /dev/null @@ -1,53 +0,0 @@ -#include -#define DATA_PIN 2 -#define NUM_LEDS 5 - -LedString myLedString; -CRGB leds[5]; - -void sequence(CRGB* leds, int ledNumber, CRGB colors[], int count) { - // step through an array of colors at intervals; - // Limitation: each color must occur only once or it will get stuck alternating between - // the first occurrence of that color and the next color. - for (int i=0; i(leds, NUM_LEDS); - myLedString.setup(leds); - - Serial.println("Adding custom handlers"); - myLedString.addHandler('2', &blink2, 3000); - myLedString.addHandler('1', &blink1, 2000); - Serial.println("Calling begin"); - myLedString.begin("21RGB"); -} - -void loop() { - myLedString.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/Sequence/Sequence.ino b/examples/Sequence/Sequence.ino index bb1575b..302fbda 100644 --- a/examples/Sequence/Sequence.ino +++ b/examples/Sequence/Sequence.ino @@ -38,7 +38,7 @@ void setup() { FastLED.clear(); FastLED.addLeds(leds, NUM_LEDS); - myLedString.setup(leds); + myLedString.setup(leds, NUM_LEDS); Serial.println("Adding custom handlers"); ColorSequence *cs1 = new ColorSequence('1', 500, colors1, 4); 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(); } diff --git a/examples/SwitchGroup/SwitchGroup.ino b/examples/SwitchGroup/SwitchGroup.ino deleted file mode 100644 index dff5c80..0000000 --- a/examples/SwitchGroup/SwitchGroup.ino +++ /dev/null @@ -1,50 +0,0 @@ -/* - SwitchGroup - - Demonstrates a LedString Handler subclass that toggles a random led in a group - on and off at random times, to simulate habitation in a model building or town. - For each group you create a new instance of SwitchGroup, assign it a label for - the pattern string, an ON color for the led, and min/max interval times. - - The handler needs only setup() and loop(). In setup() we count the leds that have - its label and pick a random interval and random led. In loop() we decrement a - countdown; when it reaches 0 we toggle the current led, disable the handler so - only this led is toggled, then pick a new random interval and led. -*/ - -#include -#define DATA_PIN 2 -#define NUM_LEDS 10 - -LedString myLedString; -CRGB leds[NUM_LEDS]; - -//void sayColor(String msg, CRGB color) { -// Serial.print(" " + msg + ": "); Serial.print(color.red); -// Serial.print(", "); Serial.print(color.green); -// Serial.print(", "); Serial.println(color.blue); -//} - -void setup() { - Serial.begin(115200); while (!Serial) { ; } - Serial.println("\n*****"); - - FastLED.clear(); - FastLED.addLeds(leds, NUM_LEDS); - myLedString.setup(leds); - - Serial.println("Adding custom handlers"); - SwitchGroup *group1 = new SwitchGroup('1', 1000, 8000, CRGB::Green); - myLedString.addHandler(group1); - SwitchGroup *group2 = new SwitchGroup('2', 200, 200, CRGB::White); - myLedString.addHandler(group2); - - - Serial.println("Calling begin"); - myLedString.begin("1111122222"); -} - -void loop() { - myLedString.loop(); -} - From 289f48a7d341b65ee5fd7ee9fe4aba30c6adb1b2 Mon Sep 17 00:00:00 2001 From: Doug Leary Date: Sun, 17 Jan 2021 00:52:21 -0800 Subject: [PATCH 12/16] minor renaming, new Custom example --- LedString.cpp | 4 ---- LedString.h | 7 ++----- examples/Custom/Custom.ino | 24 ++++++++++++++++++++++++ 3 files changed, 26 insertions(+), 9 deletions(-) create mode 100644 examples/Custom/Custom.ino diff --git a/LedString.cpp b/LedString.cpp index 17211b1..05e4227 100644 --- a/LedString.cpp +++ b/LedString.cpp @@ -181,10 +181,6 @@ void LedString::setLed(int i, CRGB color) { leds[i] = color; } -void LedString::resetAll() { - FastLED.clear(); -} - bool LedString::isOn(int led) { return ( (leds[led].red > 0) || diff --git a/LedString.h b/LedString.h index 1e6f56a..bb1433f 100644 --- a/LedString.h +++ b/LedString.h @@ -57,16 +57,14 @@ class LedString CRGB* leds = 0; String pattern; - // min/max brightness range of normal and intense flickers + 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 @@ -87,7 +85,6 @@ class LedString void turnOff(int led); void turnAllOn(); void turnAllOff(); - void resetAll(); void setLed(int i, CRGB color); private: 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(); +} From 806bf27d750a7c66e45c6bea775a85803a350720 Mon Sep 17 00:00:00 2001 From: Doug Leary Date: Sun, 29 Dec 2024 20:06:08 -0800 Subject: [PATCH 13/16] Update README.md --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index d408d62..956ac7a 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,10 @@ Behavior for individual LEDs is defined using a character string, one character * B: Blue, always lit * Y: Yellow, always lit * O: Off (same as setting the color to Black) -* 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. +* S: Switched on and off semi-randomly to give an appearance of habitation. At a random interval between SWITCH_MIN and 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. -An application may assign any other single character to a custom behavior, or override any of the standard ones (see example code Custom.ino). +An app may assign any other single character to a custom behavior, or override any of the standard ones (see example code Custom.ino). Example: "WWOFW SSFWO OOWSS FSSSW" @@ -25,13 +25,13 @@ The constant NUM_LEDS must be defined as per the FastLED docs. If the pattern st 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 default hardware is WS2811 or WS2812. To select different hardware you must make two edits: +- in LedString.h uncomment the appropriate line to set FIRE_MIN, etc. for your type of leds. +- 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 From 890a2f2430a5bd3a542cb4de363ac483f3b663ce Mon Sep 17 00:00:00 2001 From: Doug Leary Date: Sun, 29 Dec 2024 20:12:29 -0800 Subject: [PATCH 14/16] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 956ac7a..acd8e02 100644 --- a/README.md +++ b/README.md @@ -61,4 +61,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 the default 80ms cycle time. + From 69757ef8fe22048fa6453cb24ded966f6e371a9b Mon Sep 17 00:00:00 2001 From: Doug Leary Date: Sun, 29 Dec 2024 20:13:37 -0800 Subject: [PATCH 15/16] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index acd8e02..d16d7f3 100644 --- a/README.md +++ b/README.md @@ -61,5 +61,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, 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 the default 80ms cycle time. +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. From 38534ba814a90f71c8b539e4abd780261049e1e0 Mon Sep 17 00:00:00 2001 From: Doug Leary Date: Wed, 17 Dec 2025 23:26:20 -0800 Subject: [PATCH 16/16] minor: README updates and an nouncement README is out of date due to hasty changes I made a couple years ago and did not properly document. Some internal improvements are also needed, and the examples are unclear due to lack of comments. --- README.md | 49 ++++++++++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index d16d7f3..4f2602d 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,42 @@ -# LedString -FastLED wrapper to simplify using LED lighting for model towns, castles, villages. +## 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 + +------- -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. +# LedString +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. -Behavior for individual LEDs is defined using a character string, one character per LED. Blanks can be inserted anywhere for readability, and are ignored. +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. -#### Built-in Behaviors -* W: White, always lit -* R: Red, always lit -* G: Green, always lit -* B: Blue, always lit -* Y: Yellow, always lit -* O: Off (same as setting the color to Black) -* S: Switched on and off semi-randomly to give an appearance of habitation. At a random interval between SWITCH_MIN and 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. +#### Built-in Behaviors +**Static Colors** +* W: White +* R: Red +* G: Green +* B: Blue +* Y: Yellow +* O: Off -An app may assign any other single character to a custom behavior, or override any of the standard ones (see example code Custom.ino). +**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. 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 or WS2812. To select different hardware you must make two edits: -- in LedString.h uncomment the appropriate line to set FIRE_MIN, etc. for your type of leds. -- in LedString.cpp uncomment the appropriate call to FastLED.addLeds for your type of leds. +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 FastLED reqirements, which LedString is based on. Due to compile-time optimizations in FastLED, certain values cannot be passed in as parameters.