Skip to content

Exported Playback Logic

Advanced Topic Skip if you’re new, explore when you’re ready.

When you export animations, you set some basic triggering logic that is run for you “code-free.” That logic gives you options to trigger animations

  • As a starting animation
  • As an idle animation to play when no other animation is running
  • Based on a pin state (high / low / some analog range)

However, you may likely have use cases that aren’t covered by these built-in options. In this case, you can add code to the callback file to implement your own animation triggering logic.

There are three convenient methods you can use to write your own animation triggering logic:

bool streamIsInProgress()
// returns TRUE if an animation is currently playing, FALSE if not
bool isPlaying = BottangoCore::commandStreamProvider->streamIsInProgress()

You can use streamIsInProgress to detect if anything is currently playing.

void startCommandStream(byte animationIndex, bool looping)
// Start an animation playing by index, and if it should loop or not
// pass a byte for animation index and a bool for if should loop
BottangoCore::commandStreamProvider->startCommandStream(animationIndex, looping)

You can use startCommandStream to start playing an animation by index value, and loop it or not.

BottangoArduinoCallbacks.cpp
// stop playing the current animation, and if you should also fully shut down the firmware
// pass true to shutdown the firmware, and false to only stop playing the current animation.
BottangoCore::stop(bool shutDownFirmware)

You can use stop to stop playing an animation if one is playing. If you pass true it will also shut down the firmware and prevent any more movement until the hardware restarts. If you just want to stop the animation, pass false.

The normal recommended place to put your animation triggering logic is in the onEarlyLoop callback. You don’t have to put it there, but it’s a good spot if you don’t have a reason to put it somewhere else.

Here’s a few examples of putting it all together. Pin initialization and defines are placed in the callback or near it for formatting reasons for the documentation. Better practice would be to move the defines to a more appropriate location, and to do pinMode initialization in the onThisControllerStarted callback rather than every loop callback.

This is a very basic example of starting an animation index 2 when a button is pressed and an electrical state on a pin is detected. Note that you can achieve this same result “code-free” using the built-in export triggering features, but the custom code version is given below as a teaching tool.

BottangoArduinoCallbacks.cpp
void onEarlyLoop()
{
// is a button on pin 6 pressed
pinMode(6, INPUT);
bool startPressed = digitalRead(6) == LOW;
// if button is pressed, and we're not playing an animation
if (startPressed && BottangoCore::commandStreamProvider->streamIsInProgress() == false)
{
// start animation 2 without looping
BottangoCore::commandStreamProvider->startCommandStream(2, false);
}
}

In this example, we play 4 exported animations in order, starting the next immediately after the previous ends. We wrap back to the first animation after the last one plays.

BottangoArduinoCallbacks.cpp
#define NUM_ANIMATIONS 4
byte currentAnimIndex = 0;
void onEarlyLoop()
{
// check if anything is playing
// if the firmware just started or
// the last animation finished, we'll get false
if (BottangoCore::commandStreamProvider->streamIsInProgress() == false)
{
// start the current animation
BottangoCore::commandStreamProvider->startCommandStream(currentAnimIndex, false);
// increment the counter
currentAnimIndex++;
// wrap it back to the start if we're at the end
if (currentAnimIndex == NUM_ANIMATIONS)
{
currentAnimIndex = 0;
}
}
}

Here’s a more advanced example putting these methods together that plays a random animation each time a button on pin 5 is pressed while nothing is currently playing, and stops playing if a button on pin 6 is pressed.

BottangoArduinoCallbacks.cpp
#define BUTTON_START_PLAYING 5
#define BUTTON_STOP_PLAYING 6
#define NUM_ANIMATIONS 3
#define STOP_DEBOUNCE 500
unsigned long lastStopButtonTime = 0;
void onEarlyLoop()
{
pinMode(BUTTON_START_PLAYING, INPUT);
pinMode(BUTTON_STOP_PLAYING, INPUT);
// is stop button pressed (accounting for debounce timing?)
bool stopPressed = digitalRead(BUTTON_STOP_PLAYING) == LOW && millis() - lastStopButtonTime >= STOP_DEBOUNCE;
// if stop is pressed and we're playing an animation
if (stopPressed && BottangoCore::commandStreamProvider->streamIsInProgress() == true)
{
// stop playing
BottangoCore::stop(false);
// note time to debounce the next press
lastStopButtonTime = millis();
}
// is start button pressed
bool startPressed = digitalRead(BUTTON_START_PLAYING) == LOW;
// if start is pressed, and we're not playing an animation
if (startPressed && BottangoCore::commandStreamProvider->streamIsInProgress() == false)
{
// generate a random number between 0 (inclusive) and 3 (exclusive ) (IE 0, 1, or 2)
byte animationIndexToPlay = random(0, NUM_ANIMATIONS);
// start that animation without looping
BottangoCore::commandStreamProvider->startCommandStream(animationIndexToPlay, false);
}
}

Simple debouncing is in this example on the stop button to only allow the stop button press to be triggered once every 500ms.