/animately

Animately is an Arduino library that allows for precise animation of props or robots, down to the millisecond, without the need for thread-blocking (delay()) or complex state machines

Primary LanguageC++MIT LicenseMIT

Animately

Animately is an Arduino library that allows for precise animation of props or robots, down to the millisecond, without the need for thread-blocking (delay()) or complex state machines. This frees you to focus on the creative aspects of animating rather than the implementation details.

Animately provides an animation timeline, tweening options, and several basic classes for interfacing with the outside world (LEDs, servos, switches, etc.) It's provides a flexible DSL built around delegates and uses an event-driven architecture. The delegates are designed to be flexible and modular; if you have an instance with method with the right signature, you can plug it in. It doesn't matter whether you've inherited from any interfaces or are using Animately classes at all. This allows you to easily create new interfaces and tweens as you go.

Animately is optimized for integrated systems and has a memory footprint that can be tuned based on the needs of the prop and microcontroller. It can comfortably run on an ATmega328 (an Arduino Uno), and can be expanded with additional capabilities if using something bigger like an ATmega2560.

Configuration

Overall

TIMELINE_MAX_SCHEDULED_ENTRIES

How many actions can be put on the schedule at the same time. Once an action starts it will be removed from this list, freeing up the space. Adding actions when the timeline is already full will result in the action not getting scheduled. This setting has a substantial impact on SRAM usage.

Default value: 10

TIMELINE_MAX_ACTIVE_ENTRIES

How many actions can be in progress simultaneously. Once an action moves from being scheduled to actually starting, it will persist in this data store until the action completes. This setting has a substantial impact on SRAM usage.

Default value: 5

Logging

LOG

If you overwrite this with a no-op (#define LOG) then you'll disable the entire logging system and free up a non-trivial amount of SRAM.

LOG_BUFFER_SIZE

You can define a higher limit if you're like to create longer log messages.

Default value: 60 bytes

ERROR

If you overwrite this with a no-op (#define ERROR) then you'll free up a tiny bit of memory but at the cost of not knowing when the program does something unexpected. Changing this is not recommended.

Error Codes

Animately uses error codes to cut down on the length of error messages that it has to store and transmit. You can overwrite any of these with empty strings to cut down on SRAM usage, or change the names to something more meaningful if you have SRAM to spare.

COULD_NOT_ALLOCATE

Could not allocate for an operation. This likely means that you have TIMELINE_MAX_SCHEDULED_ENTRIES or TIMELINE_MAX_ACTIVE_ENTRIES set too low.

Default value: "E01"

COULD_NOT_SCHEDULE

Could not schedule the prop as requested. See the surrounding logs for more information.

Default value: "E03"

INVALID_INDEX

An invalid index was supplied (less than zero or equal to or greater than size.

Default value: "E04"

Usage

Timeline

Timeline is the class that holds and coordinates all the animations within Animately. It can be initialized without any arguments:

#include "Core/Timeline.h"
using namespace Animately;
Timeline timeline;

To play the animations, you must call timeline.tick() in the main loop:

void setup() {
    Serial.begin(9600); // Needed for Animately logging
}

void loop() {
    timeline.tick();
}

tick() should be invoked at least once per millisecond to ensure smooth animations. If you have a timer free, a timer interupt would be a good place to call tick() from.

Scheduling

Items can be scheduled to run in the future using timeline.schedule. The schedule method has several simplified signatures, but these are the common parameters you'll find across all of them:

  • int fromValue - The integer value to start animating from. startDelegate will be called with this value.
  • int toValue - The target integer value to end at once the animation is complete. endDelegate will be called with this value.
  • milliseconds delay - How soon, in milliseconds, to start playing this animation. This is always relative to the current time.
  • milliseconds duration - How long, in milliseconds, it takes to smoothly go from fromValue to toValue.
  • TimelineEventStartDelegate startDelegate - Method to call when the animation starts. See Action Start Delegate below.
  • TimelineTransitionDelegate transitionDelegate - Method to call while the animation is in progress. See Action Transition Delegate below.
  • TimelineTweenDelegate tweenDelegate - Method to call to tween the values. See Action Tween Delegate below.
  • TimelineEventEndDelegate endDelegate - Method to call when the animation finishes. See Action End Delegate below.

If there are more than TIMELINE_MAX_SCHEDULED_ENTRIES items scheduled for animation already then the request to schedule a new item will be ignored.

Events and Delegates

All delegates can take in a pointer to any instance method, as long as the method signature matches what is expected below. This means you can very easily schedule arbitrary method invocations as part of your animation, or create new tweening algorithms.

Action Start Delegate

Method signature: void methodName(int fromValue)

The Action Start Delegate is called when a scheduled action is started. This is where you can setup your variables, turn on an LED, set the starting position of a servo, etc. It will always supply the fromValue that was supplied during scheduling.

Action Transition Delegate

Method signature: void methodName(int transitionValue)

Action Transition Delegate provides a place to smoothly transition between two states/values. The delegate is called every time the timeline ticks. This means that it may be invoked more often than once per millisecond, depending on how fast the main timeline tick is being called. Because of this, all behavior in this method must be time-independent and rely only on the incoming value to determine how to respond.

transitionValue has already been passed through the tweening algorithm when it reaches this point, so it may progress from fromValue to toValue non-linearly, depending on how the tween is tuning the value. There's a chance the tweening algorithm may push the value outside of the fromValue to toValue range, if it's doing something like an overshoot smoothing or spring smoothing.

Action Tween Delegate

Method signature: float methodName(float transitionAmount)

Action Tween Delegate provides a place to apply a custom in-betweening algorithm to the animation, such as ease-in. The method is supplied with a float value from zero to one and the method should return a value in the same range. It's acceptable for the method to return slightly less than zero or slightly more than one if the animation smoothing purposely overshoots one of the target values, but this should be kept to a minimum to prevent accidentally hitting hard limits on the parts being animated.

Action End Delegate

Method signature: void methodName(int toValue)

The Action Start Delegate is called when a scheduled action is finished. This is where you can shut off power to parts, save values, etc. It will always supply the toValue that was supplied during scheduling, regardless of what the tweening ended at.

Memory Usage

Every timeline entry uses 37 bytes of SRAM, which can add up quickly if you're scheduling longer animations. So it's important that you tune TIMELINE_MAX_SCHEDULED_ENTRIES carefully. If you need to play longer animations, you should chain together several shorter animations, so it doesn't have to load the entire animation into memory at once.

Roadmap

  • Streaming animations from flash/program memory.
    • Delivery estimate: Unknown.
    • This would allow for smaller SRAM usage, as the timeline would act only as a buffer.
  • Streaming animations from SD card.
    • Delivery estimate: Unknown.
    • This would allow for even larger animations, while keeping SRAM usage to a minimum.