So, in addition to the led library itself, Mark and I both have higher level libraries that we use for doing a lot of our work. We use these libraries to give us things like oscillators, or patterns. I’m working n one that’s a combination of a task scheduler/oscillator library (and pool)/pattern manager.
The library is nowhere near publishing, but I can lay out some of the things that I’m doing in it to make my life easier, that may be helpful to some folks here. The sample code here is based on somewhat older versions of what I currently have (in part, because they don’t rely on extra infrastructure).
I start out with a base class that defines the interface for a pattern:
class CPattern {
protected:
CRGB *m_pLeds;
int m_nLeds;
public:
CPattern(CRGB *pStartLeds, int nLeds) : m_pLeds(pStartLeds), m_nLeds(nLeds); {}
virtual void draw() = 0;
};
Now, let’s have a “pattern” that basically fades everything in the leds that it’s given:
CFadePattern : public CPattern {
uint8_t m_nFadeAmount
public:
CFadePattern(CRGB *pStartLed, int nLeds, nFadeAmount) : CPattern(pStartLed, nLeds), m_nFadeAmount(nFadeAmount) {}
virtual void draw() {
for(int i = 0; i < m_nLeds; i++) {
m_pLeds[i].nscale8(m_nFadeAmount);
}
}
};
And how about a dot mover pattern:
CDotMover : public CPattern {
int m_nPos;
public:
CDotMover(CRGB *pLeds, int nLeds, int nStartPos) : CPattern(pLeds, nLeds), m_nPos(nStartPos) {}
virtual void draw() {
if(m_nPos >= m_nLeds) { m_nPos = 0; }
leds[m_nPos++] = CRGB::Blue;
}
};
Now, to use this stuff - I have a renderer that basically does the following (exploding it all out into the main program):
#include <FastSPI_LED2.h>
#define NUM_LEDS 100
struct CRGB leds[NUM_LEDS];
CPattern *pPatterns[] = {
new CFadePattern(leds, NUM_LEDS, 192),
// start a dot mover at led 10, over all the leds
new CDotMover(leds, NUM_LEDS, 10),
// start a dot mover at led 40, over all the leds
new CDotMover(leds, NUM_LEDS, 40),
// start a dot mover on leds 0-49, starting at the 5th led in that set
new CDotMover(leds, 50, 5),
// start a dot mover on leds 50-99, starting at the 10th led in that set
new CDotMover(leds+50, 50, 10),
NULL;
}
void setup() { LEDS.addLeds<WS2811, 6>(leds, NUM_LEDS); }
void loop() {
int iPattern=0;
while(pPattern[iPattern] != NULL) {
pPattern[iPattern++]->draw();
}
LEDS.show();
}
et. voila! Patterns!
The current incarnation of the library that i’m working on goes a bit further. It abstracts out the access to leds into classes that behave like arrays, but may do their own mapping to actual led structures differently in the background (e.g. a sliding window of leds, or skipping n leds for each led, etc…).
Also, I have a task manager that allows me to schedule tasks to run at regular intervals (so, for example, I can enforce a framerate where the renderer only draws once every Xms). Because of this, patterns have two methods - there’s an execute() method, that runs on every call to loop to allow a pattern to decide whether or not to update what it’s going to do when it draws[1]. This allows patterns to “move” at different rates than they’re drawing at. I use tasks to do things like read from sensors/inputs and make decisions, or to do other, non-led related regular tasks.
In addition, I have oscillators - so, for example, in the dot mover, instead of having it hard code to CRGB::Blue, instead, I’d have it be something like leds[i] = CHSV(m_hueOsc.get(), 255, 255); The oscillators can all run at their own rates and update independently of how they’re used. (What? some of my background is music and synthesis, and I have a love of modular synthesis…)
I’m still fighting with a good abstract representation/mapping to complex structures of leds, that code will probably go through a couple more rewrites, luckily I have some projects brewing that will make good use of it! Not only that, I’m also spec’ing out a pattern controller, so that I could define, say, 100 patterns and have them fade in/out and start and stop independently of each other and/or group them together, etc… (and then have an external controller, say, that allows controlling what patterns are running and what their parameters are).
However, even without all the frameworks/harness of the task executor and render and oscillators, hopefully the drops of code above can give people enough of a starting point to abstract out their drawing code so that they can do things like change up what leds their patterns are running on (or duplicate things, or have multiple patterns running at once!).
[1] that’s a bit misleading, actually, the task scheduler allows tasks to specify when they’d like to run next, so really execute gets called when it’s next time for that task to run, not on every loop