Release Notes: 2.0.0 implements the following changes:
The library is designed for projects that need to control values over time. For example: setting the new angle of a servo in 3.0 seconds; setting the brightness of an LED by fading up in 2.5 seconds; or moving a graphic on a screen. You can set the amount of time for a change in value, and apply easing to each move to make it seem more natural.
It also provides a function for running sequences of moves where each move in the sequence has a new position and speed. Sequences can be looped or repeated if desired.
More than one move or sequence can be run at the same time.
VarSpeedPython objects are designed to be called repeatedly and do not block execution. It is compatible with standard Python and CircuitPython (v8.0+ to support asyncio, more info here).
This Python library is descended from the VarspeedServo library (https://github.com/netlabtoolkit/VarSpeedServo), originally written for the Arduino in C++ (which was itself built on an early Arduino servo library). Unlike the old Arduino library, VarSpeedPython is not tied to servos, and can be used more generally for timed moves from one value to another. It is also not bound to any processor architecture with hardware interrupts etc.
Sync vs Async — which should I use?
Sync (
varspeed.py): best for beginners, existing curriculum, or CircuitPython without asyncio. Simple event-loop style — callmove()on every loop iteration.Async (
varspeed_async.py): use when you need concurrent actuators (e.g. a servo and an LED moving independently at the same time), or when you want to learnasync/awaitpatterns. Requires Python 3.9+ or CircuitPython withadafruit_asynciov3+.
Computer (simple) — Copy the files you need from the varspeed/ directory into your project folder:
| You want | Files to copy |
|---|---|
| Sync | varspeed/varspeed.py + varspeed/easing_functions.py |
| Async | varspeed/varspeed_async.py + varspeed/easing_functions.py |
Then import directly:
from varspeed import Vspeed # sync
from varspeed_async import Vspeed # async
Computer (repo clone) — Clone the repo and point PYTHONPATH at the varspeed/ directory:
git clone https://github.com/pvanallen/VarSpeedPython.git
cd VarSpeedPython
python -m venv .venv
source .venv/bin/activate
echo 'export PYTHONPATH="/path/to/VarSpeedPython/varspeed"' >> .venv/bin/activate
deactivate && source .venv/bin/activate
Device with CircuitPython — Copy varspeed/varspeed.py (sync) or varspeed/varspeed_async.py (async) and varspeed/easing_functions.py into the CIRCUITPY/lib/ directory. For the async version also add the asyncio lib folder from the Adafruit CircuitPython bundle. asyncio Requires CircuitPython v8+.
Uses varspeed_async.py — requires: from varspeed_async import Vspeed
import asyncio
from varspeed_async import Vspeed
vs = Vspeed(init_position=0, result="int")
async def main():
async for position, running, changed in vs.move(
new_position=100, time_secs=2.0, steps=20, easing="SineEaseInOut"
):
if changed:
print(position) # drive your actuator here
asyncio.run(main())
Uses varspeed.py — requires: from varspeed import Vspeed
from varspeed import Vspeed
vs = Vspeed(init_position=0, result="int")
while True:
position, running, changed = vs.move(new_position=100, time_secs=2.0, steps=20, easing="SineEaseInOut")
if changed:
print(position) # drive your actuator here
if not running:
break
class Vspeed():
Provides a non-blocking object that can be called repeatedly from an event loop with the move() and sequence() functions to generate a timed series of values from a current position to a new position(s).
def __init__(self, init_position = 0, result = "int", debug = False):
Creates and initializes a Vspeed object.
sync only — requires varspeed.py
from varspeed import Vspeed
def move(self, new_position = 0, time_secs = 2.0, steps = 20, easing = "LinearInOut", delay_start = 0.0):
Generates a series of values that transition from the current position to a new_position. Call this repeatedly from your event loop.
async only — requires varspeed_async.py
from varspeed_async import Vspeed
async for position, running, changed in vs.move(new_position, time_secs, steps, easing):
...
Async iterator that yields (position, running, changed) on every step, sleeping between steps. Replaces the sync move() — instead of calling in a loop, use async for.
Same as move() above (new_position, time_secs, steps, easing, delay_start).
def sequence(self, sequence, loop_max = 1):
Creates a series of values in a sequence of moves as specified in the sequence array. In the async version (varspeed_async.py), this is an async iterator used with async for. In the sync version (varspeed.py), call it repeatedly from your event loop.
(position, time_secs, steps, easing[, delay_start]).def sequence_change_seq_num(self, seq_position = 0):
Sets the current sequence number.
nothing
def sequence_run(self, value = True):
Pauses or unpauses a running sequence.
nothing
def set_position(self, position = 0):
Sets the current position from which the next move will proceed.
nothing
def set_bounds(self, lower_bound = 0, upper_bound = 1000, bounded=True):
Sets the lower and upper bounds of values returned by a move or sequence.
nothing
from varspeed import map_range # sync
from varspeed_async import map_range # async
map_range(value, in_min, in_max, out_min, out_max, result="float")
Maps a value from one range to another. Useful for converting sensor readings to actuator ranges.
"int" to return a rounded integer, "float" for a float (default)For any move (even within a sequence), you can set an easing function using any of the following classic Robert Penner easing types. For an animated and graphed visualization of each easing type, see https://pvanallen.github.io/VarSpeedPython/docs/easings_cheatsheet/.
For an explanation of the use of easing, see this article: Animation Principles in UI Design: Understanding Easing
Easing names in this library start with the family name (e.g. Linear, Quad, Cubic) followed by Ease and the direction (In, Out, or InOut). For example, CubicEaseInOut. The Gamma functions are unique to this library and not found on other easing references.
GammaEaseIn, GammaEaseOut, GammaEaseInOut
async only — requires varspeed_async.py (from varspeed_async import Vspeed)
Uses varspeed.py (from varspeed import Vspeed)
To set up on a CircuitPython hardware device:
varspeed/varspeed.py (sync) or varspeed/varspeed_async.py (async) in the lib directory on CIRCUITPYvarspeed/easing_functions.py in the lib directory on CIRCUITPYcode.py or main.py at the top of CIRCUITPYIf you have existing sync code and want to move to the async version, here is the key change:
Before (sync):
from varspeed import Vspeed
vs = Vspeed(init_position=0, result="int")
while True:
position, running, changed = vs.move(new_position=100, time_secs=2.0, steps=20, easing="SineEaseInOut")
if changed:
print(position)
if not running:
break
After (async):
import asyncio
from varspeed_async import Vspeed
vs = Vspeed(init_position=0, result="int")
async def main():
# iterate over the move() call
async for position, running, changed in vs.move(
new_position=100, time_secs=2.0, steps=20, easing="SineEaseInOut"
):
if changed:
print(position)
asyncio.run(main())
Key differences for the new async version:
move() is now an async iterator — use async for instead of calling it in a while loopasync def main() and call with asyncio.run(main())sequence() works with async for in the same wayasyncio.create_task() to run multiple actuators concurrently — this is the main reason to migrate