Package: vulp
Vulp – Robot/simulation switchVulp provides an action-observation loop to control robots from a standalone "agent" process, like this:The agent can be a simple Python script with few dependencies.Vulp is designed for robots built with the mjbots stack (moteusservo controllers andpi3hatcommunication board). It provides a robot/simulation switch to train or test agents inBulletbefore running them on the real system.Vulp supports Linux and macOS for development, and Raspberry Pi OS for robot deployment.Installationpip install vulpExampleCheck out theupkierepository for an example where Vulp is used to implement simulation environments, real-robot spines, state observers and locomotion agents.DetailsMore accurately, Vulp is a tiny inter-process communication (IPC) protocol shipped with reference libraries (currently in Python and C++, other languages welcome). It is suitable for tasks that requirereal-timebutnothigh-frequency performance. The main use case for this is balancing, as there istheoretical and empirical evidencesuggesting that bipeds and quadrupeds can balance themselves as leisurely as 5–15 Hz, although balance control is frequently implemented at 200–1000 Hz. And if you are wondering whether Python is suitable for real-time applications, we were too! Until wetried it out.In Vulp, a fast program, called aspine, talks to a slow program, called anagent, in a standard action-observation loop. Spine and agent run in separate processes and exchangeactionandobservationdictionaries through shared memory. For instance,actioncan be a set of joint commands andobservationa set of joint observations. Vulp provides a pipeline API to grow more complex spines with additional controllers (for higher-level actions) and observers (for richer observations). For example, a spine can run an inverse kinematics solver, or output its own ground contact estimation.Features and non-featuresAll design decisions have their pros and cons. Take a look at the features and non-features below to decide if Vulp is a fit to your use case.FeaturesRun the same Python code on simulated and real robotsInterfaces with to themjbots pi3hatand mjbots actuatorsInterfaces with to theBulletsimulatorObserver pipeline to extend observations🏗️ Controller pipeline to extend actionsSoft real-time: spine-agent loop interactions are predictable and repeatableUnit tested, and not only with end-to-end testsNon-featuresLow frequency: Vulp is designed for tasks that run in the 1–400 Hz range (like balancing bipeds or quadrupeds)Soft, not hard real-time guarantee: the code is empirically reliable by a large margin, that's itWeakly-typed IPC: typing is used within agents and spines, but the interface between them is only checked at runtimeAlternativesIf any of the non-features is a no-go to you, you may also want to check out these existing alternatives:kodlab_mjbots_sdk- C++-only framework integrated withLCMfor logging and remote I/O. Still a work in progress, only supports torque commands as of writing this note.mc_rtc- C++ real-time control framework from which Vulp inherited, among others, the idea of running the same code on simulated and real robots. The choice of a weakly-typed dictionary-based IPC was also inspired by mc_rtc's data store. C++ controllers are bigger cathedrals to build but they can run at higher frequencies.robot_interfaces- Similar IPC between non-realtime Python and real-time C++ processes. The main difference lies in the use of Python bindings and action/observation types (more overhead, more safeguards) where Vulp goes structureless (faster changes, faster blunders). Also, robot_interfaces enforces process synchronization with atime-series APIwhile in Vulp this is up to the agent (most agents act greedily on the latest observation).ros2_control- A C++ framework for real-time control using ROS2 (still a work in progress). Its barrier of entry is steeper than the other alternatives, making it a fit for production rather than prototyping, as it aims for compatibility with other ROS frameworks likeMoveIt. A Vulp C++ spine is equivalent to a ROSControllerInterfaceimplementing the dictionary-based IPC protocol.If your robot is built with some of the following open hardware components, you can also use their corresponding Python bindings directly:moteus- bindings for moteus brushless controllers alsorun well up to 200 Hz.odri_control_interface- interface to control robots built with theODRIMaster Board.Using control bindings directly is a simpler alternative if you don't need the action-observation loop and simulation/real-robot switch from Vulp.Q and APerformanceHow can motion control be real-time in Python, with garbage collection and all?Python agents talk with Vulp spines via theSpineInterface, which can process both actions and observations inabout 0.7 ± 0.3 ms. This leaves plenty of room to implement other control components in a low-frequency loop. You may also be surprised at how Python performance has improved in recent years (most "tricks" that were popular ten years ago have been optimized away in CPython 3.8+). To consider one data point, here are the cycle periods measured in a complete Python agent for Upkie (the Pink balancer fromupkie) running on a Raspberry Pi 4 Model B (Quad core ARM Cortex-A72 @ 1.5GHz). It performs non-trivial tasks like balancing and whole-body inverse kinematics by quadratic programming:Note that the aforementioned 0.7 ± 0.3 ms processing time happens on the Python side, and is thus included in the 5.0 ms cycles represented by the orange curve. Meanwhile the spine is set to a reference frequency of 1.0 kHz and its corresponding cycle period was measured here at 1.0 ± 0.05 ms.I just started a simulation spine but it's surprisingly slow, how come?Make sure you switch Bazel'scompilation modeto "opt" when running both robot experiments and simulations. The compilation mode is "fastbuild" by default. Note that it is totally fine to compile agents in "fastbuild" during development while testing them on a spine compiled in "opt" that keeps running in the background.I have a Bullet simulation where the robot balances fine, but the agent repeatedly warns it "Skipped X clock cycles". What could be causing this?This happens when your CPU is not powerful enough to run the simulator in real-time along with your agent and spine. You can callSpine::simulatewithnb_substeps = 1instead ofSpine::run, which will result in the correct simulation time from the agent's point of view but make the simulation slower than real-time from your point of view.I'm running a pi3hat spine, why are my timings more erratic than the ones plotted above?Make sure you configure CPU isolation and set the scaling governor toperformancefor real-time performance on a Raspberry Pi.Design choicesWhy use dictionaries rather than aninterface description languagelike Protocol Buffers?Interface description languages like Protocol Buffers are strongly typed: they formally specify a data exchange format that has to be written down and maintained, but brings benefits like versioning or breaking-change detection. Vulp, on the other hand, follows a weakly-typed, self-describing approach that is better suited to prototyping with rapidly-changing APIs: the spec is in the code. If an agent and spine communicate with incompatible/incomplete actions/observations, execution will break, begging for developers to fix it.Why the weakly-typed dictionary IPC rather than Python–C++ bindings?Vulp is designed for prototyping: it strives to eliminate intermediaries when it can, and keep a low barrier of entry. Python bindings bring the benefits of typing and are a good choice in production contexts, but like interface description languages, they also add overhead in terms of developer training, bookkeeping code and compilation time. Vulp rather goes for a crash-early approach: fast changes, fast blunders (interface errors raise exceptions that end execution), fast fixes (know immediately when an error was introduced).Is it possible to run two agents at the same time?That is not possible. One of the core assumptions in Vulp is that the agent and the spine are two respective processes communicating via one single shared-memory area. In this Vulp differs frome.g.ROS, which is multi-process by design. This design choice is discussed in#55.Why the name "Vulp"?Vulpmeans "fox" in Romansh, a language spoken in the Swiss canton of the Grisons. Foxes are arguably quite reliable in their reaction times 🦊