Skip to content

locusrobotics/aiorospy

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

72 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

aiorospy

An asyncio wrapper around rospy I/O interfaces for Python >=3.6 to enable use of the co-operative multitasking concurrency model.

Rationale

Rospy implements its I/O interfaces using callbacks and therefore handles concurrency with a preemptive multitasking model. This means that complex nodes need to worry about threading concerns such as locking (and therefore deadlocks), as well as how to structure callback-based control flow in a sane manner. If you've attempted to write a sufficiently complex rospy node that handles I/O from different sources, you probably understand how painful this can get.

Asyncio was added to the Python 3.5 standard library on a provisional bases, formalized in Python 3.6. It implements the capabilities to handle concurrency with a co-operative multitasking model. This means that a complex node can more easily manage its core loop through the use of awaitables in a single thread. This tends to make your code straightforward to write, reason about, and debug. If you're not convinced, check out the example that handles about a dozen I/O interfaces in a single event loop.

Aiorospy and asyncio might be for you if:

  • your rospy node wants to handle I/O from numerous sources in a complex manner
  • you're trying to implement a WebSocket or HTTP server in a single process alongside topics, services, and actions (if a multi-process WSGI/ASGI with IPC feels like overkill)
  • you've grown to hate maintaining complex rospy nodes because of callback hell, locking noise, and deadlocking
  • you're tired of having to debug complex rospy nodes where the core control flow jumps among countless threads
  • you want precdictability over what will happen next and in what order

Requirements

Python >= 3.6

Asyncio does not exist in Python 2 and is only provisional in Python 3.5. If you're using Xenial, you can install it using the deadsnakes ppa.

Simplifies dependency management and makes using a different version of python pretty easy. Check requirements.txt to see what additional dependencies are used when this package is built.

Examples

Check out the asyncio_examples/scripts folder for examples of topics, services, actions, and a composite node.

Take note that when using rospy and asyncio together, the following boilerplate is recommended:

import aiorospy
import asyncio
import rospy

rospy.init_node('node_name')

loop = asyncio.get_event_loop()

tasks = asyncio.gather(
    my_top_level_task(),
    # ...
)

# Any uncaught exceptions will cause this task to get cancelled
aiorospy.cancel_on_exception(tasks)
# ROS shutdown will cause this task toget cancelled
aiorospy.cancel_on_shutdown(tasks)

try:
    loop.run_until_complete(tasks)
except asyncio.CancelledError:
    pass

How it works

There's no desire to reimplement rospy this late in its life, so this package wraps the various rospy interfaces instead. This means that there are still underlying threads that handle TCP I/O for topics and services. But unlike rospy, the API is not callbacks that run in separate threads, but rather awaitables that run in the main thread's event loop. This is accomplished by using thread-safe intermediaries such as asyncio.call_soon_threadsafe and janus.Queue.