Asyncio interface for ROS 2, Zenoh, and other robotic middlewares.
Project description
Asyncio For Robotics
| Requirements | Compatibility | Tests |
|---|---|---|
The Asyncio For Robotics (afor) library makes asyncio usable with ROS 2, Zenoh and more, letting you write linear, testable, and non-blocking Python code.
- Better syntax.
- Only native python: Better docs and support.
- Simplifies testing.
Will this make my code slower? No.
Will this make my code faster? No. However, asyncio will help YOU write
better, faster code.
[!TIP]
asyncio_for_roboticsinterfaces do not replace their primary interfaces! We add capabilities, giving you more choices, not less.
Install
Barebone
Compatible with ROS 2 (jazzy,humble and newer) out of the box. This library is pure python (>=3.10), so it installs easily.
pip install asyncio_for_robotics
Along with Zenoh
pip install asyncio_for_robotics eclipse-zenoh
Read more
- Detailed ROS 2 tutorial
- Detailed examples
- Cross-Platform deployment even with ROS
- Usage for software testing
Available interfaces:
- Rate: Every tick of a clock. (native)
- TextIO:
stdoutlines of aPopenprocess (and otherTextIOfiles). (native) - ROS 2: Subscriber, Service Client, Service Server.
- Zenoh: Subscriber.
- Implement your own interface!
[!TIP] An interface is not required for every operation. ROS 2 native publishers and nodes work just fine. Furthermore, advanced behavior can be composed of generic
aforobject (see ROS2 Event Callback Example).
Additional Projects and Interfaces
- gogo_keyboard: Subscribe to keyboard key presses and release.
- asyncio_gazebo: Subscribe to Gazebo transport.
Code sample
Syntax is identical between ROS 2, Zenoh, TextIO, Rate...
Wait for messages one by one
Application:
- Get the latest sensor data
- Get clock value
- Wait for trigger
- Wait for next tick of the Rate
- Wait for system to be operational
sub = afor.Sub(...)
# Get the latest message
latest = await sub.wait_for_value()
# Get a new message
new = await sub.wait_for_new()
# Get the next message received
next = await sub.wait_for_next()
Continuously listen to a data stream
Application:
- Process a whole data stream
- React to changes in sensor data
- Execute on every tick of the Rate
# Continuously process the latest messages
async for msg in sub.listen():
status = foo(msg)
if status == DONE:
break
# Continuously process all incoming messages
async for msg in sub.listen_reliable():
status = foo(msg)
if status == DONE:
break
Improved Services / Queryable for ROS 2
[!NOTE] This is only for ROS 2.
Application:
- Client request reply from a server.
- Servers can delay their response without blocking (not possible in native ROS 2)
# Server is once again a afor subscriber, but generating responder objects
server = afor.Server(...)
# processes all requests.
# listen_reliable method is recommanded as it cannot skip requests
async for responder in server.listen_reliable():
if responder.request == "PING!":
reponder.response = "PONG!"
await asyncio.sleep(...) # reply can be differed
reponder.send()
else:
... # reply is not necessary
# the client implements a async call method
client = afor.Client(...)
response = await client.call("PING!")
Process for the right amount of time
Application:
- Test if the system is responding as expected
- Run small tasks with small and local code
# Listen with a timeout
data = await afor.soft_wait_for(sub.wait_for_new(), timeout=1)
if isinstance(data, TimeoutError):
pytest.fail(f"Failed to get new data in under 1 second")
# Process a codeblock with a timeout
async with afor.soft_timeout(1):
sum = 0
total = 0
async for msg in sub.listen_reliable():
number = process(msg)
sum += number
total += 1
last_second_average = sum/total
assert last_second_average == pytest.approx(expected_average)
Apply pre-processing to a data-stream
Application:
- Parse payload of different transport into a common type.
# ROS2 String type afor subscriber
inner_sub: Sub[String] = afor.ros2.Sub(String, "topic_name")
# converted into a subscriber generating python `str`
ros2str_func = lambda msg: msg.data
sub: Sub[str] = afor.ConverterSub(sub=inner_sub, convert_func=ros2str_func)
About Speed
The inevitable question: “But isn’t this slower than the ROS 2 executor? ROS 2 is the best!”
In short: rclpy's executor is the bottleneck.
- Comparing to the best ROS 2 Jazzy can do (
SingleThreadedExecutor),aforincreases latency from 110us to 150us. - Comparing to other execution methods,
aforis equivalent if not faster. - If you find it slow, you should use C++ or Zenoh (or contribute to this repo?).
Benchmark code is available in ./tests/bench/, it consists in two pairs of pub/sub infinitely echoing a message (using one single node). The messaging rate, thus measures the request to response latency.
With afor |
Transport | Executor | Frequency (kHz) | Latency (ms) | |
|---|---|---|---|---|---|
| ✔️ | Zenoh | None | 95 | 0.01 | |
| ✔️ | ROS 2 | Experimental Asyncio | 17 | 0.06 | |
| ❌ | ROS 2 | Experimental Asyncio | 13 | 0.08 | |
| ❌ | ROS 2 | SingleThreaded | 9 | 0.11 | |
| ✔️ | ROS 2 | SingleThreaded | 7 | 0.15 | |
| ✔️ | ROS 2 | MultiThreaded | 3 | 0.3 | |
| ❌ | ROS 2 | MultiThreaded | 3 | 0.3 | |
| ✔️ | ROS 2 | ros_loop Method |
3 | 0.3 |
Details:
uvloopwas used, replacing the asyncio executor (more or less doubles the performances for Zenoh)- RMW was set to
rmw_zenoh_cpp - ROS2 benchmarks uses
afor'sros2.ThreadedSession(the default inafor). - Only the Benchmark of the
ros_loopmethod usesafor's second type of session:ros2.SynchronousSession. - ROS 2 executors can easily be changed in
aforwhen creating a session. - The experimental
AsyncioExecutorPR on ros rolling by nadavelkabets is incredible https://github.com/ros2/rclpy/pull/1399. Maybe I will add proper support for it (but only a few will want to use an unmerged experimental PR of ROS 2 rolling). - If there is interest in those benchmarks I will improve them, so others can run them all easily.
Analysis:
- Zenoh is extremely fast, proving that
aforis not the bottleneck. - This
AsyncioExecutorhaving better perf when usingaforis interesting, becauseafordoes not bypass code.- I think this is due to
AsyncioExecutorhaving some overhead that affects its own callback. - Without
aforthe ROS 2 callback executes some code and publishes. - With
aforthe ROS 2 callback returns immediately, and fully delegates execution toasyncio.
- I think this is due to
- The increase of latency on the
SingleThreadedexecutors proves that getting data in and out of therclpyexecutor and thread is the main bottleneck.AsyncioExecutordoes not have such thread, thus can directly communicate.- Zenoh has its own thread, however it is built exclusively for multi-thread operations, without any executor. Thus achieves far superior performances.
MultiThreadedExecutoris just famously slow.- Very surprisingly, the well known
ros_loopmethod detailed here https://github.com/m2-farzan/ros2-asyncio is slow.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file asyncio_for_robotics-1.3.5.tar.gz.
File metadata
- Download URL: asyncio_for_robotics-1.3.5.tar.gz
- Upload date:
- Size: 37.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.23
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
879713bb0a0470999d0f0013421476b65c4a1abbbcf528da807fd9f436ebc70f
|
|
| MD5 |
173f7b3b396f87065330ad0aa4df98ce
|
|
| BLAKE2b-256 |
baf9ee563f674c4f8d23c8308495734a03d71a31792607deeaf4819a5cf2b195
|
File details
Details for the file asyncio_for_robotics-1.3.5-py3-none-any.whl.
File metadata
- Download URL: asyncio_for_robotics-1.3.5-py3-none-any.whl
- Upload date:
- Size: 40.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.23
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b0051680182668149575cedf3092a97f50e6d5c27ea46ac0b263671e193891d5
|
|
| MD5 |
80a9e985489fc243a2fe97c543f22d01
|
|
| BLAKE2b-256 |
9d369c270a9a0cd7e8a7df1c26f34a17fba8abc01312e0cbcb3fbadb00ff75dc
|