-
Notifications
You must be signed in to change notification settings - Fork 0
Animation Framework
The portal core, consisting of multiple (8 or 9) separate LED strips, will be driven by a single Arduino instance. As such, the Arduino will need to manage animations of all of these LED strips simultaneously. I.e., we cannot make use of blocking 'sleep' calls to orchestrate an animation.
I've adopted a strategy of creating animation implementations with a non-blocking doFrame
method that can be called at arbitrary points in time. An animation instance manages a single strand of LEDs, and the doFrame
method is responsible for 'drawing' the state of the LEDs in that strand at whatever point in time it is called. The animation remembers the time at which it was first started, and then based on the current time it is able to set LED states appropriately.
The main Arduino loop then simply iterates through the managed animations calling doFrame on each in sequence (interspersed with servicing input on the serial port dictating new portal states). (Prior to invoking doFrame
on an animation, it sets the appropriate I/O pin to be active.) This loop runs as fast as it can: i.e., it does not use on a set frame rate, but instead relies on the animations being able to draw themselves appropriately at arbitrary points in time.
From an OO design perspective, it would make sense for each animation to be implemented as a class containing requisite state associated with that animation, and an instance of the class could be created and disposed of as necessary. Given the Arduino's constrained environment, this has been modified somewhat. Specifically, the state and behavior of the animations have been divided into separate classes/structs. Each animation type has an associated 'state' class (deriving from a CommonState
class which contains state relevant to all animations. The animation class itself contains only behavior (i.e., method definitions); these methods accept a reference to an instance of the relevant state class. Animation classes all derive from an abstract Animation
class providing some common behavior and a polymorphic interface that can be invoked by the caller. (Technically speaking, the Animation
subclasses do contain state in the form of vtables for the virtual method definitions, but these are small, and Animation
class instances are singletons.
The state classes for each animation are gathered into an AnimationState
union
, providing a convenient way to allocate memory to contain state for an arbitrary Animation
. The Animation
subclasses are gathered into an Animations
struct
, a static singleton instance of which is allocated by the program.
The main Arduino service loop must manage Animation
and AnimationState
instances. Basic requirements are:
- Support independent animations for multiple strands of LEDs
- Support sequencing of animations for certain portal transitions (e.g., when a portal goes neutral, show a brief 'red flash', followed by a constant white color
For each strand of LEDs, a small queue/circular buffer of animations is maintained. If there is a single animation in the queue, it is repeated indefinitely. If there are multiple animations in the queue, each animation is played for one cycle (as defined by the animation itself), and then the final animation is repeated indefinitely.
The queue contains a pointer to the (singleton) Animation
instances associated with the desired animation. However, as discussed above, the state associated with animation instances is stored separately from the Animation
class instance itself. This is accommodated by storing an array of AnimationState
union
s for each LED strip being managed. These arrays are the same size as the circular buffer of Animation *
instances mentioned above. The circular buffer implementation provides visibility to the index within the internal circular buffer associated with the Animation *
in question. Calling code makes uses of that index to find the appropriate AnimationState
instance to use in each case.
NeoPixel LEDs use a 32-bit data type to encode color (with 8-bit red, green, blue, and optionally white components). In order to provide a convenient way to deal with this representation, I've defined a Color
union
, which provides access to both the individual 8-bit components and the 32-bit integer value. Animation
methods accept and AnimationState
members store instances of Color
rather than uint32_t
bitpacked values or uint8_t
component values.
In addition, several overrides of method ToColor
are provided to create Color
instances from RGB or RGBW components, or from 32-bit bitpacked representations.