VarSpeedPython

VarSpeedPython library — v2.0

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 — call move() 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 learn async/await patterns. Requires Python 3.9+ or CircuitPython with adafruit_asyncio v3+.



Installation

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+.


Quick Start (async — start here)

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())

Quick Start (sync)

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

API Reference

CLASS: Vspeed

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).


init

def __init__(self, init_position = 0, result = "int", debug = False):

Creates and initializes a Vspeed object.

Args

Returns


move (sync)

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.

Args

Returns


move (async)

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.

Args

Same as move() above (new_position, time_secs, steps, easing, delay_start).

Yields


sequence

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.

Args

Returns / Yields


sequence_change_seq_num

def sequence_change_seq_num(self, seq_position = 0):

Sets the current sequence number.

Args

Returns

nothing


sequence_run

def sequence_run(self, value = True):

Pauses or unpauses a running sequence.

Args

Returns

nothing


set_position

def set_position(self, position = 0):

Sets the current position from which the next move will proceed.

Args

Returns

nothing


set_bounds

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.

Args

Returns

nothing


map_range

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.

Args

Returns


Easing Types

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.


Examples

Async examples

async only — requires varspeed_async.py (from varspeed_async import Vspeed)

No Hardware

Sync examples

Uses varspeed.py (from varspeed import Vspeed)

No Hardware


CircuitPython Setup

To set up on a CircuitPython hardware device:


Migration: Sync → Async

If 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: